summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Devices/Storage
parentInitial commit. (diff)
downloadvirtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz
virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Storage')
-rw-r--r--src/VBox/Devices/Storage/ATAPIPassthrough.cpp955
-rw-r--r--src/VBox/Devices/Storage/ATAPIPassthrough.h100
-rw-r--r--src/VBox/Devices/Storage/Debug.cpp1152
-rw-r--r--src/VBox/Devices/Storage/DevAHCI.cpp6280
-rw-r--r--src/VBox/Devices/Storage/DevATA.cpp7959
-rw-r--r--src/VBox/Devices/Storage/DevBusLogic.cpp4545
-rw-r--r--src/VBox/Devices/Storage/DevFdc.cpp3114
-rw-r--r--src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp5849
-rw-r--r--src/VBox/Devices/Storage/DevLsiLogicSCSI.h3648
-rw-r--r--src/VBox/Devices/Storage/DrvDiskIntegrity.cpp2115
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-darwin.cpp758
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp434
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-linux.cpp389
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-solaris.cpp421
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-win.cpp558
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.cpp1571
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.h192
-rw-r--r--src/VBox/Devices/Storage/DrvHostDVD.cpp525
-rw-r--r--src/VBox/Devices/Storage/DrvHostFloppy.cpp110
-rw-r--r--src/VBox/Devices/Storage/DrvRamDisk.cpp1843
-rw-r--r--src/VBox/Devices/Storage/DrvSCSI.cpp1532
-rw-r--r--src/VBox/Devices/Storage/DrvVD.cpp6028
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-darwin.cpp534
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-generic.cpp60
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-win.cpp563
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt.h94
-rw-r--r--src/VBox/Devices/Storage/IOBufMgmt.cpp535
-rw-r--r--src/VBox/Devices/Storage/IOBufMgmt.h111
-rw-r--r--src/VBox/Devices/Storage/Makefile.kup0
-rw-r--r--src/VBox/Devices/Storage/UsbMsd.cpp2401
-rw-r--r--src/VBox/Devices/Storage/VBoxSCSI.cpp532
-rw-r--r--src/VBox/Devices/Storage/VBoxSCSI.h158
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp418
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h693
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp257
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp174
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp1757
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp630
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp459
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp88
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp117
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h202
-rw-r--r--src/VBox/Devices/Storage/swab.h64
43 files changed, 59925 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/ATAPIPassthrough.cpp b/src/VBox/Devices/Storage/ATAPIPassthrough.cpp
new file mode 100644
index 00000000..9c79aff5
--- /dev/null
+++ b/src/VBox/Devices/Storage/ATAPIPassthrough.cpp
@@ -0,0 +1,955 @@
+/* $Id: ATAPIPassthrough.cpp $ */
+/** @file
+ * VBox storage devices: ATAPI emulation (common code for DevATA and DevAHCI).
+ */
+
+/*
+ * Copyright (C) 2012-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_DEV_IDE
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <VBox/cdefs.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+
+#include "ATAPIPassthrough.h"
+
+/** The track was not detected yet. */
+#define TRACK_FLAGS_UNDETECTED RT_BIT_32(0)
+/** The track is the lead in track of the medium. */
+#define TRACK_FLAGS_LEAD_IN RT_BIT_32(1)
+/** The track is the lead out track of the medium. */
+#define TRACK_FLAGS_LEAD_OUT RT_BIT_32(2)
+
+/** Don't clear already detected tracks on the medium. */
+#define ATAPI_TRACK_LIST_REALLOCATE_FLAGS_DONT_CLEAR RT_BIT_32(0)
+
+/**
+ * Track main data form.
+ */
+typedef enum TRACKDATAFORM
+{
+ /** Invalid data form. */
+ TRACKDATAFORM_INVALID = 0,
+ /** 2352 bytes of data. */
+ TRACKDATAFORM_CDDA,
+ /** CDDA data is pause. */
+ TRACKDATAFORM_CDDA_PAUSE,
+ /** Mode 1 with 2048 bytes sector size. */
+ TRACKDATAFORM_MODE1_2048,
+ /** Mode 1 with 2352 bytes sector size. */
+ TRACKDATAFORM_MODE1_2352,
+ /** Mode 1 with 0 bytes sector size (generated by the drive). */
+ TRACKDATAFORM_MODE1_0,
+ /** XA Mode with 2336 bytes sector size. */
+ TRACKDATAFORM_XA_2336,
+ /** XA Mode with 2352 bytes sector size. */
+ TRACKDATAFORM_XA_2352,
+ /** XA Mode with 0 bytes sector size (generated by the drive). */
+ TRACKDATAFORM_XA_0,
+ /** Mode 2 with 2336 bytes sector size. */
+ TRACKDATAFORM_MODE2_2336,
+ /** Mode 2 with 2352 bytes sector size. */
+ TRACKDATAFORM_MODE2_2352,
+ /** Mode 2 with 0 bytes sector size (generated by the drive). */
+ TRACKDATAFORM_MODE2_0
+} TRACKDATAFORM;
+
+/**
+ * Subchannel data form.
+ */
+typedef enum SUBCHNDATAFORM
+{
+ /** Invalid subchannel data form. */
+ SUBCHNDATAFORM_INVALID = 0,
+ /** 0 bytes for the subchannel (generated by the drive). */
+ SUBCHNDATAFORM_0,
+ /** 96 bytes of data for the subchannel. */
+ SUBCHNDATAFORM_96
+} SUBCHNDATAFORM;
+
+/**
+ * Track entry.
+ */
+typedef struct TRACK
+{
+ /** Start LBA of the track. */
+ int64_t iLbaStart;
+ /** Number of sectors in the track. */
+ uint32_t cSectors;
+ /** Data form of main data. */
+ TRACKDATAFORM enmMainDataForm;
+ /** Data form of sub channel. */
+ SUBCHNDATAFORM enmSubChnDataForm;
+ /** Flags for the track. */
+ uint32_t fFlags;
+} TRACK, *PTRACK;
+
+/**
+ * Media track list.
+ */
+typedef struct TRACKLIST
+{
+ /** Number of detected tracks of the current medium. */
+ unsigned cTracksCurrent;
+ /** Maximum number of tracks the list can contain. */
+ unsigned cTracksMax;
+ /** Variable list of tracks. */
+ PTRACK paTracks;
+} TRACKLIST, *PTRACKLIST;
+
+
+/**
+ * Reallocate the given track list to be able to hold the given number of tracks.
+ *
+ * @returns VBox status code.
+ * @param pTrackList The track list to reallocate.
+ * @param cTracks Number of tracks the list must be able to hold.
+ * @param fFlags Flags for the reallocation.
+ */
+static int atapiTrackListReallocate(PTRACKLIST pTrackList, unsigned cTracks, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!(fFlags & ATAPI_TRACK_LIST_REALLOCATE_FLAGS_DONT_CLEAR))
+ ATAPIPassthroughTrackListClear(pTrackList);
+
+ if (pTrackList->cTracksMax < cTracks)
+ {
+ PTRACK paTracksNew = (PTRACK)RTMemRealloc(pTrackList->paTracks, cTracks * sizeof(TRACK));
+ if (paTracksNew)
+ {
+ pTrackList->paTracks = paTracksNew;
+
+ /* Mark new tracks as undetected. */
+ for (unsigned i = pTrackList->cTracksMax; i < cTracks; i++)
+ pTrackList->paTracks[i].fFlags |= TRACK_FLAGS_UNDETECTED;
+
+ pTrackList->cTracksMax = cTracks;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ pTrackList->cTracksCurrent = cTracks;
+
+ return rc;
+}
+
+/**
+ * Initilizes the given track from the given CUE sheet entry.
+ *
+ * @returns nothing.
+ * @param pTrack The track to initialize.
+ * @param pbCueSheetEntry CUE sheet entry to use.
+ */
+static void atapiTrackListEntryCreateFromCueSheetEntry(PTRACK pTrack, const uint8_t *pbCueSheetEntry)
+{
+ TRACKDATAFORM enmTrackDataForm = TRACKDATAFORM_INVALID;
+ SUBCHNDATAFORM enmSubChnDataForm = SUBCHNDATAFORM_INVALID;
+
+ /* Determine size of main data based on the data form field. */
+ switch (pbCueSheetEntry[3] & 0x3f)
+ {
+ case 0x00: /* CD-DA with data. */
+ enmTrackDataForm = TRACKDATAFORM_CDDA;
+ break;
+ case 0x01: /* CD-DA without data (used for pauses between tracks). */
+ enmTrackDataForm = TRACKDATAFORM_CDDA_PAUSE;
+ break;
+ case 0x10: /* CD-ROM mode 1 */
+ case 0x12:
+ enmTrackDataForm = TRACKDATAFORM_MODE1_2048;
+ break;
+ case 0x11:
+ case 0x13:
+ enmTrackDataForm = TRACKDATAFORM_MODE1_2352;
+ break;
+ case 0x14:
+ enmTrackDataForm = TRACKDATAFORM_MODE1_0;
+ break;
+ case 0x20: /* CD-ROM XA, CD-I */
+ case 0x22:
+ enmTrackDataForm = TRACKDATAFORM_XA_2336;
+ break;
+ case 0x21:
+ case 0x23:
+ enmTrackDataForm = TRACKDATAFORM_XA_2352;
+ break;
+ case 0x24:
+ enmTrackDataForm = TRACKDATAFORM_XA_0;
+ break;
+ case 0x31: /* CD-ROM Mode 2 */
+ case 0x33:
+ enmTrackDataForm = TRACKDATAFORM_MODE2_2352;
+ break;
+ case 0x30:
+ case 0x32:
+ enmTrackDataForm = TRACKDATAFORM_MODE2_2336;
+ break;
+ case 0x34:
+ enmTrackDataForm = TRACKDATAFORM_MODE2_0;
+ break;
+ default: /* Reserved, invalid mode. Log and leave default sector size. */
+ LogRel(("ATA: Invalid data form mode %d for current CUE sheet\n",
+ pbCueSheetEntry[3] & 0x3f));
+ }
+
+ /* Determine size of sub channel data based on data form field. */
+ switch ((pbCueSheetEntry[3] & 0xc0) >> 6)
+ {
+ case 0x00: /* Sub channel all zeroes, autogenerated by the drive. */
+ enmSubChnDataForm = SUBCHNDATAFORM_0;
+ break;
+ case 0x01:
+ case 0x03:
+ enmSubChnDataForm = SUBCHNDATAFORM_96;
+ break;
+ default:
+ LogRel(("ATA: Invalid sub-channel data form mode %u for current CUE sheet\n",
+ pbCueSheetEntry[3] & 0xc0));
+ }
+
+ pTrack->enmMainDataForm = enmTrackDataForm;
+ pTrack->enmSubChnDataForm = enmSubChnDataForm;
+ pTrack->iLbaStart = scsiMSF2LBA(&pbCueSheetEntry[5]);
+ if (pbCueSheetEntry[1] != 0xaa)
+ {
+ /* Calculate number of sectors from the next entry. */
+ int64_t iLbaNext = scsiMSF2LBA(&pbCueSheetEntry[5+8]);
+ pTrack->cSectors = iLbaNext - pTrack->iLbaStart;
+ }
+ else
+ {
+ pTrack->fFlags |= TRACK_FLAGS_LEAD_OUT;
+ pTrack->cSectors = 0;
+ }
+ pTrack->fFlags &= ~TRACK_FLAGS_UNDETECTED;
+}
+
+/**
+ * Update the track list from a SEND CUE SHEET request.
+ *
+ * @returns VBox status code.
+ * @param pTrackList Track list to update.
+ * @param pbCDB CDB of the SEND CUE SHEET request.
+ * @param pvBuf The CUE sheet.
+ */
+static int atapiTrackListUpdateFromSendCueSheet(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cbCueSheet = scsiBE2H_U24(pbCDB + 6);
+ unsigned cTracks = cbCueSheet / 8;
+
+ AssertReturn(cbCueSheet % 8 == 0 && cTracks, VERR_INVALID_PARAMETER);
+
+ rc = atapiTrackListReallocate(pTrackList, cTracks, 0);
+ if (RT_SUCCESS(rc))
+ {
+ const uint8_t *pbCueSheet = (uint8_t *)pvBuf;
+ PTRACK pTrack = pTrackList->paTracks;
+
+ for (unsigned i = 0; i < cTracks; i++)
+ {
+ atapiTrackListEntryCreateFromCueSheetEntry(pTrack, pbCueSheet);
+ if (i == 0)
+ pTrack->fFlags |= TRACK_FLAGS_LEAD_IN;
+ pTrack++;
+ pbCueSheet += 8;
+ }
+ }
+
+ return rc;
+}
+
+static int atapiTrackListUpdateFromSendDvdStructure(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Update track list from formatted TOC data.
+ *
+ * @returns VBox status code.
+ * @param pTrackList The track list to update.
+ * @param iTrack The first track the TOC has data for.
+ * @param fMSF Flag whether block addresses are in MSF or LBA format.
+ * @param pbBuf Buffer holding the formatted TOC.
+ * @param cbBuffer Size of the buffer.
+ */
+static int atapiTrackListUpdateFromFormattedToc(PTRACKLIST pTrackList, uint8_t iTrack,
+ bool fMSF, const uint8_t *pbBuf, uint32_t cbBuffer)
+{
+ RT_NOREF(iTrack, cbBuffer); /** @todo unused parameters */
+ int rc = VINF_SUCCESS;
+ unsigned cbToc = scsiBE2H_U16(pbBuf);
+ uint8_t iTrackFirst = pbBuf[2];
+ unsigned cTracks;
+
+ cbToc -= 2;
+ pbBuf += 4;
+ AssertReturn(cbToc % 8 == 0, VERR_INVALID_PARAMETER);
+
+ cTracks = cbToc / 8 + iTrackFirst;
+
+ rc = atapiTrackListReallocate(pTrackList, iTrackFirst + cTracks, ATAPI_TRACK_LIST_REALLOCATE_FLAGS_DONT_CLEAR);
+ if (RT_SUCCESS(rc))
+ {
+ PTRACK pTrack = &pTrackList->paTracks[iTrackFirst];
+
+ for (unsigned i = iTrackFirst; i < cTracks; i++)
+ {
+ if (pbBuf[1] & 0x4)
+ pTrack->enmMainDataForm = TRACKDATAFORM_MODE1_2048;
+ else
+ pTrack->enmMainDataForm = TRACKDATAFORM_CDDA;
+
+ pTrack->enmSubChnDataForm = SUBCHNDATAFORM_0;
+ if (fMSF)
+ pTrack->iLbaStart = scsiMSF2LBA(&pbBuf[4]);
+ else
+ pTrack->iLbaStart = scsiBE2H_U32(&pbBuf[4]);
+
+ if (pbBuf[2] != 0xaa)
+ {
+ /* Calculate number of sectors from the next entry. */
+ int64_t iLbaNext;
+
+ if (fMSF)
+ iLbaNext = scsiMSF2LBA(&pbBuf[4+8]);
+ else
+ iLbaNext = scsiBE2H_U32(&pbBuf[4+8]);
+
+ pTrack->cSectors = iLbaNext - pTrack->iLbaStart;
+ }
+ else
+ pTrack->cSectors = 0;
+
+ pTrack->fFlags &= ~TRACK_FLAGS_UNDETECTED;
+ pbBuf += 8;
+ pTrack++;
+ }
+ }
+
+ return rc;
+}
+
+static int atapiTrackListUpdateFromReadTocPmaAtip(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ int rc = VINF_SUCCESS;
+ uint16_t cbBuffer = scsiBE2H_U16(&pbCDB[7]);
+ bool fMSF = (pbCDB[1] & 0x2) != 0;
+ uint8_t uFmt = pbCDB[2] & 0xf;
+ uint8_t iTrack = pbCDB[6];
+
+ switch (uFmt)
+ {
+ case 0x00:
+ rc = atapiTrackListUpdateFromFormattedToc(pTrackList, iTrack, fMSF, (uint8_t *)pvBuf, cbBuffer);
+ break;
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ case 0x05:
+ rc = VINF_SUCCESS; /* Does not give information about the tracklist. */
+ break;
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+static int atapiTrackListUpdateFromReadTrackInformation(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static int atapiTrackListUpdateFromReadDvdStructure(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static int atapiTrackListUpdateFromReadDiscInformation(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+#ifdef LOG_ENABLED
+
+/**
+ * Converts the given track data form to a string.
+ *
+ * @returns Track data form as a string.
+ * @param enmTrackDataForm The track main data form.
+ */
+static const char *atapiTrackListMainDataFormToString(TRACKDATAFORM enmTrackDataForm)
+{
+ switch (enmTrackDataForm)
+ {
+ case TRACKDATAFORM_CDDA:
+ return "CD-DA";
+ case TRACKDATAFORM_CDDA_PAUSE:
+ return "CD-DA Pause";
+ case TRACKDATAFORM_MODE1_2048:
+ return "Mode 1 (2048 bytes)";
+ case TRACKDATAFORM_MODE1_2352:
+ return "Mode 1 (2352 bytes)";
+ case TRACKDATAFORM_MODE1_0:
+ return "Mode 1 (0 bytes)";
+ case TRACKDATAFORM_XA_2336:
+ return "XA (2336 bytes)";
+ case TRACKDATAFORM_XA_2352:
+ return "XA (2352 bytes)";
+ case TRACKDATAFORM_XA_0:
+ return "XA (0 bytes)";
+ case TRACKDATAFORM_MODE2_2336:
+ return "Mode 2 (2336 bytes)";
+ case TRACKDATAFORM_MODE2_2352:
+ return "Mode 2 (2352 bytes)";
+ case TRACKDATAFORM_MODE2_0:
+ return "Mode 2 (0 bytes)";
+ case TRACKDATAFORM_INVALID:
+ default:
+ return "Invalid";
+ }
+}
+
+/**
+ * Converts the given subchannel data form to a string.
+ *
+ * @returns Subchannel data form as a string.
+ * @param enmSubChnDataForm The subchannel main data form.
+ */
+static const char *atapiTrackListSubChnDataFormToString(SUBCHNDATAFORM enmSubChnDataForm)
+{
+ switch (enmSubChnDataForm)
+ {
+ case SUBCHNDATAFORM_0:
+ return "0";
+ case SUBCHNDATAFORM_96:
+ return "96";
+ case SUBCHNDATAFORM_INVALID:
+ default:
+ return "Invalid";
+ }
+}
+
+/**
+ * Dump the complete track list to the release log.
+ *
+ * @returns nothing.
+ * @param pTrackList The track list to dump.
+ */
+static void atapiTrackListDump(PTRACKLIST pTrackList)
+{
+ LogRel(("Track List: cTracks=%u\n", pTrackList->cTracksCurrent));
+ for (unsigned i = 0; i < pTrackList->cTracksCurrent; i++)
+ {
+ PTRACK pTrack = &pTrackList->paTracks[i];
+
+ LogRel((" Track %u: LBAStart=%lld cSectors=%u enmMainDataForm=%s enmSubChnDataForm=%s fFlags=[%s%s%s]\n",
+ i, pTrack->iLbaStart, pTrack->cSectors, atapiTrackListMainDataFormToString(pTrack->enmMainDataForm),
+ atapiTrackListSubChnDataFormToString(pTrack->enmSubChnDataForm),
+ pTrack->fFlags & TRACK_FLAGS_UNDETECTED ? "UNDETECTED " : "",
+ pTrack->fFlags & TRACK_FLAGS_LEAD_IN ? "Lead-In " : "",
+ pTrack->fFlags & TRACK_FLAGS_LEAD_OUT ? "Lead-Out" : ""));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+DECLHIDDEN(int) ATAPIPassthroughTrackListCreateEmpty(PTRACKLIST *ppTrackList)
+{
+ int rc = VERR_NO_MEMORY;
+ PTRACKLIST pTrackList = (PTRACKLIST)RTMemAllocZ(sizeof(TRACKLIST));
+
+ if (pTrackList)
+ {
+ rc = VINF_SUCCESS;
+ *ppTrackList = pTrackList;
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(void) ATAPIPassthroughTrackListDestroy(PTRACKLIST pTrackList)
+{
+ if (pTrackList->paTracks)
+ RTMemFree(pTrackList->paTracks);
+ RTMemFree(pTrackList);
+}
+
+DECLHIDDEN(void) ATAPIPassthroughTrackListClear(PTRACKLIST pTrackList)
+{
+ AssertPtrReturnVoid(pTrackList);
+
+ pTrackList->cTracksCurrent = 0;
+
+ /* Mark all tracks as undetected. */
+ for (unsigned i = 0; i < pTrackList->cTracksMax; i++)
+ pTrackList->paTracks[i].fFlags |= TRACK_FLAGS_UNDETECTED;
+}
+
+DECLHIDDEN(int) ATAPIPassthroughTrackListUpdate(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pbCDB[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ rc = atapiTrackListUpdateFromSendCueSheet(pTrackList, pbCDB, pvBuf);
+ break;
+ case SCSI_SEND_DVD_STRUCTURE:
+ rc = atapiTrackListUpdateFromSendDvdStructure(pTrackList, pbCDB, pvBuf);
+ break;
+ case SCSI_READ_TOC_PMA_ATIP:
+ rc = atapiTrackListUpdateFromReadTocPmaAtip(pTrackList, pbCDB, pvBuf);
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ rc = atapiTrackListUpdateFromReadTrackInformation(pTrackList, pbCDB, pvBuf);
+ break;
+ case SCSI_READ_DVD_STRUCTURE:
+ rc = atapiTrackListUpdateFromReadDvdStructure(pTrackList, pbCDB, pvBuf);
+ break;
+ case SCSI_READ_DISC_INFORMATION:
+ rc = atapiTrackListUpdateFromReadDiscInformation(pTrackList, pbCDB, pvBuf);
+ break;
+ default:
+ LogRel(("ATAPI: Invalid opcode %#x while determining media layout\n", pbCDB[0]));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+#ifdef LOG_ENABLED
+ atapiTrackListDump(pTrackList);
+#endif
+
+ return rc;
+}
+
+DECLHIDDEN(uint32_t) ATAPIPassthroughTrackListGetSectorSizeFromLba(PTRACKLIST pTrackList, uint32_t iAtapiLba)
+{
+ PTRACK pTrack = NULL;
+ uint32_t cbAtapiSector = 2048;
+
+ if (pTrackList->cTracksCurrent)
+ {
+ if ( iAtapiLba > UINT32_C(0xffff4fa1)
+ && (int32_t)iAtapiLba < -150)
+ {
+ /* Lead-In area, this is always the first entry in the cue sheet. */
+ pTrack = pTrackList->paTracks;
+ Assert(pTrack->fFlags & TRACK_FLAGS_LEAD_IN);
+ LogFlowFunc(("Selected Lead-In area\n"));
+ }
+ else
+ {
+ int64_t iAtapiLba64 = (int32_t)iAtapiLba;
+ pTrack = &pTrackList->paTracks[1];
+
+ /* Go through the track list and find the correct entry. */
+ for (unsigned i = 1; i < pTrackList->cTracksCurrent - 1; i++)
+ {
+ if (pTrack->fFlags & TRACK_FLAGS_UNDETECTED)
+ continue;
+
+ if ( pTrack->iLbaStart <= iAtapiLba64
+ && iAtapiLba64 < pTrack->iLbaStart + pTrack->cSectors)
+ break;
+
+ pTrack++;
+ }
+ }
+
+ if (pTrack)
+ {
+ switch (pTrack->enmMainDataForm)
+ {
+ case TRACKDATAFORM_CDDA:
+ case TRACKDATAFORM_MODE1_2352:
+ case TRACKDATAFORM_XA_2352:
+ case TRACKDATAFORM_MODE2_2352:
+ cbAtapiSector = 2352;
+ break;
+ case TRACKDATAFORM_MODE1_2048:
+ cbAtapiSector = 2048;
+ break;
+ case TRACKDATAFORM_CDDA_PAUSE:
+ case TRACKDATAFORM_MODE1_0:
+ case TRACKDATAFORM_XA_0:
+ case TRACKDATAFORM_MODE2_0:
+ cbAtapiSector = 0;
+ break;
+ case TRACKDATAFORM_XA_2336:
+ case TRACKDATAFORM_MODE2_2336:
+ cbAtapiSector = 2336;
+ break;
+ case TRACKDATAFORM_INVALID:
+ default:
+ AssertMsgFailed(("Invalid track data form %d\n", pTrack->enmMainDataForm));
+ }
+
+ switch (pTrack->enmSubChnDataForm)
+ {
+ case SUBCHNDATAFORM_0:
+ break;
+ case SUBCHNDATAFORM_96:
+ cbAtapiSector += 96;
+ break;
+ case SUBCHNDATAFORM_INVALID:
+ default:
+ AssertMsgFailed(("Invalid subchannel data form %d\n", pTrack->enmSubChnDataForm));
+ }
+ }
+ }
+
+ return cbAtapiSector;
+}
+
+
+static uint8_t atapiPassthroughCmdErrorSimple(uint8_t *pbSense, size_t cbSense, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+ memset(pbSense, '\0', cbSense);
+ if (RT_LIKELY(cbSense >= 13))
+ {
+ pbSense[0] = 0x70 | (1 << 7);
+ pbSense[2] = uATAPISenseKey & 0x0f;
+ pbSense[7] = 10;
+ pbSense[12] = uATAPIASC;
+ }
+ return SCSI_STATUS_CHECK_CONDITION;
+}
+
+
+DECLHIDDEN(bool) ATAPIPassthroughParseCdb(const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
+ PTRACKLIST pTrackList, uint8_t *pbSense, size_t cbSense,
+ PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
+ size_t *pcbSector, uint8_t *pu8ScsiSts)
+{
+ uint32_t uLba = 0;
+ uint32_t cSectors = 0;
+ size_t cbSector = 0;
+ size_t cbXfer = 0;
+ bool fPassthrough = false;
+ PDMMEDIATXDIR enmTxDir = PDMMEDIATXDIR_NONE;
+
+ RT_NOREF(cbCdb);
+
+ switch (pbCdb[0])
+ {
+ /* First the commands we can pass through without further processing. */
+ case SCSI_BLANK:
+ case SCSI_CLOSE_TRACK_SESSION:
+ case SCSI_LOAD_UNLOAD_MEDIUM:
+ case SCSI_PAUSE_RESUME:
+ case SCSI_PLAY_AUDIO_10:
+ case SCSI_PLAY_AUDIO_12:
+ case SCSI_PLAY_AUDIO_MSF:
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ case SCSI_REPAIR_TRACK:
+ case SCSI_RESERVE_TRACK:
+ case SCSI_SCAN:
+ case SCSI_SEEK_10:
+ case SCSI_SET_CD_SPEED:
+ case SCSI_SET_READ_AHEAD:
+ case SCSI_START_STOP_UNIT:
+ case SCSI_STOP_PLAY_SCAN:
+ case SCSI_SYNCHRONIZE_CACHE:
+ case SCSI_TEST_UNIT_READY:
+ case SCSI_VERIFY_10:
+ fPassthrough = true;
+ break;
+ case SCSI_ERASE_10:
+ uLba = scsiBE2H_U32(pbCdb + 2);
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_FORMAT_UNIT:
+ cbXfer = cbBuf;
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_GET_CONFIGURATION:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_GET_PERFORMANCE:
+ cbXfer = cbBuf;
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_INQUIRY:
+ cbXfer = scsiBE2H_U16(pbCdb + 3);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_MECHANISM_STATUS:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_MODE_SELECT_10:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_MODE_SENSE_10:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_10:
+ uLba = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U16(pbCdb + 7);
+ cbSector = 2048;
+ cbXfer = cSectors * cbSector;
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_12:
+ uLba = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U32(pbCdb + 6);
+ cbSector = 2048;
+ cbXfer = cSectors * cbSector;
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_BUFFER:
+ cbXfer = scsiBE2H_U24(pbCdb + 6);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_BUFFER_CAPACITY:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_CAPACITY:
+ cbXfer = 8;
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_CD:
+ case SCSI_READ_CD_MSF:
+ {
+ /* Get sector size based on the expected sector type field. */
+ switch ((pbCdb[1] >> 2) & 0x7)
+ {
+ case 0x0: /* All types. */
+ {
+ uint32_t iLbaStart;
+
+ if (pbCdb[0] == SCSI_READ_CD)
+ iLbaStart = scsiBE2H_U32(&pbCdb[2]);
+ else
+ iLbaStart = scsiMSF2LBA(&pbCdb[3]);
+
+ if (pTrackList)
+ cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, iLbaStart);
+ else
+ cbSector = 2048; /* Might be incorrect if we couldn't determine the type. */
+ break;
+ }
+ case 0x1: /* CD-DA */
+ cbSector = 2352;
+ break;
+ case 0x2: /* Mode 1 */
+ cbSector = 2048;
+ break;
+ case 0x3: /* Mode 2 formless */
+ cbSector = 2336;
+ break;
+ case 0x4: /* Mode 2 form 1 */
+ cbSector = 2048;
+ break;
+ case 0x5: /* Mode 2 form 2 */
+ cbSector = 2324;
+ break;
+ default: /* Reserved */
+ AssertMsgFailed(("Unknown sector type\n"));
+ cbSector = 0; /** @todo we should probably fail the command here already. */
+ }
+
+ if (pbCdb[0] == SCSI_READ_CD)
+ cbXfer = scsiBE2H_U24(pbCdb + 6) * cbSector;
+ else /* SCSI_READ_MSF */
+ {
+ cSectors = scsiMSF2LBA(pbCdb + 6) - scsiMSF2LBA(pbCdb + 3);
+ if (cSectors > 32)
+ cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
+ cbXfer = cSectors * cbSector;
+ }
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ }
+ case SCSI_READ_DISC_INFORMATION:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_DVD_STRUCTURE:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_FORMAT_CAPACITIES:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_SUBCHANNEL:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_TOC_PMA_ATIP:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_REPORT_KEY:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_REQUEST_SENSE:
+ cbXfer = pbCdb[4];
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SEND_CUE_SHEET:
+ cbXfer = scsiBE2H_U24(pbCdb + 6);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SEND_DVD_STRUCTURE:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SEND_EVENT:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SEND_KEY:
+ cbXfer = scsiBE2H_U16(pbCdb + 8);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SEND_OPC_INFORMATION:
+ cbXfer = scsiBE2H_U16(pbCdb + 7);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_SET_STREAMING:
+ cbXfer = scsiBE2H_U16(pbCdb + 9);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ uLba = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U16(pbCdb + 7);
+ if (pTrackList)
+ cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, uLba);
+ else
+ cbSector = 2048;
+ cbXfer = cSectors * cbSector;
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_WRITE_12:
+ uLba = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U32(pbCdb + 6);
+ if (pTrackList)
+ cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, uLba);
+ else
+ cbSector = 2048;
+ cbXfer = cSectors * cbSector;
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_WRITE_BUFFER:
+ switch (pbCdb[1] & 0x1f)
+ {
+ case 0x04: /* download microcode */
+ case 0x05: /* download microcode and save */
+ case 0x06: /* download microcode with offsets */
+ case 0x07: /* download microcode with offsets and save */
+ case 0x0e: /* download microcode with offsets and defer activation */
+ case 0x0f: /* activate deferred microcode */
+ LogRel(("ATAPI: CD-ROM passthrough command attempted to update firmware, blocked\n"));
+ *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ default:
+ cbXfer = scsiBE2H_U16(pbCdb + 6);
+ enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+ fPassthrough = true;
+ break;
+ }
+ break;
+ case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
+ cbXfer = scsiBE2H_U32(pbCdb + 6);
+ enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+ fPassthrough = true;
+ break;
+ case SCSI_REZERO_UNIT:
+ /* Obsolete command used by cdrecord. What else would one expect?
+ * This command is not sent to the drive, it is handled internally,
+ * as the Linux kernel doesn't like it (message "scsi: unknown
+ * opcode 0x01" in syslog) and replies with a sense code of 0,
+ * which sends cdrecord to an endless loop. */
+ *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ default:
+ LogRel(("ATAPI: Passthrough unimplemented for command %#x\n", pbCdb[0]));
+ *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ }
+
+ if (fPassthrough)
+ {
+ *penmTxDir = enmTxDir;
+ *pcbXfer = cbXfer;
+ *pcbSector = cbSector;
+ }
+
+ return fPassthrough;
+}
+
diff --git a/src/VBox/Devices/Storage/ATAPIPassthrough.h b/src/VBox/Devices/Storage/ATAPIPassthrough.h
new file mode 100644
index 00000000..ee4e86f7
--- /dev/null
+++ b/src/VBox/Devices/Storage/ATAPIPassthrough.h
@@ -0,0 +1,100 @@
+/* $Id: ATAPIPassthrough.h $ */
+/** @file
+ * VBox storage devices: ATAPI passthrough helpers (common code for DevATA and DevAHCI).
+ */
+
+/*
+ * Copyright (C) 2012-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_ATAPIPassthrough_h
+#define VBOX_INCLUDED_SRC_Storage_ATAPIPassthrough_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <VBox/vmm/pdmifs.h>
+#include <VBox/vmm/pdmstorageifs.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Opaque media track list.
+ */
+typedef struct TRACKLIST *PTRACKLIST;
+
+/**
+ * Creates an empty track list handle.
+ *
+ * @returns VBox status code.
+ * @param ppTrackList Where to store the track list handle on success.
+ */
+DECLHIDDEN(int) ATAPIPassthroughTrackListCreateEmpty(PTRACKLIST *ppTrackList);
+
+/**
+ * Destroys the allocated task list handle.
+ *
+ * @returns nothing.
+ * @param pTrackList The track list handle to destroy.
+ */
+DECLHIDDEN(void) ATAPIPassthroughTrackListDestroy(PTRACKLIST pTrackList);
+
+/**
+ * Clears all tracks from the given task list.
+ *
+ * @returns nothing.
+ * @param pTrackList The track list to clear.
+ */
+DECLHIDDEN(void) ATAPIPassthroughTrackListClear(PTRACKLIST pTrackList);
+
+/**
+ * Updates the track list from the given CDB and data buffer.
+ *
+ * @returns VBox status code.
+ * @param pTrackList The track list to update.
+ * @param pCDB The CDB buffer.
+ * @param pvBuf The data buffer.
+ */
+DECLHIDDEN(int) ATAPIPassthroughTrackListUpdate(PTRACKLIST pTrackList, const uint8_t *pCDB, const void *pvBuf);
+
+/**
+ * Return the sector size from the track matching the LBA in the given track list.
+ *
+ * @returns Sector size.
+ * @param pTrackList The track list to use.
+ * @param iAtapiLba The start LBA to get the sector size for.
+ */
+DECLHIDDEN(uint32_t) ATAPIPassthroughTrackListGetSectorSizeFromLba(PTRACKLIST pTrackList, uint32_t iAtapiLba);
+
+/**
+ * Parses the given CDB and returns whether it is safe to pass it through to the host drive.
+ *
+ * @returns Flag whether passing the CDB through to the host drive is safe.
+ * @param pbCdb The CDB to parse.
+ * @param cbCdb Size of the CDB in bytes.
+ * @param cbBuf Size of the guest buffer.
+ * @param pTrackList The track list for the current medium if available (optional).
+ * @param pbSense Pointer to the sense buffer.
+ * @param cbSense Size of the sense buffer.
+ * @param penmTxDir Where to store the transfer direction (guest to host or vice versa).
+ * @param pcbXfer Where to store the transfer size encoded in the CDB.
+ * @param pcbSector Where to store the sector size used for the transfer.
+ * @param pu8ScsiSts Where to store the SCSI status code.
+ */
+DECLHIDDEN(bool) ATAPIPassthroughParseCdb(const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
+ PTRACKLIST pTrackList, uint8_t *pbSense, size_t cbSense,
+ PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
+ size_t *pcbSector, uint8_t *pu8ScsiSts);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_ATAPIPassthrough_h */
diff --git a/src/VBox/Devices/Storage/Debug.cpp b/src/VBox/Devices/Storage/Debug.cpp
new file mode 100644
index 00000000..2aea4cf2
--- /dev/null
+++ b/src/VBox/Devices/Storage/Debug.cpp
@@ -0,0 +1,1152 @@
+/* $Id: Debug.cpp $ */
+/** @file
+ * VBox storage devices: debug helpers
+ */
+
+/*
+ * Copyright (C) 2008-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/types.h>
+#include <iprt/string.h>
+#include <VBox/scsi.h>
+#include <VBox/ata.h>
+
+#ifdef LOG_ENABLED
+
+/**
+ * ATA command codes
+ */
+static const char * const g_apszATACmdNames[256] =
+{
+ "NOP", /* 0x00 */
+ "", /* 0x01 */
+ "", /* 0x02 */
+ "CFA REQUEST EXTENDED ERROR CODE", /* 0x03 */
+ "", /* 0x04 */
+ "", /* 0x05 */
+ "DATA SET MANAGEMENT", /* 0x06 */
+ "", /* 0x07 */
+ "DEVICE RESET", /* 0x08 */
+ "", /* 0x09 */
+ "", /* 0x0a */
+ "", /* 0x0b */
+ "", /* 0x0c */
+ "", /* 0x0d */
+ "", /* 0x0e */
+ "", /* 0x0f */
+ "RECALIBRATE", /* 0x10 */
+ "", /* 0x11 */
+ "", /* 0x12 */
+ "", /* 0x13 */
+ "", /* 0x14 */
+ "", /* 0x15 */
+ "", /* 0x16 */
+ "", /* 0x17 */
+ "", /* 0x18 */
+ "", /* 0x19 */
+ "", /* 0x1a */
+ "", /* 0x1b */
+ "", /* 0x1c */
+ "", /* 0x1d */
+ "", /* 0x1e */
+ "", /* 0x1f */
+ "READ SECTORS", /* 0x20 */
+ "READ SECTORS WITHOUT RETRIES", /* 0x21 */
+ "READ LONG", /* 0x22 */
+ "READ LONG WITHOUT RETRIES", /* 0x23 */
+ "READ SECTORS EXT", /* 0x24 */
+ "READ DMA EXT", /* 0x25 */
+ "READ DMA QUEUED EXT", /* 0x26 */
+ "READ NATIVE MAX ADDRESS EXT", /* 0x27 */
+ "", /* 0x28 */
+ "READ MULTIPLE EXT", /* 0x29 */
+ "READ STREAM DMA EXT", /* 0x2a */
+ "READ STREAM EXT", /* 0x2b */
+ "", /* 0x2c */
+ "", /* 0x2d */
+ "", /* 0x2e */
+ "READ LOG EXT", /* 0x2f */
+ "WRITE SECTORS", /* 0x30 */
+ "WRITE SECTORS WITHOUT RETRIES", /* 0x31 */
+ "WRITE LONG", /* 0x32 */
+ "WRITE LONG WITHOUT RETRIES", /* 0x33 */
+ "WRITE SECTORS EXT", /* 0x34 */
+ "WRITE DMA EXT", /* 0x35 */
+ "WRITE DMA QUEUED EXT", /* 0x36 */
+ "SET MAX ADDRESS EXT", /* 0x37 */
+ "CFA WRITE SECTORS WITHOUT ERASE", /* 0x38 */
+ "WRITE MULTIPLE EXT", /* 0x39 */
+ "WRITE STREAM DMA EXT", /* 0x3a */
+ "WRITE STREAM EXT", /* 0x3b */
+ "WRITE VERIFY", /* 0x3c */
+ "WRITE DMA FUA EXT", /* 0x3d */
+ "WRITE DMA QUEUED FUA EXT", /* 0x3e */
+ "WRITE LOG EXT", /* 0x3f */
+ "READ VERIFY SECTORS", /* 0x40 */
+ "READ VERIFY SECTORS WITHOUT RETRIES", /* 0x41 */
+ "READ VERIFY SECTORS EXT", /* 0x42 */
+ "", /* 0x43 */
+ "", /* 0x44 */
+ "WRITE UNCORRECTABLE EXT", /* 0x45 */
+ "", /* 0x46 */
+ "READ LOG DMA EXT", /* 0x47 */
+ "", /* 0x48 */
+ "", /* 0x49 */
+ "", /* 0x4a */
+ "", /* 0x4b */
+ "", /* 0x4c */
+ "", /* 0x4d */
+ "", /* 0x4e */
+ "", /* 0x4f */
+ "FORMAT TRACK", /* 0x50 */
+ "CONFIGURE STREAM", /* 0x51 */
+ "", /* 0x52 */
+ "", /* 0x53 */
+ "", /* 0x54 */
+ "", /* 0x55 */
+ "", /* 0x56 */
+ "WRITE LOG DMA EXT", /* 0x57 */
+ "", /* 0x58 */
+ "", /* 0x59 */
+ "", /* 0x5a */
+ "", /* 0x5b */
+ "TRUSTED RECEIVE", /* 0x5c */
+ "TRUSTED RECEIVE DMA", /* 0x5d */
+ "TRUSTED SEND", /* 0x5e */
+ "TRUSTED SEND DMA", /* 0x5f */
+ "READ FPDMA QUEUED", /* 0x60 */
+ "WRITE FPDMA QUEUED", /* 0x61 */
+ "", /* 0x62 */
+ "", /* 0x63 */
+ "", /* 0x64 */
+ "", /* 0x65 */
+ "", /* 0x66 */
+ "", /* 0x67 */
+ "", /* 0x68 */
+ "", /* 0x69 */
+ "", /* 0x6a */
+ "", /* 0x6b */
+ "", /* 0x6c */
+ "", /* 0x6d */
+ "", /* 0x6e */
+ "", /* 0x6f */
+ "SEEK", /* 0x70 */
+ "", /* 0x71 */
+ "", /* 0x72 */
+ "", /* 0x73 */
+ "", /* 0x74 */
+ "", /* 0x75 */
+ "", /* 0x76 */
+ "", /* 0x77 */
+ "", /* 0x78 */
+ "", /* 0x79 */
+ "", /* 0x7a */
+ "", /* 0x7b */
+ "", /* 0x7c */
+ "", /* 0x7d */
+ "", /* 0x7e */
+ "", /* 0x7f */
+ "", /* 0x80 */
+ "", /* 0x81 */
+ "", /* 0x82 */
+ "", /* 0x83 */
+ "", /* 0x84 */
+ "", /* 0x85 */
+ "", /* 0x86 */
+ "CFA TRANSLATE SECTOR", /* 0x87 */
+ "", /* 0x88 */
+ "", /* 0x89 */
+ "", /* 0x8a */
+ "", /* 0x8b */
+ "", /* 0x8c */
+ "", /* 0x8d */
+ "", /* 0x8e */
+ "", /* 0x8f */
+ "EXECUTE DEVICE DIAGNOSTIC", /* 0x90 */
+ "INITIALIZE DEVICE PARAMETERS", /* 0x91 */
+ "DOWNLOAD MICROCODE", /* 0x92 */
+ "", /* 0x93 */
+ "STANDBY IMMEDIATE ALT", /* 0x94 */
+ "IDLE IMMEDIATE ALT", /* 0x95 */
+ "STANDBY ALT", /* 0x96 */
+ "IDLE ALT", /* 0x97 */
+ "CHECK POWER MODE ALT", /* 0x98 */
+ "SLEEP ALT", /* 0x99 */
+ "", /* 0x9a */
+ "", /* 0x9b */
+ "", /* 0x9c */
+ "", /* 0x9d */
+ "", /* 0x9e */
+ "", /* 0x9f */
+ "PACKET", /* 0xa0 */
+ "IDENTIFY PACKET DEVICE", /* 0xa1 */
+ "SERVICE", /* 0xa2 */
+ "", /* 0xa3 */
+ "", /* 0xa4 */
+ "", /* 0xa5 */
+ "", /* 0xa6 */
+ "", /* 0xa7 */
+ "", /* 0xa8 */
+ "", /* 0xa9 */
+ "", /* 0xaa */
+ "", /* 0xab */
+ "", /* 0xac */
+ "", /* 0xad */
+ "", /* 0xae */
+ "", /* 0xaf */
+ "SMART", /* 0xb0 */
+ "DEVICE CONFIGURATION OVERLAY", /* 0xb1 */
+ "", /* 0xb2 */
+ "", /* 0xb3 */
+ "", /* 0xb4 */
+ "", /* 0xb5 */
+ "NV CACHE", /* 0xb6 */
+ "", /* 0xb7 */
+ "", /* 0xb8 */
+ "", /* 0xb9 */
+ "", /* 0xba */
+ "", /* 0xbb */
+ "", /* 0xbc */
+ "", /* 0xbd */
+ "", /* 0xbe */
+ "", /* 0xbf */
+ "CFA ERASE SECTORS", /* 0xc0 */
+ "", /* 0xc1 */
+ "", /* 0xc2 */
+ "", /* 0xc3 */
+ "READ MULTIPLE", /* 0xc4 */
+ "WRITE MULTIPLE", /* 0xc5 */
+ "SET MULTIPLE MODE", /* 0xc6 */
+ "READ DMA QUEUED", /* 0xc7 */
+ "READ DMA", /* 0xc8 */
+ "READ DMA WITHOUT RETRIES", /* 0xc9 */
+ "WRITE DMA", /* 0xca */
+ "WRITE DMA WITHOUT RETRIES", /* 0xcb */
+ "WRITE DMA QUEUED", /* 0xcc */
+ "CFA WRITE MULTIPLE WITHOUT ERASE", /* 0xcd */
+ "WRITE MULTIPLE FUA EXT", /* 0xce */
+ "", /* 0xcf */
+ "", /* 0xd0 */
+ "CHECK MEDIA CARD TYPE", /* 0xd1 */
+ "", /* 0xd2 */
+ "", /* 0xd3 */
+ "", /* 0xd4 */
+ "", /* 0xd5 */
+ "", /* 0xd6 */
+ "", /* 0xd7 */
+ "", /* 0xd8 */
+ "", /* 0xd9 */
+ "GET MEDIA STATUS", /* 0xda */
+ "ACKNOWLEDGE MEDIA CHANGE", /* 0xdb */
+ "BOOT POST BOOT", /* 0xdc */
+ "BOOT PRE BOOT", /* 0xdd */
+ "MEDIA LOCK", /* 0xde */
+ "MEDIA UNLOCK", /* 0xdf */
+ "STANDBY IMMEDIATE", /* 0xe0 */
+ "IDLE IMMEDIATE", /* 0xe1 */
+ "STANDBY", /* 0xe2 */
+ "IDLE", /* 0xe3 */
+ "READ BUFFER", /* 0xe4 */
+ "CHECK POWER MODE", /* 0xe5 */
+ "SLEEP", /* 0xe6 */
+ "FLUSH CACHE", /* 0xe7 */
+ "WRITE BUFFER", /* 0xe8 */
+ "WRITE SAME", /* 0xe9 */
+ "FLUSH CACHE EXT", /* 0xea */
+ "", /* 0xeb */
+ "IDENTIFY DEVICE", /* 0xec */
+ "MEDIA EJECT", /* 0xed */
+ "IDENTIFY DMA", /* 0xee */
+ "SET FEATURES", /* 0xef */
+ "", /* 0xf0 */
+ "SECURITY SET PASSWORD", /* 0xf1 */
+ "SECURITY UNLOCK", /* 0xf2 */
+ "SECURITY ERASE PREPARE", /* 0xf3 */
+ "SECURITY ERASE UNIT", /* 0xf4 */
+ "SECURITY FREEZE LOCK", /* 0xf5 */
+ "SECURITY DISABLE PASSWORD", /* 0xf6 */
+ "", /* 0xf7 */
+ "READ NATIVE MAX ADDRESS", /* 0xf8 */
+ "SET MAX", /* 0xf9 */
+ "", /* 0xfa */
+ "", /* 0xfb */
+ "", /* 0xfc */
+ "", /* 0xfd */
+ "", /* 0xfe */
+ "" /* 0xff */
+};
+
+#endif /* LOG_ENABLED */
+
+#if defined(LOG_ENABLED) || defined(RT_STRICT)
+
+/**
+ * SCSI command codes.
+ */
+static const char * const g_apszSCSICmdNames[256] =
+{
+ "TEST UNIT READY", /* 0x00 */
+ "REZERO UNIT", /* 0x01 */
+ "", /* 0x02 */
+ "REQUEST SENSE", /* 0x03 */
+ "FORMAT UNIT", /* 0x04 */
+ "READ BLOCK LIMITS", /* 0x05 */
+ "", /* 0x06 */
+ "REASSIGN BLOCKS", /* 0x07 */
+ "READ (6)", /* 0x08 */
+ "", /* 0x09 */
+ "WRITE (6)", /* 0x0a */
+ "SEEK (6)", /* 0x0b */
+ "", /* 0x0c */
+ "", /* 0x0d */
+ "", /* 0x0e */
+ "READ REVERSE (6)", /* 0x0f */
+ "READ FILEMARKS (6)", /* 0x10 */
+ "SPACE (6)", /* 0x11 */
+ "INQUIRY", /* 0x12 */
+ "VERIFY (6)", /* 0x13 */
+ "RECOVER BUFFERED DATA", /* 0x14 */
+ "MODE SELECT (6)", /* 0x15 */
+ "RESERVE (6)", /* 0x16 */
+ "RELEASE (6)", /* 0x17 */
+ "COPY", /* 0x18 */
+ "ERASE (6)", /* 0x19 */
+ "MODE SENSE (6)", /* 0x1a */
+ "START STOP UNIT", /* 0x1b */
+ "RECEIVE DIAGNOSTIC RESULTS", /* 0x1c */
+ "SEND DIAGNOSTIC", /* 0x1d */
+ "PREVENT ALLOW MEDIUM REMOVAL", /* 0x1e */
+ "", /* 0x1f */
+ "", /* 0x20 */
+ "", /* 0x21 */
+ "", /* 0x22 */
+ "READ FORMAT CAPACITIES", /* 0x23 */
+ "SET WINDOW", /* 0x24 */
+ "READ CAPACITY", /* 0x25 */
+ "", /* 0x26 */
+ "", /* 0x27 */
+ "READ (10)", /* 0x28 */
+ "READ GENERATION", /* 0x29 */
+ "WRITE (10)", /* 0x2a */
+ "SEEK (10)", /* 0x2b */
+ "ERASE (10)", /* 0x2c */
+ "READ UPDATED BLOCK", /* 0x2d */
+ "WRITE AND VERIFY (10)", /* 0x2e */
+ "VERIFY (10)", /* 0x2f */
+ "SEARCH DATA HIGH (10)", /* 0x30 */
+ "SEARCH DATA EQUAL (10)", /* 0x31 */
+ "SEARCH DATA LOW (10)", /* 0x32 */
+ "SET LIMITS (10)", /* 0x33 */
+ "PRE-FETCH (10)", /* 0x34 */
+ "SYNCHRONIZE CACHE (10)", /* 0x35 */
+ "LOCK UNLOCK CACHE (10)", /* 0x36 */
+ "READ DEFECT DATA (10)", /* 0x37 */
+ "MEDIUM SCAN", /* 0x38 */
+ "COMPARE", /* 0x39 */
+ "COPY AND VERIFY", /* 0x3a */
+ "WRITE BUFFER", /* 0x3b */
+ "READ BUFFER", /* 0x3c */
+ "UPDATE BLOCK", /* 0x3d */
+ "READ LONG (10)", /* 0x3e */
+ "WRITE LONG (10)", /* 0x3f */
+ "CHANGE DEFINITION", /* 0x40 */
+ "WRITE SAME (10)", /* 0x41 */
+ "READ SUBCHANNEL", /* 0x42 */
+ "READ TOC/PMA/ATIP", /* 0x43 */
+ "REPORT DENSITY SUPPORT", /* 0x44 */
+ "PLAY AUDIO (10)", /* 0x45 */
+ "GET CONFIGURATION", /* 0x46 */
+ "PLAY AUDIO MSF", /* 0x47 */
+ "", /* 0x48 */
+ "", /* 0x49 */
+ "GET EVENT STATUS NOTIFICATION", /* 0x4a */
+ "PAUSE/RESUME", /* 0x4b */
+ "LOG SELECT", /* 0x4c */
+ "LOG SENSE", /* 0x4d */
+ "STOP PLAY/SCAN", /* 0x4e */
+ "", /* 0x4f */
+ "XDWRITE (10)", /* 0x50 */
+ "READ DISC INFORMATION", /* 0x51 */
+ "READ TRACK INFORMATION", /* 0x52 */
+ "RESERVE TRACK", /* 0x53 */
+ "SEND OPC INFORMATION", /* 0x54 */
+ "MODE SELECT (10)", /* 0x55 */
+ "RESERVE (10)", /* 0x56 */
+ "RELEASE (10)", /* 0x57 */
+ "REPAIR TRACK", /* 0x58 */
+ "", /* 0x59 */
+ "MODE SENSE (10)", /* 0x5a */
+ "CLOSE TRACK/SESSION", /* 0x5b */
+ "READ BUFFER CAPACITY", /* 0x5c */
+ "SEND CUE SHEET", /* 0x5d */
+ "PERSISTENT RESERVE IN", /* 0x5e */
+ "PERSISTENT RESERVE OUT", /* 0x5f */
+ "", /* 0x60 */
+ "", /* 0x61 */
+ "", /* 0x62 */
+ "", /* 0x63 */
+ "", /* 0x64 */
+ "", /* 0x65 */
+ "", /* 0x66 */
+ "", /* 0x67 */
+ "", /* 0x68 */
+ "", /* 0x69 */
+ "", /* 0x6a */
+ "", /* 0x6b */
+ "", /* 0x6c */
+ "", /* 0x6d */
+ "", /* 0x6e */
+ "", /* 0x6f */
+ "", /* 0x70 */
+ "", /* 0x71 */
+ "", /* 0x72 */
+ "", /* 0x73 */
+ "", /* 0x74 */
+ "", /* 0x75 */
+ "", /* 0x76 */
+ "", /* 0x77 */
+ "", /* 0x78 */
+ "", /* 0x79 */
+ "", /* 0x7a */
+ "", /* 0x7b */
+ "", /* 0x7c */
+ "", /* 0x7d */
+ "", /* 0x7e */
+ "", /* 0x7f */
+ "WRITE FILEMARKS (16)", /* 0x80 */
+ "READ REVERSE (16)", /* 0x81 */
+ "REGENERATE (16)", /* 0x82 */
+ "EXTENDED COPY", /* 0x83 */
+ "RECEIVE COPY RESULTS", /* 0x84 */
+ "ATA COMMAND PASS THROUGH (16)", /* 0x85 */
+ "ACCESS CONTROL IN", /* 0x86 */
+ "ACCESS CONTROL OUT", /* 0x87 */
+ "READ (16)", /* 0x88 */
+ "", /* 0x89 */
+ "WRITE(16)", /* 0x8a */
+ "", /* 0x8b */
+ "READ ATTRIBUTE", /* 0x8c */
+ "WRITE ATTRIBUTE", /* 0x8d */
+ "WRITE AND VERIFY (16)", /* 0x8e */
+ "VERIFY (16)", /* 0x8f */
+ "PRE-FETCH (16)", /* 0x90 */
+ "SYNCHRONIZE CACHE (16)", /* 0x91 */
+ "LOCK UNLOCK CACHE (16)", /* 0x92 */
+ "WRITE SAME (16)", /* 0x93 */
+ "", /* 0x94 */
+ "", /* 0x95 */
+ "", /* 0x96 */
+ "", /* 0x97 */
+ "", /* 0x98 */
+ "", /* 0x99 */
+ "", /* 0x9a */
+ "", /* 0x9b */
+ "", /* 0x9c */
+ "", /* 0x9d */
+ "SERVICE ACTION IN (16)", /* 0x9e */
+ "SERVICE ACTION OUT (16)", /* 0x9f */
+ "REPORT LUNS", /* 0xa0 */
+ "BLANK", /* 0xa1 */
+ "SEND EVENT", /* 0xa2 */
+ "SEND KEY", /* 0xa3 */
+ "REPORT KEY", /* 0xa4 */
+ "PLAY AUDIO (12)", /* 0xa5 */
+ "LOAD/UNLOAD MEDIUM", /* 0xa6 */
+ "SET READ AHEAD", /* 0xa7 */
+ "READ (12)", /* 0xa8 */
+ "SERVICE ACTION OUT (12)", /* 0xa9 */
+ "WRITE (12)", /* 0xaa */
+ "SERVICE ACTION IN (12)", /* 0xab */
+ "GET PERFORMANCE", /* 0xac */
+ "READ DVD STRUCTURE", /* 0xad */
+ "WRITE AND VERIFY (12)", /* 0xae */
+ "VERIFY (12)", /* 0xaf */
+ "SEARCH DATA HIGH (12)", /* 0xb0 */
+ "SEARCH DATA EQUAL (12)", /* 0xb1 */
+ "SEARCH DATA LOW (12)", /* 0xb2 */
+ "SET LIMITS (12)", /* 0xb3 */
+ "READ ELEMENT STATUS ATTACHED", /* 0xb4 */
+ "REQUEST VOLUME ELEMENT ADDRESS", /* 0xb5 */
+ "SET STREAMING", /* 0xb6 */
+ "READ DEFECT DATA (12)", /* 0xb7 */
+ "READ ELEMENT STATUS", /* 0xb8 */
+ "READ CD MSF", /* 0xb9 */
+ "SCAN", /* 0xba */
+ "SET CD SPEED", /* 0xbb */
+ "SPARE (IN)", /* 0xbc */
+ "MECHANISM STATUS", /* 0xbd */
+ "READ CD", /* 0xbe */
+ "SEND DVD STRUCTURE", /* 0xbf */
+ "", /* 0xc0 */
+ "", /* 0xc1 */
+ "", /* 0xc2 */
+ "", /* 0xc3 */
+ "", /* 0xc4 */
+ "", /* 0xc5 */
+ "", /* 0xc6 */
+ "", /* 0xc7 */
+ "", /* 0xc8 */
+ "", /* 0xc9 */
+ "", /* 0xca */
+ "", /* 0xcb */
+ "", /* 0xcc */
+ "", /* 0xcd */
+ "", /* 0xce */
+ "", /* 0xcf */
+ "", /* 0xd0 */
+ "", /* 0xd1 */
+ "", /* 0xd2 */
+ "", /* 0xd3 */
+ "", /* 0xd4 */
+ "", /* 0xd5 */
+ "", /* 0xd6 */
+ "", /* 0xd7 */
+ "", /* 0xd8 */
+ "", /* 0xd9 */
+ "", /* 0xda */
+ "", /* 0xdb */
+ "", /* 0xdc */
+ "", /* 0xdd */
+ "", /* 0xde */
+ "", /* 0xdf */
+ "", /* 0xe0 */
+ "", /* 0xe1 */
+ "", /* 0xe2 */
+ "", /* 0xe3 */
+ "", /* 0xe4 */
+ "", /* 0xe5 */
+ "", /* 0xe6 */
+ "", /* 0xe7 */
+ "", /* 0xe8 */
+ "", /* 0xe9 */
+ "", /* 0xea */
+ "", /* 0xeb */
+ "", /* 0xec */
+ "", /* 0xed */
+ "", /* 0xee */
+ "", /* 0xef */
+ "", /* 0xf0 */
+ "", /* 0xf1 */
+ "", /* 0xf2 */
+ "", /* 0xf3 */
+ "", /* 0xf4 */
+ "", /* 0xf5 */
+ "", /* 0xf6 */
+ "", /* 0xf7 */
+ "", /* 0xf8 */
+ "", /* 0xf9 */
+ "", /* 0xfa */
+ "", /* 0xfb */
+ "", /* 0xfc */
+ "", /* 0xfd */
+ "", /* 0xfe */
+ "" /* 0xff */
+};
+
+static const char * const g_apszSCSISenseNames[] =
+{
+ "NO SENSE",
+ "RECOVERED ERROR",
+ "NOT READY",
+ "MEDIUM ERROR",
+ "HARDWARE ERROR",
+ "ILLEGAL REQUEST",
+ "UNIT ATTENTION",
+ "DATA PROTECT",
+ "BLANK CHECK",
+ "VENDOR-SPECIFIC",
+ "COPY ABORTED",
+ "ABORTED COMMAND",
+ "(obsolete)",
+ "VOLUME OVERFLOW",
+ "MISCOMPARE",
+ "(reserved)"
+};
+
+/**
+ * SCSI Sense text
+ */
+static struct
+{
+ uint8_t uASC;
+ uint8_t uASCQ;
+ const char * const pszSenseText;
+} g_aSCSISenseText[]
+=
+{
+ { 0x67, 0x02, "A ADD LOGICAL UNIT FAILED" },
+ { 0x13, 0x00, "ADDRESS MARK NOT FOUND FOR DATA FIELD" },
+ { 0x12, 0x00, "ADDRESS MARK NOT FOUND FOR ID FIELD" },
+ { 0x27, 0x03, "ASSOCIATED WRITE PROTECT" },
+ { 0x67, 0x06, "ATTACHMENT OF LOGICAL UNIT FAILED" },
+ { 0x00, 0x11, "AUDIO PLAY OPERATION IN PROGRESS" },
+ { 0x00, 0x12, "AUDIO PLAY OPERATION PAUSED" },
+ { 0x00, 0x14, "AUDIO PLAY OPERATION STOPPED DUE TO ERROR" },
+ { 0x00, 0x13, "AUDIO PLAY OPERATION SUCCESSFULLY COMPLETED" },
+ { 0x66, 0x00, "AUTOMATIC DOCUMENT FEEDER COVER UP" },
+ { 0x66, 0x01, "AUTOMATIC DOCUMENT FEEDER LIFT UP" },
+ { 0x00, 0x04, "BEGINNING-OF-PARTITION/MEDIUM DETECTED" },
+ { 0x0C, 0x06, "BLOCK NOT COMPRESSIBLE" },
+ { 0x14, 0x04, "BLOCK SEQUENCE ERROR" },
+ { 0x29, 0x03, "BUS DEVICE RESET FUNCTION OCCURRED" },
+ { 0x11, 0x0E, "CANNOT DECOMPRESS USING DECLARED ALGORITHM" },
+ { 0x30, 0x06, "CANNOT FORMAT MEDIUM - INCOMPATIBLE MEDIUM" },
+ { 0x30, 0x02, "CANNOT READ MEDIUM - INCOMPATIBLE FORMAT" },
+ { 0x30, 0x01, "CANNOT READ MEDIUM - UNKNOWN FORMAT" },
+ { 0x30, 0x08, "CANNOT WRITE - APPLICATION CODE MISMATCH" },
+ { 0x30, 0x05, "CANNOT WRITE MEDIUM - INCOMPATIBLE FORMAT" },
+ { 0x30, 0x04, "CANNOT WRITE MEDIUM - UNKNOWN FORMAT" },
+ { 0x52, 0x00, "CARTRIDGE FAULT" },
+ { 0x73, 0x00, "CD CONTROL ERROR" },
+ { 0x3F, 0x02, "CHANGED OPERATING DEFINITION" },
+ { 0x11, 0x06, "CIRC UNRECOVERED ERROR" },
+ { 0x30, 0x03, "CLEANING CARTRIDGE INSTALLED" },
+ { 0x30, 0x07, "CLEANING FAILURE" },
+ { 0x00, 0x17, "CLEANING REQUESTED" },
+ { 0x4A, 0x00, "COMMAND PHASE ERROR" },
+ { 0x2C, 0x00, "COMMAND SEQUENCE ERROR" },
+ { 0x6E, 0x00, "COMMAND TO LOGICAL UNIT FAILED" },
+ { 0x2F, 0x00, "COMMANDS CLEARED BY ANOTHER INITIATOR" },
+ { 0x0C, 0x04, "COMPRESSION CHECK MISCOMPARE ERROR" },
+ { 0x67, 0x00, "CONFIGURATION FAILURE" },
+ { 0x67, 0x01, "CONFIGURATION OF INCAPABLE LOGICAL UNITS FAILED" },
+ { 0x2B, 0x00, "COPY CANNOT EXECUTE SINCE HOST CANNOT DISCONNECT" },
+ { 0x67, 0x07, "CREATION OF LOGICAL UNIT FAILED" },
+ { 0x2C, 0x04, "CURRENT PROGRAM AREA IS EMPTY" },
+ { 0x2C, 0x03, "CURRENT PROGRAM AREA IS NOT EMPTY" },
+ { 0x30, 0x09, "CURRENT SESSION NOT FIXATED FOR APPEND" },
+ { 0x0C, 0x05, "DATA EXPANSION OCCURRED DURING COMPRESSION" },
+ { 0x69, 0x00, "DATA LOSS ON LOGICAL UNIT" },
+ { 0x41, 0x00, "DATA PATH FAILURE (SHOULD USE 40 NN)" },
+ { 0x4B, 0x00, "DATA PHASE ERROR" },
+ { 0x11, 0x07, "DATA RE-SYNCHRONIZATION ERROR" },
+ { 0x16, 0x03, "DATA SYNC ERROR - DATA AUTO-REALLOCATED" },
+ { 0x16, 0x01, "DATA SYNC ERROR - DATA REWRITTEN" },
+ { 0x16, 0x04, "DATA SYNC ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x16, 0x02, "DATA SYNC ERROR - RECOMMEND REWRITE" },
+ { 0x16, 0x00, "DATA SYNCHRONIZATION MARK ERROR" },
+ { 0x11, 0x0D, "DE-COMPRESSION CRC ERROR" },
+ { 0x71, 0x00, "DECOMPRESSION EXCEPTION LONG ALGORITHM ID" },
+ { 0x70, 0xFF, "DECOMPRESSION EXCEPTION SHORT ALGORITHM ID OF NN" },
+ { 0x19, 0x00, "DEFECT LIST ERROR" },
+ { 0x19, 0x03, "DEFECT LIST ERROR IN GROWN LIST" },
+ { 0x19, 0x02, "DEFECT LIST ERROR IN PRIMARY LIST" },
+ { 0x19, 0x01, "DEFECT LIST NOT AVAILABLE" },
+ { 0x1C, 0x00, "DEFECT LIST NOT FOUND" },
+ { 0x32, 0x01, "DEFECT LIST UPDATE FAILURE" },
+ { 0x29, 0x04, "DEVICE INTERNAL RESET" },
+ { 0x40, 0xFF, "DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH)" },
+ { 0x66, 0x02, "DOCUMENT JAM IN AUTOMATIC DOCUMENT FEEDER" },
+ { 0x66, 0x03, "DOCUMENT MISS FEED AUTOMATIC IN DOCUMENT FEEDER" },
+ { 0x72, 0x04, "EMPTY OR PARTIALLY WRITTEN RESERVED TRACK" },
+ { 0x34, 0x00, "ENCLOSURE FAILURE" },
+ { 0x35, 0x00, "ENCLOSURE SERVICES FAILURE" },
+ { 0x35, 0x03, "ENCLOSURE SERVICES TRANSFER FAILURE" },
+ { 0x35, 0x04, "ENCLOSURE SERVICES TRANSFER REFUSED" },
+ { 0x35, 0x02, "ENCLOSURE SERVICES UNAVAILABLE" },
+ { 0x3B, 0x0F, "END OF MEDIUM REACHED" },
+ { 0x63, 0x00, "END OF USER AREA ENCOUNTERED ON THIS TRACK" },
+ { 0x00, 0x05, "END-OF-DATA DETECTED" },
+ { 0x14, 0x03, "END-OF-DATA NOT FOUND" },
+ { 0x00, 0x02, "END-OF-PARTITION/MEDIUM DETECTED" },
+ { 0x51, 0x00, "ERASE FAILURE" },
+ { 0x0A, 0x00, "ERROR LOG OVERFLOW" },
+ { 0x11, 0x10, "ERROR READING ISRC NUMBER" },
+ { 0x11, 0x0F, "ERROR READING UPC/EAN NUMBER" },
+ { 0x11, 0x02, "ERROR TOO LONG TO CORRECT" },
+ { 0x03, 0x02, "EXCESSIVE WRITE ERRORS" },
+ { 0x67, 0x04, "EXCHANGE OF LOGICAL UNIT FAILED" },
+ { 0x3B, 0x07, "FAILED TO SENSE BOTTOM-OF-FORM" },
+ { 0x3B, 0x06, "FAILED TO SENSE TOP-OF-FORM" },
+ { 0x5D, 0x00, "FAILURE PREDICTION THRESHOLD EXCEEDED" },
+ { 0x5D, 0xFF, "FAILURE PREDICTION THRESHOLD EXCEEDED (FALSE)" },
+ { 0x00, 0x01, "FILEMARK DETECTED" },
+ { 0x14, 0x02, "FILEMARK OR SETMARK NOT FOUND" },
+ { 0x09, 0x02, "FOCUS SERVO FAILURE" },
+ { 0x31, 0x01, "FORMAT COMMAND FAILED" },
+ { 0x58, 0x00, "GENERATION DOES NOT EXIST" },
+ { 0x1C, 0x02, "GROWN DEFECT LIST NOT FOUND" },
+ { 0x27, 0x01, "HARDWARE WRITE PROTECTED" },
+ { 0x09, 0x04, "HEAD SELECT FAULT" },
+ { 0x00, 0x06, "I/O PROCESS TERMINATED" },
+ { 0x10, 0x00, "ID CRC OR ECC ERROR" },
+ { 0x5E, 0x03, "IDLE CONDITION ACTIVATED BY COMMAND" },
+ { 0x5E, 0x01, "IDLE CONDITION ACTIVATED BY TIMER" },
+ { 0x22, 0x00, "ILLEGAL FUNCTION (USE 20 00, 24 00, OR 26 00)" },
+ { 0x64, 0x00, "ILLEGAL MODE FOR THIS TRACK" },
+ { 0x28, 0x01, "IMPORT OR EXPORT ELEMENT ACCESSED" },
+ { 0x30, 0x00, "INCOMPATIBLE MEDIUM INSTALLED" },
+ { 0x11, 0x08, "INCOMPLETE BLOCK READ" },
+ { 0x6A, 0x00, "INFORMATIONAL, REFER TO LOG" },
+ { 0x48, 0x00, "INITIATOR DETECTED ERROR MESSAGE RECEIVED" },
+ { 0x3F, 0x03, "INQUIRY DATA HAS CHANGED" },
+ { 0x44, 0x00, "INTERNAL TARGET FAILURE" },
+ { 0x3D, 0x00, "INVALID BITS IN IDENTIFY MESSAGE" },
+ { 0x2C, 0x02, "INVALID COMBINATION OF WINDOWS SPECIFIED" },
+ { 0x20, 0x00, "INVALID COMMAND OPERATION CODE" },
+ { 0x21, 0x01, "INVALID ELEMENT ADDRESS" },
+ { 0x24, 0x00, "INVALID FIELD IN CDB" },
+ { 0x26, 0x00, "INVALID FIELD IN PARAMETER LIST" },
+ { 0x49, 0x00, "INVALID MESSAGE ERROR" },
+ { 0x64, 0x01, "INVALID PACKET SIZE" },
+ { 0x26, 0x04, "INVALID RELEASE OF ACTIVE PERSISTENT RESERVATION" },
+ { 0x11, 0x05, "L-EC UNCORRECTABLE ERROR" },
+ { 0x60, 0x00, "LAMP FAILURE" },
+ { 0x5B, 0x02, "LOG COUNTER AT MAXIMUM" },
+ { 0x5B, 0x00, "LOG EXCEPTION" },
+ { 0x5B, 0x03, "LOG LIST CODES EXHAUSTED" },
+ { 0x2A, 0x02, "LOG PARAMETERS CHANGED" },
+ { 0x21, 0x00, "LOGICAL BLOCK ADDRESS OUT OF RANGE" },
+ { 0x08, 0x03, "LOGICAL UNIT COMMUNICATION CRC ERROR (ULTRA-DMA/32)" },
+ { 0x08, 0x00, "LOGICAL UNIT COMMUNICATION FAILURE" },
+ { 0x08, 0x02, "LOGICAL UNIT COMMUNICATION PARITY ERROR" },
+ { 0x08, 0x01, "LOGICAL UNIT COMMUNICATION TIME-OUT" },
+ { 0x05, 0x00, "LOGICAL UNIT DOES NOT RESPOND TO SELECTION" },
+ { 0x4C, 0x00, "LOGICAL UNIT FAILED SELF-CONFIGURATION" },
+ { 0x3E, 0x01, "LOGICAL UNIT FAILURE" },
+ { 0x3E, 0x00, "LOGICAL UNIT HAS NOT SELF-CONFIGURED YET" },
+ { 0x04, 0x01, "LOGICAL UNIT IS IN PROCESS OF BECOMING READY" },
+ { 0x68, 0x00, "LOGICAL UNIT NOT CONFIGURED" },
+ { 0x04, 0x00, "LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE" },
+ { 0x04, 0x04, "LOGICAL UNIT NOT READY, FORMAT IN PROGRESS" },
+ { 0x04, 0x02, "LOGICAL UNIT NOT READY, INITIALIZING CMD. REQUIRED" },
+ { 0x04, 0x08, "LOGICAL UNIT NOT READY, LONG WRITE IN PROGRESS" },
+ { 0x04, 0x03, "LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED" },
+ { 0x04, 0x07, "LOGICAL UNIT NOT READY, OPERATION IN PROGRESS" },
+ { 0x04, 0x05, "LOGICAL UNIT NOT READY, REBUILD IN PROGRESS" },
+ { 0x04, 0x06, "LOGICAL UNIT NOT READY, RECALCULATION IN PROGRESS" },
+ { 0x25, 0x00, "LOGICAL UNIT NOT SUPPORTED" },
+ { 0x27, 0x02, "LOGICAL UNIT SOFTWARE WRITE PROTECTED" },
+ { 0x5E, 0x00, "LOW POWER CONDITION ON" },
+ { 0x15, 0x01, "MECHANICAL POSITIONING ERROR" },
+ { 0x53, 0x00, "MEDIA LOAD OR EJECT FAILED" },
+ { 0x3B, 0x0D, "MEDIUM DESTINATION ELEMENT FULL" },
+ { 0x31, 0x00, "MEDIUM FORMAT CORRUPTED" },
+ { 0x3B, 0x13, "MEDIUM MAGAZINE INSERTED" },
+ { 0x3B, 0x14, "MEDIUM MAGAZINE LOCKED" },
+ { 0x3B, 0x11, "MEDIUM MAGAZINE NOT ACCESSIBLE" },
+ { 0x3B, 0x12, "MEDIUM MAGAZINE REMOVED" },
+ { 0x3B, 0x15, "MEDIUM MAGAZINE UNLOCKED" },
+ { 0x3A, 0x00, "MEDIUM NOT PRESENT" },
+ { 0x3A, 0x01, "MEDIUM NOT PRESENT - TRAY CLOSED" },
+ { 0x3A, 0x02, "MEDIUM NOT PRESENT - TRAY OPEN" },
+ { 0x53, 0x02, "MEDIUM REMOVAL PREVENTED" },
+ { 0x3B, 0x0E, "MEDIUM SOURCE ELEMENT EMPTY" },
+ { 0x43, 0x00, "MESSAGE ERROR" },
+ { 0x3F, 0x01, "MICROCODE HAS BEEN CHANGED" },
+ { 0x1D, 0x00, "MISCOMPARE DURING VERIFY OPERATION" },
+ { 0x11, 0x0A, "MISCORRECTED ERROR" },
+ { 0x2A, 0x01, "MODE PARAMETERS CHANGED" },
+ { 0x67, 0x03, "MODIFICATION OF LOGICAL UNIT FAILED" },
+ { 0x69, 0x01, "MULTIPLE LOGICAL UNIT FAILURES" },
+ { 0x07, 0x00, "MULTIPLE PERIPHERAL DEVICES SELECTED" },
+ { 0x11, 0x03, "MULTIPLE READ ERRORS" },
+ { 0x00, 0x00, "NO ADDITIONAL SENSE INFORMATION" },
+ { 0x00, 0x15, "NO CURRENT AUDIO STATUS TO RETURN" },
+ { 0x32, 0x00, "NO DEFECT SPARE LOCATION AVAILABLE" },
+ { 0x11, 0x09, "NO GAP FOUND" },
+ { 0x01, 0x00, "NO INDEX/SECTOR SIGNAL" },
+ { 0x06, 0x00, "NO REFERENCE POSITION FOUND" },
+ { 0x02, 0x00, "NO SEEK COMPLETE" },
+ { 0x03, 0x01, "NO WRITE CURRENT" },
+ { 0x28, 0x00, "NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED" },
+ { 0x00, 0x16, "OPERATION IN PROGRESS" },
+ { 0x5A, 0x01, "OPERATOR MEDIUM REMOVAL REQUEST" },
+ { 0x5A, 0x00, "OPERATOR REQUEST OR STATE CHANGE INPUT" },
+ { 0x5A, 0x03, "OPERATOR SELECTED WRITE PERMIT" },
+ { 0x5A, 0x02, "OPERATOR SELECTED WRITE PROTECT" },
+ { 0x61, 0x02, "OUT OF FOCUS" },
+ { 0x4E, 0x00, "OVERLAPPED COMMANDS ATTEMPTED" },
+ { 0x2D, 0x00, "OVERWRITE ERROR ON UPDATE IN PLACE" },
+ { 0x63, 0x01, "PACKET DOES NOT FIT IN AVAILABLE SPACE" },
+ { 0x3B, 0x05, "PAPER JAM" },
+ { 0x1A, 0x00, "PARAMETER LIST LENGTH ERROR" },
+ { 0x26, 0x01, "PARAMETER NOT SUPPORTED" },
+ { 0x26, 0x02, "PARAMETER VALUE INVALID" },
+ { 0x2A, 0x00, "PARAMETERS CHANGED" },
+ { 0x69, 0x02, "PARITY/DATA MISMATCH" },
+ { 0x1F, 0x00, "PARTIAL DEFECT LIST TRANSFER" },
+ { 0x03, 0x00, "PERIPHERAL DEVICE WRITE FAULT" },
+ { 0x27, 0x05, "PERMANENT WRITE PROTECT" },
+ { 0x27, 0x04, "PERSISTENT WRITE PROTECT" },
+ { 0x50, 0x02, "POSITION ERROR RELATED TO TIMING" },
+ { 0x3B, 0x0C, "POSITION PAST BEGINNING OF MEDIUM" },
+ { 0x3B, 0x0B, "POSITION PAST END OF MEDIUM" },
+ { 0x15, 0x02, "POSITIONING ERROR DETECTED BY READ OF MEDIUM" },
+ { 0x73, 0x01, "POWER CALIBRATION AREA ALMOST FULL" },
+ { 0x73, 0x03, "POWER CALIBRATION AREA ERROR" },
+ { 0x73, 0x02, "POWER CALIBRATION AREA IS FULL" },
+ { 0x29, 0x01, "POWER ON OCCURRED" },
+ { 0x29, 0x00, "POWER ON, RESET, OR BUS DEVICE RESET OCCURRED" },
+ { 0x42, 0x00, "POWER-ON OR SELF-TEST FAILURE (SHOULD USE 40 NN)" },
+ { 0x1C, 0x01, "PRIMARY DEFECT LIST NOT FOUND" },
+ { 0x73, 0x05, "PROGRAM MEMORY AREA IS FULL" },
+ { 0x73, 0x04, "PROGRAM MEMORY AREA UPDATE FAILURE" },
+ { 0x40, 0x00, "RAM FAILURE (SHOULD USE 40 NN)" },
+ { 0x15, 0x00, "RANDOM POSITIONING ERROR" },
+ { 0x11, 0x11, "READ ERROR - LOSS OF STREAMING" },
+ { 0x3B, 0x0A, "READ PAST BEGINNING OF MEDIUM" },
+ { 0x3B, 0x09, "READ PAST END OF MEDIUM" },
+ { 0x11, 0x01, "READ RETRIES EXHAUSTED" },
+ { 0x6C, 0x00, "REBUILD FAILURE OCCURRED" },
+ { 0x6D, 0x00, "RECALCULATE FAILURE OCCURRED" },
+ { 0x14, 0x01, "RECORD NOT FOUND" },
+ { 0x14, 0x06, "RECORD NOT FOUND - DATA AUTO-REALLOCATED" },
+ { 0x14, 0x05, "RECORD NOT FOUND - RECOMMEND REASSIGNMENT" },
+ { 0x14, 0x00, "RECORDED ENTITY NOT FOUND" },
+ { 0x18, 0x02, "RECOVERED DATA - DATA AUTO-REALLOCATED" },
+ { 0x18, 0x05, "RECOVERED DATA - RECOMMEND REASSIGNMENT" },
+ { 0x18, 0x06, "RECOVERED DATA - RECOMMEND REWRITE" },
+ { 0x17, 0x05, "RECOVERED DATA USING PREVIOUS SECTOR ID" },
+ { 0x18, 0x03, "RECOVERED DATA WITH CIRC" },
+ { 0x18, 0x07, "RECOVERED DATA WITH ECC - DATA REWRITTEN" },
+ { 0x18, 0x01, "RECOVERED DATA WITH ERROR CORR. & RETRIES APPLIED" },
+ { 0x18, 0x00, "RECOVERED DATA WITH ERROR CORRECTION APPLIED" },
+ { 0x18, 0x04, "RECOVERED DATA WITH L-EC" },
+ { 0x17, 0x03, "RECOVERED DATA WITH NEGATIVE HEAD OFFSET" },
+ { 0x17, 0x00, "RECOVERED DATA WITH NO ERROR CORRECTION APPLIED" },
+ { 0x17, 0x02, "RECOVERED DATA WITH POSITIVE HEAD OFFSET" },
+ { 0x17, 0x01, "RECOVERED DATA WITH RETRIES" },
+ { 0x17, 0x04, "RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED" },
+ { 0x17, 0x06, "RECOVERED DATA WITHOUT ECC - DATA AUTO-REALLOCATED" },
+ { 0x17, 0x09, "RECOVERED DATA WITHOUT ECC - DATA REWRITTEN" },
+ { 0x17, 0x07, "RECOVERED DATA WITHOUT ECC - RECOMMEND REASSIGNMENT" },
+ { 0x17, 0x08, "RECOVERED DATA WITHOUT ECC - RECOMMEND REWRITE" },
+ { 0x1E, 0x00, "RECOVERED ID WITH ECC CORRECTION" },
+ { 0x6B, 0x01, "REDUNDANCY LEVEL GOT BETTER" },
+ { 0x6B, 0x02, "REDUNDANCY LEVEL GOT WORSE" },
+ { 0x67, 0x05, "REMOVE OF LOGICAL UNIT FAILED" },
+ { 0x3B, 0x08, "REPOSITION ERROR" },
+ { 0x2A, 0x03, "RESERVATIONS PREEMPTED" },
+ { 0x36, 0x00, "RIBBON, INK, OR TONER FAILURE" },
+ { 0x37, 0x00, "ROUNDED PARAMETER" },
+ { 0x5C, 0x00, "RPL STATUS CHANGE" },
+ { 0x39, 0x00, "SAVING PARAMETERS NOT SUPPORTED" },
+ { 0x62, 0x00, "SCAN HEAD POSITIONING ERROR" },
+ { 0x29, 0x02, "SCSI BUS RESET OCCURRED" },
+ { 0x47, 0x00, "SCSI PARITY ERROR" },
+ { 0x54, 0x00, "SCSI TO HOST SYSTEM INTERFACE FAILURE" },
+ { 0x45, 0x00, "SELECT OR RESELECT FAILURE" },
+ { 0x3B, 0x00, "SEQUENTIAL POSITIONING ERROR" },
+ { 0x72, 0x00, "SESSION FIXATION ERROR" },
+ { 0x72, 0x03, "SESSION FIXATION ERROR - INCOMPLETE TRACK IN SESSION" },
+ { 0x72, 0x01, "SESSION FIXATION ERROR WRITING LEAD-IN" },
+ { 0x72, 0x02, "SESSION FIXATION ERROR WRITING LEAD-OUT" },
+ { 0x00, 0x03, "SETMARK DETECTED" },
+ { 0x3B, 0x04, "SLEW FAILURE" },
+ { 0x09, 0x03, "SPINDLE SERVO FAILURE" },
+ { 0x5C, 0x02, "SPINDLES NOT SYNCHRONIZED" },
+ { 0x5C, 0x01, "SPINDLES SYNCHRONIZED" },
+ { 0x5E, 0x04, "STANDBY CONDITION ACTIVATED BY COMMAND" },
+ { 0x5E, 0x02, "STANDBY CONDITION ACTIVATED BY TIMER" },
+ { 0x6B, 0x00, "STATE CHANGE HAS OCCURRED" },
+ { 0x1B, 0x00, "SYNCHRONOUS DATA TRANSFER ERROR" },
+ { 0x55, 0x01, "SYSTEM BUFFER FULL" },
+ { 0x55, 0x00, "SYSTEM RESOURCE FAILURE" },
+ { 0x4D, 0xFF, "TAGGED OVERLAPPED COMMANDS (NN = QUEUE TAG)" },
+ { 0x33, 0x00, "TAPE LENGTH ERROR" },
+ { 0x3B, 0x03, "TAPE OR ELECTRONIC VERTICAL FORMS UNIT NOT READY" },
+ { 0x3B, 0x01, "TAPE POSITION ERROR AT BEGINNING-OF-MEDIUM" },
+ { 0x3B, 0x02, "TAPE POSITION ERROR AT END-OF-MEDIUM" },
+ { 0x3F, 0x00, "TARGET OPERATING CONDITIONS HAVE CHANGED" },
+ { 0x5B, 0x01, "THRESHOLD CONDITION MET" },
+ { 0x26, 0x03, "THRESHOLD PARAMETERS NOT SUPPORTED" },
+ { 0x3E, 0x02, "TIMEOUT ON LOGICAL UNIT" },
+ { 0x2C, 0x01, "TOO MANY WINDOWS SPECIFIED" },
+ { 0x09, 0x00, "TRACK FOLLOWING ERROR" },
+ { 0x09, 0x01, "TRACKING SERVO FAILURE" },
+ { 0x61, 0x01, "UNABLE TO ACQUIRE VIDEO" },
+ { 0x57, 0x00, "UNABLE TO RECOVER TABLE-OF-CONTENTS" },
+ { 0x53, 0x01, "UNLOAD TAPE FAILURE" },
+ { 0x11, 0x00, "UNRECOVERED READ ERROR" },
+ { 0x11, 0x04, "UNRECOVERED READ ERROR - AUTO REALLOCATE FAILED" },
+ { 0x11, 0x0B, "UNRECOVERED READ ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x11, 0x0C, "UNRECOVERED READ ERROR - RECOMMEND REWRITE THE DATA" },
+ { 0x46, 0x00, "UNSUCCESSFUL SOFT RESET" },
+ { 0x35, 0x01, "UNSUPPORTED ENCLOSURE FUNCTION" },
+ { 0x59, 0x00, "UPDATED BLOCK READ" },
+ { 0x61, 0x00, "VIDEO ACQUISITION ERROR" },
+ { 0x65, 0x00, "VOLTAGE FAULT" },
+ { 0x0B, 0x00, "WARNING" },
+ { 0x0B, 0x02, "WARNING - ENCLOSURE DEGRADED" },
+ { 0x0B, 0x01, "WARNING - SPECIFIED TEMPERATURE EXCEEDED" },
+ { 0x50, 0x00, "WRITE APPEND ERROR" },
+ { 0x50, 0x01, "WRITE APPEND POSITION ERROR" },
+ { 0x0C, 0x00, "WRITE ERROR" },
+ { 0x0C, 0x02, "WRITE ERROR - AUTO REALLOCATION FAILED" },
+ { 0x0C, 0x09, "WRITE ERROR - LOSS OF STREAMING" },
+ { 0x0C, 0x0A, "WRITE ERROR - PADDING BLOCKS ADDED" },
+ { 0x0C, 0x03, "WRITE ERROR - RECOMMEND REASSIGNMENT" },
+ { 0x0C, 0x01, "WRITE ERROR - RECOVERED WITH AUTO REALLOCATION" },
+ { 0x0C, 0x08, "WRITE ERROR - RECOVERY FAILED" },
+ { 0x0C, 0x07, "WRITE ERROR - RECOVERY NEEDED" },
+ { 0x27, 0x00, "WRITE PROTECTED" },
+};
+
+#endif /* LOG_ENABLED || RT_STRICT */
+
+#ifdef LOG_ENABLED
+
+/**
+ * Return the plain text of an ATA command for debugging purposes.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * ATACmdText(uint8_t uCmd)
+{
+ AssertCompile(RT_ELEMENTS(g_apszATACmdNames) == (1 << (8*sizeof(uCmd))));
+ return g_apszATACmdNames[uCmd];
+}
+
+#endif
+
+#if defined(LOG_ENABLED) || defined(RT_STRICT)
+
+/**
+ * Return the plain text of a SCSI command for debugging purposes.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSICmdText(uint8_t uCmd)
+{
+ AssertCompile(RT_ELEMENTS(g_apszSCSICmdNames) == (1 << (8*sizeof(uCmd))));
+ return g_apszSCSICmdNames[uCmd];
+}
+
+/**
+ * Return the plain text of a SCSI sense code.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSISenseText(uint8_t uSense)
+{
+ if (uSense < RT_ELEMENTS(g_apszSCSISenseNames))
+ return g_apszSCSISenseNames[uSense];
+
+ return "(SCSI sense out of range)";
+}
+
+/**
+ * Return the plain text of an extended SCSI sense key.
+ * Don't allocate the string as we use this function in Log() statements.
+ */
+const char * SCSISenseExtText(uint8_t uASC, uint8_t uASCQ)
+{
+ unsigned iIdx;
+
+ /* Linear search. Doesn't hurt as we don't call this function very frequently */
+ for (iIdx = 0; iIdx < RT_ELEMENTS(g_aSCSISenseText); iIdx++)
+ {
+ if ( g_aSCSISenseText[iIdx].uASC == uASC
+ && ( g_aSCSISenseText[iIdx].uASCQ == uASCQ
+ || g_aSCSISenseText[iIdx].uASCQ == 0xff))
+ return g_aSCSISenseText[iIdx].pszSenseText;
+ }
+ return "(Unknown extended sense code)";
+}
+
+/**
+ * Log the write parameters mode page into a given buffer.
+ */
+static int scsiLogWriteParamsModePage(char *pszBuffer, size_t cchBuffer, uint8_t *pbModePage, size_t cbModePage)
+{
+ RT_NOREF(cbModePage);
+ size_t cch = 0;
+ const char *pcsz = NULL;
+
+ switch (pbModePage[2] & 0x0f)
+ {
+ case 0x00: pcsz = "Packet/Incremental"; break;
+ case 0x01: pcsz = "Track At Once"; break;
+ case 0x02: pcsz = "Session At Once"; break;
+ case 0x03: pcsz = "RAW"; break;
+ case 0x04: pcsz = "Layer Jump Recording"; break;
+ default : pcsz = "Unknown/Reserved Write Type"; break;
+ }
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "BUFE=%d LS_V=%d TestWrite=%d WriteType=%s\n",
+ pbModePage[2] & RT_BIT(6) ? 1 : 0,
+ pbModePage[2] & RT_BIT(5) ? 1 : 0,
+ pbModePage[2] & RT_BIT(4) ? 1 : 0,
+ pcsz);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ switch ((pbModePage[3] & 0xc0) >> 6)
+ {
+ case 0x00: pcsz = "No B0 pointer, no next session"; break;
+ case 0x01: pcsz = "B0 pointer=FF:FF:FF, no next session"; break;
+ case 0x02: pcsz = "Reserved"; break;
+ case 0x03: pcsz = "Next session allowed"; break;
+ default: pcsz = "Impossible multi session field value"; break;
+ }
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "MultiSession=%s FP=%d Copy=%d TrackMode=%d\n",
+ pcsz,
+ pbModePage[3] & RT_BIT(5) ? 1 : 0,
+ pbModePage[3] & RT_BIT(4) ? 1 : 0,
+ pbModePage[3] & 0x0f);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ switch (pbModePage[4] & 0x0f)
+ {
+ case 0: pcsz = "Raw data (2352)"; break;
+ case 1: pcsz = "Raw data with P and Q Sub-channel (2368)"; break;
+ case 2: pcsz = "Raw data with P-W Sub-channel (2448)"; break;
+ case 3: pcsz = "Raw data with raw P-W Sub-channel (2448)"; break;
+ case 8: pcsz = "Mode 1 (ISO/IEC 10149) (2048)"; break;
+ case 9: pcsz = "Mode 2 (ISO/IEC 10149) (2336)"; break;
+ case 10: pcsz = "Mode 2 (CD-ROM XA, form 1) (2048)"; break;
+ case 11: pcsz = "Mode 2 (CD-ROM XA, form 1) (2056)"; break;
+ case 12: pcsz = "Mode 2 (CD-ROM XA, form 2) (2324)"; break;
+ case 13: pcsz = "Mode 2 (CD-ROM XA, form 1, form 2 or mixed form) (2332)"; break;
+ default: pcsz = "Reserved or vendor specific Data Block Type Code"; break;
+ }
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "DataBlockType=%d (%s)\n",
+ pbModePage[4] & 0x0f,
+ pcsz);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "LinkSize=%d\n", pbModePage[5]);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "HostApplicationCode=%d\n",
+ pbModePage[7] & 0x3f);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ switch (pbModePage[8])
+ {
+ case 0x00: pcsz = "CD-DA or CD-ROM or other data discs"; break;
+ case 0x10: pcsz = "CD-I Disc"; break;
+ case 0x20: pcsz = "CD-ROM XA Disc"; break;
+ default: pcsz = "Reserved"; break;
+ }
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "SessionFormat=%d (%s)\n",
+ pbModePage[8], pcsz);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Log a mode page in a human readable form.
+ *
+ * @returns VBox status code.
+ * @retval VERR_BUFFER_OVERFLOW if the given buffer is not large enough.
+ * The buffer might contain valid data though.
+ * @param pszBuffer The buffer to log into.
+ * @param cchBuffer Size of the buffer in characters.
+ * @param pbModePage The mode page buffer.
+ * @param cbModePage Size of the mode page buffer in bytes.
+ */
+int SCSILogModePage(char *pszBuffer, size_t cchBuffer, uint8_t *pbModePage,
+ size_t cbModePage)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t uModePage;
+ const char *pcszModePage = NULL;
+ size_t cch = 0;
+
+ uModePage = pbModePage[0] & 0x3f;
+ switch (uModePage)
+ {
+ case 0x05: pcszModePage = "Write Parameters"; break;
+ default:
+ pcszModePage = "Unknown mode page";
+ }
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "Byte 0: PS=%d, Page code=%d (%s)\n",
+ pbModePage[0] & 0x80 ? 1 : 0, uModePage, pcszModePage);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ cch = RTStrPrintf(pszBuffer, cchBuffer, "Byte 1: Page length=%u\n", pbModePage[1]);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ return VERR_BUFFER_OVERFLOW;
+
+ switch (uModePage)
+ {
+ case 0x05:
+ rc = scsiLogWriteParamsModePage(pszBuffer, cchBuffer, pbModePage, cbModePage);
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Log a cue sheet in a human readable form.
+ *
+ * @returns VBox status code.
+ * @retval VERR_BUFFER_OVERFLOW if the given buffer is not large enough.
+ * The buffer might contain valid data though.
+ * @param pszBuffer The buffer to log into.
+ * @param cchBuffer Size of the buffer in characters.
+ * @param pbCueSheet The cue sheet buffer.
+ * @param cbCueSheet Size of the cue sheet buffer in bytes.
+ */
+int SCSILogCueSheet(char *pszBuffer, size_t cchBuffer, uint8_t *pbCueSheet, size_t cbCueSheet)
+{
+ int rc = VINF_SUCCESS;
+ size_t cch = 0;
+ size_t cCueSheetEntries = cbCueSheet / 8;
+
+ AssertReturn(cbCueSheet % 8 == 0, VERR_INVALID_PARAMETER);
+
+ for (size_t i = 0; i < cCueSheetEntries; i++)
+ {
+ cch = RTStrPrintf(pszBuffer, cchBuffer,
+ "CTL/ADR=%#x TNO=%#x INDEX=%#x DATA=%#x SCMS=%#x TIME=%u:%u:%u\n",
+ pbCueSheet[0], pbCueSheet[1], pbCueSheet[2], pbCueSheet[3],
+ pbCueSheet[4], pbCueSheet[5], pbCueSheet[6], pbCueSheet[7]);
+ pszBuffer += cch;
+ cchBuffer -= cch;
+ if (!cchBuffer)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ pbCueSheet += 8;
+ cbCueSheet -= 8;
+ }
+
+ return rc;
+}
+
+#endif /* LOG_ENABLED || RT_STRICT */
diff --git a/src/VBox/Devices/Storage/DevAHCI.cpp b/src/VBox/Devices/Storage/DevAHCI.cpp
new file mode 100644
index 00000000..38930838
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevAHCI.cpp
@@ -0,0 +1,6280 @@
+/* $Id: DevAHCI.cpp $ */
+/** @file
+ * DevAHCI - AHCI controller device (disk and cdrom).
+ *
+ * Implements the AHCI standard 1.1
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/** @page pg_dev_ahci AHCI - Advanced Host Controller Interface Emulation.
+ *
+ * This component implements an AHCI serial ATA controller. The device is split
+ * into two parts. The first part implements the register interface for the
+ * guest and the second one does the data transfer.
+ *
+ * The guest can access the controller in two ways. The first one is the native
+ * way implementing the registers described in the AHCI specification and is
+ * the preferred one. The second implements the I/O ports used for booting from
+ * the hard disk and for guests which don't have an AHCI SATA driver.
+ *
+ * The data is transfered using the extended media interface, asynchronously if
+ * it is supported by the driver below otherwise it weill be done synchronous.
+ * Either way a thread is used to process new requests from the guest.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_AHCI
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmqueue.h>
+#include <VBox/vmm/pdmthread.h>
+#include <VBox/vmm/pdmcritsect.h>
+#include <VBox/sup.h>
+#include <VBox/scsi.h>
+#include <VBox/ata.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#ifdef IN_RING3
+# include <iprt/param.h>
+# include <iprt/thread.h>
+# include <iprt/semaphore.h>
+# include <iprt/alloc.h>
+# include <iprt/uuid.h>
+# include <iprt/time.h>
+#endif
+#include "VBoxDD.h"
+
+#if defined(VBOX_WITH_DTRACE) \
+ && defined(IN_RING3) \
+ && !defined(VBOX_DEVICE_STRUCT_TESTCASE)
+# include "dtrace/VBoxDD.h"
+#else
+# define VBOXDD_AHCI_REQ_SUBMIT(a,b,c,d) do { } while (0)
+# define VBOXDD_AHCI_REQ_COMPLETED(a,b,c,d) do { } while (0)
+#endif
+
+/** Maximum number of ports available.
+ * Spec defines 32 but we have one allocated for command completion coalescing
+ * and another for a reserved future feature.
+ */
+#define AHCI_MAX_NR_PORTS_IMPL 30
+/** Maximum number of command slots available. */
+#define AHCI_NR_COMMAND_SLOTS 32
+
+/** The current saved state version. */
+#define AHCI_SAVED_STATE_VERSION 9
+/** The saved state version before the ATAPI emulation was removed and the generic SCSI driver was used. */
+#define AHCI_SAVED_STATE_VERSION_PRE_ATAPI_REMOVE 8
+/** The saved state version before changing the port reset logic in an incompatible way. */
+#define AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES 7
+/** Saved state version before the per port hotplug port was added. */
+#define AHCI_SAVED_STATE_VERSION_PRE_HOTPLUG_FLAG 6
+/** Saved state version before legacy ATA emulation was dropped. */
+#define AHCI_SAVED_STATE_VERSION_IDE_EMULATION 5
+/** Saved state version before ATAPI support was added. */
+#define AHCI_SAVED_STATE_VERSION_PRE_ATAPI 3
+/** The saved state version use in VirtualBox 3.0 and earlier.
+ * This was before the config was added and ahciIOTasks was dropped. */
+#define AHCI_SAVED_STATE_VERSION_VBOX_30 2
+/* for Older ATA state Read handling */
+#define ATA_CTL_SAVED_STATE_VERSION 3
+#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 1
+#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 2
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/**
+ * Length of the configurable VPD data (without termination)
+ */
+#define AHCI_SERIAL_NUMBER_LENGTH 20
+#define AHCI_FIRMWARE_REVISION_LENGTH 8
+#define AHCI_MODEL_NUMBER_LENGTH 40
+#define AHCI_ATAPI_INQUIRY_VENDOR_ID_LENGTH 8
+#define AHCI_ATAPI_INQUIRY_PRODUCT_ID_LENGTH 16
+#define AHCI_ATAPI_INQUIRY_REVISION_LENGTH 4
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+
+/**
+ * Command Header.
+ */
+#pragma pack(1)
+typedef struct
+{
+ /** Description Information. */
+ uint32_t u32DescInf;
+ /** Command status. */
+ uint32_t u32PRDBC;
+ /** Command Table Base Address. */
+ uint32_t u32CmdTblAddr;
+ /** Command Table Base Address - upper 32-bits. */
+ uint32_t u32CmdTblAddrUp;
+ /** Reserved */
+ uint32_t u32Reserved[4];
+} CmdHdr;
+#pragma pack()
+AssertCompileSize(CmdHdr, 32);
+
+/* Defines for the command header. */
+#define AHCI_CMDHDR_PRDTL_MASK 0xffff0000
+#define AHCI_CMDHDR_PRDTL_ENTRIES(x) ((x & AHCI_CMDHDR_PRDTL_MASK) >> 16)
+#define AHCI_CMDHDR_C RT_BIT(10)
+#define AHCI_CMDHDR_B RT_BIT(9)
+#define AHCI_CMDHDR_R RT_BIT(8)
+#define AHCI_CMDHDR_P RT_BIT(7)
+#define AHCI_CMDHDR_W RT_BIT(6)
+#define AHCI_CMDHDR_A RT_BIT(5)
+#define AHCI_CMDHDR_CFL_MASK 0x1f
+
+#define AHCI_CMDHDR_PRDT_OFFSET 0x80
+#define AHCI_CMDHDR_ACMD_OFFSET 0x40
+
+/* Defines for the command FIS. */
+/* Defines that are used in the first double word. */
+#define AHCI_CMDFIS_TYPE 0 /* The first byte. */
+# define AHCI_CMDFIS_TYPE_H2D 0x27 /* Register - Host to Device FIS. */
+# define AHCI_CMDFIS_TYPE_H2D_SIZE 20 /* Five double words. */
+# define AHCI_CMDFIS_TYPE_D2H 0x34 /* Register - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_D2H_SIZE 20 /* Five double words. */
+# define AHCI_CMDFIS_TYPE_SETDEVBITS 0xa1 /* Set Device Bits - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE 8 /* Two double words. */
+# define AHCI_CMDFIS_TYPE_DMAACTD2H 0x39 /* DMA Activate - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE 4 /* One double word. */
+# define AHCI_CMDFIS_TYPE_DMASETUP 0x41 /* DMA Setup - Bidirectional FIS. */
+# define AHCI_CMDFIS_TYPE_DMASETUP_SIZE 28 /* Seven double words. */
+# define AHCI_CMDFIS_TYPE_PIOSETUP 0x5f /* PIO Setup - Device to Host FIS. */
+# define AHCI_CMDFIS_TYPE_PIOSETUP_SIZE 20 /* Five double words. */
+# define AHCI_CMDFIS_TYPE_DATA 0x46 /* Data - Bidirectional FIS. */
+
+#define AHCI_CMDFIS_BITS 1 /* Interrupt and Update bit. */
+#define AHCI_CMDFIS_C RT_BIT(7) /* Host to device. */
+#define AHCI_CMDFIS_I RT_BIT(6) /* Device to Host. */
+#define AHCI_CMDFIS_D RT_BIT(5)
+
+#define AHCI_CMDFIS_CMD 2
+#define AHCI_CMDFIS_FET 3
+
+#define AHCI_CMDFIS_SECTN 4
+#define AHCI_CMDFIS_CYLL 5
+#define AHCI_CMDFIS_CYLH 6
+#define AHCI_CMDFIS_HEAD 7
+
+#define AHCI_CMDFIS_SECTNEXP 8
+#define AHCI_CMDFIS_CYLLEXP 9
+#define AHCI_CMDFIS_CYLHEXP 10
+#define AHCI_CMDFIS_FETEXP 11
+
+#define AHCI_CMDFIS_SECTC 12
+#define AHCI_CMDFIS_SECTCEXP 13
+#define AHCI_CMDFIS_CTL 15
+# define AHCI_CMDFIS_CTL_SRST RT_BIT(2) /* Reset device. */
+# define AHCI_CMDFIS_CTL_NIEN RT_BIT(1) /* Assert or clear interrupt. */
+
+/* For D2H FIS */
+#define AHCI_CMDFIS_STS 2
+#define AHCI_CMDFIS_ERR 3
+
+/** Pointer to a task state. */
+typedef struct AHCIREQ *PAHCIREQ;
+
+/** Task encountered a buffer overflow. */
+#define AHCI_REQ_OVERFLOW RT_BIT_32(0)
+/** Request is a PIO data command, if this flag is not set it either is
+ * a command which does not transfer data or a DMA command based on the transfer size. */
+#define AHCI_REQ_PIO_DATA RT_BIT_32(1)
+/** The request has the SACT register set. */
+#define AHCI_REQ_CLEAR_SACT RT_BIT_32(2)
+/** Flag whether the request is queued. */
+#define AHCI_REQ_IS_QUEUED RT_BIT_32(3)
+/** Flag whether the request is stored on the stack. */
+#define AHCI_REQ_IS_ON_STACK RT_BIT_32(4)
+/** Flag whether this request transfers data from the device to the HBA or
+ * the other way around .*/
+#define AHCI_REQ_XFER_2_HOST RT_BIT_32(5)
+
+/**
+ * A task state.
+ */
+typedef struct AHCIREQ
+{
+ /** The I/O request handle from the driver below associated with this request. */
+ PDMMEDIAEXIOREQ hIoReq;
+ /** Tag of the task. */
+ uint32_t uTag;
+ /** The command Fis for this task. */
+ uint8_t cmdFis[AHCI_CMDFIS_TYPE_H2D_SIZE];
+ /** The ATAPI command data. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ /** Size of one sector for the ATAPI transfer. */
+ uint32_t cbATAPISector;
+ /** Physical address of the command header. - GC */
+ RTGCPHYS GCPhysCmdHdrAddr;
+ /** Physical address of the PRDT */
+ RTGCPHYS GCPhysPrdtl;
+ /** Number of entries in the PRDTL. */
+ unsigned cPrdtlEntries;
+ /** Data direction. */
+ PDMMEDIAEXIOREQTYPE enmType;
+ /** Start offset. */
+ uint64_t uOffset;
+ /** Number of bytes to transfer. */
+ size_t cbTransfer;
+ /** Flags for this task. */
+ uint32_t fFlags;
+ /** SCSI status code. */
+ uint8_t u8ScsiSts;
+ /** Flag when the buffer is mapped. */
+ bool fMapped;
+ /** Page lock when the buffer is mapped. */
+ PGMPAGEMAPLOCK PgLck;
+} AHCIREQ;
+
+/**
+ * Notifier queue item.
+ */
+typedef struct DEVPORTNOTIFIERQUEUEITEM
+{
+ /** The core part owned by the queue manager. */
+ PDMQUEUEITEMCORE Core;
+ /** The port to process. */
+ uint8_t iPort;
+} DEVPORTNOTIFIERQUEUEITEM, *PDEVPORTNOTIFIERQUEUEITEM;
+
+
+/**
+ * @implements PDMIBASE
+ * @implements PDMIMEDIAPORT
+ * @implements PDMIMEDIAEXPORT
+ */
+typedef struct AHCIPort
+{
+ /** Pointer to the device instance - HC ptr */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 ptr */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Pointer to the parent AHCI structure - R3 ptr. */
+ R3PTRTYPE(struct AHCI *) pAhciR3;
+ /** Pointer to the parent AHCI structure - R0 ptr. */
+ R0PTRTYPE(struct AHCI *) pAhciR0;
+ /** Pointer to the parent AHCI structure - RC ptr. */
+ RCPTRTYPE(struct AHCI *) pAhciRC;
+
+ /** Command List Base Address. */
+ uint32_t regCLB;
+ /** Command List Base Address upper bits. */
+ uint32_t regCLBU;
+ /** FIS Base Address. */
+ uint32_t regFB;
+ /** FIS Base Address upper bits. */
+ uint32_t regFBU;
+ /** Interrupt Status. */
+ volatile uint32_t regIS;
+ /** Interrupt Enable. */
+ uint32_t regIE;
+ /** Command. */
+ uint32_t regCMD;
+ /** Task File Data. */
+ uint32_t regTFD;
+ /** Signature */
+ uint32_t regSIG;
+ /** Serial ATA Status. */
+ uint32_t regSSTS;
+ /** Serial ATA Control. */
+ uint32_t regSCTL;
+ /** Serial ATA Error. */
+ uint32_t regSERR;
+ /** Serial ATA Active. */
+ volatile uint32_t regSACT;
+ /** Command Issue. */
+ uint32_t regCI;
+
+ /** Current number of active tasks. */
+ volatile uint32_t cTasksActive;
+ /** Command List Base Address */
+ volatile RTGCPHYS GCPhysAddrClb;
+ /** FIS Base Address */
+ volatile RTGCPHYS GCPhysAddrFb;
+
+ /** Device is powered on. */
+ bool fPoweredOn;
+ /** Device has spun up. */
+ bool fSpunUp;
+ /** First D2H FIS was sent. */
+ bool fFirstD2HFisSent;
+ /** Attached device is a CD/DVD drive. */
+ bool fATAPI;
+ /** Flag whether this port is in a reset state. */
+ volatile bool fPortReset;
+ /** Flag whether TRIM is supported. */
+ bool fTrimEnabled;
+ /** Flag if we are in a device reset. */
+ bool fResetDevice;
+ /** Flag whether this port is hot plug capable. */
+ bool fHotpluggable;
+ /** Flag whether the port is in redo task mode. */
+ volatile bool fRedo;
+ /** Flag whether the worker thread is sleeping. */
+ volatile bool fWrkThreadSleeping;
+
+ bool afAlignment[4];
+
+ /** Number of total sectors. */
+ uint64_t cTotalSectors;
+ /** Size of one sector. */
+ uint32_t cbSector;
+ /** Currently configured number of sectors in a multi-sector transfer. */
+ uint32_t cMultSectors;
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** Exponent of logical sectors in a physical sector, number of logical sectors is 2^exp. */
+ uint8_t cLogSectorsPerPhysicalExp;
+ /** The LUN. */
+ RTUINT iLUN;
+
+ /** Bitmap for finished tasks (R3 -> Guest). */
+ volatile uint32_t u32TasksFinished;
+ /** Bitmap for finished queued tasks (R3 -> Guest). */
+ volatile uint32_t u32QueuedTasksFinished;
+ /** Bitmap for new queued tasks (Guest -> R3). */
+ volatile uint32_t u32TasksNew;
+ /** Bitmap of tasks which must be redone because of a non fatal error. */
+ volatile uint32_t u32TasksRedo;
+
+ /** Current command slot processed.
+ * Accessed by the guest by reading the CMD register.
+ * Holds the command slot of the command processed at the moment. */
+ volatile uint32_t u32CurrentCommandSlot;
+
+#if HC_ARCH_BITS == 64
+ uint32_t u32Alignment2;
+#endif
+
+ /** Device specific settings (R3 only stuff). */
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's extended interface. */
+ R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The extended media port interface. */
+ PDMIMEDIAEXPORT IMediaExPort;
+ /** Physical geometry of this image. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** The status LED state for this drive. */
+ PDMLED Led;
+
+ uint32_t u32Alignment3;
+
+ /** Async IO Thread. */
+ R3PTRTYPE(PPDMTHREAD) pAsyncIOThread;
+ /** First task throwing an error. */
+ R3PTRTYPE(volatile PAHCIREQ) pTaskErr;
+
+ /** The event semaphore the processing thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+ /** The serial numnber to use for IDENTIFY DEVICE commands. */
+ char szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1]; /** < one extra byte for termination */
+ /** The firmware revision to use for IDENTIFY DEVICE commands. */
+ char szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1]; /** < one extra byte for termination */
+ /** The model number to use for IDENTIFY DEVICE commands. */
+ char szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1]; /** < one extra byte for termination */
+ /** The vendor identification string for SCSI INQUIRY commands. */
+ char szInquiryVendorId[AHCI_ATAPI_INQUIRY_VENDOR_ID_LENGTH+1];
+ /** The product identification string for SCSI INQUIRY commands. */
+ char szInquiryProductId[AHCI_ATAPI_INQUIRY_PRODUCT_ID_LENGTH+1];
+ /** The revision string for SCSI INQUIRY commands. */
+ char szInquiryRevision[AHCI_ATAPI_INQUIRY_REVISION_LENGTH+1];
+ /** Error counter */
+ uint32_t cErrors;
+
+ uint32_t u32Alignment5;
+
+} AHCIPort;
+/** Pointer to the state of an AHCI port. */
+typedef AHCIPort *PAHCIPort;
+
+AssertCompileSizeAlignment(AHCIPort, 8);
+
+/**
+ * Main AHCI device state.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct AHCI
+{
+ /** The PCI device structure. */
+ PDMPCIDEV dev;
+ /** Pointer to the device instance - R3 ptr */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 ptr */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Status LUN: The base interface. */
+ PDMIBASE IBase;
+ /** Status LUN: Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Status LUN: Media Notifys. */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+
+#if HC_ARCH_BITS == 32
+ uint32_t Alignment1;
+#endif
+
+ /** Base address of the MMIO region. */
+ RTGCPHYS MMIOBase;
+ /** Base address of the I/O port region for Idx/Data. */
+ RTIOPORT IOPortBase;
+
+ /** Global Host Control register of the HBA */
+
+ /** HBA Capabilities - Readonly */
+ uint32_t regHbaCap;
+ /** HBA Control */
+ uint32_t regHbaCtrl;
+ /** Interrupt Status */
+ uint32_t regHbaIs;
+ /** Ports Implemented - Readonly */
+ uint32_t regHbaPi;
+ /** AHCI Version - Readonly */
+ uint32_t regHbaVs;
+ /** Command completion coalescing control */
+ uint32_t regHbaCccCtl;
+ /** Command completion coalescing ports */
+ uint32_t regHbaCccPorts;
+
+ /** Index register for BIOS access. */
+ uint32_t regIdx;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment3;
+#endif
+
+ /** Countdown timer for command completion coalescing - R3 ptr */
+ PTMTIMERR3 pHbaCccTimerR3;
+ /** Countdown timer for command completion coalescing - R0 ptr */
+ PTMTIMERR0 pHbaCccTimerR0;
+ /** Countdown timer for command completion coalescing - RC ptr */
+ PTMTIMERRC pHbaCccTimerRC;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment4;
+#endif
+
+ /** Queue to send tasks to R3. - HC ptr */
+ R3PTRTYPE(PPDMQUEUE) pNotifierQueueR3;
+ /** Queue to send tasks to R3. - HC ptr */
+ R0PTRTYPE(PPDMQUEUE) pNotifierQueueR0;
+ /** Queue to send tasks to R3. - RC ptr */
+ RCPTRTYPE(PPDMQUEUE) pNotifierQueueRC;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment5;
+#endif
+
+
+ /** Which port number is used to mark an CCC interrupt */
+ uint8_t uCccPortNr;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment6;
+#endif
+
+ /** Timeout value */
+ uint64_t uCccTimeout;
+ /** Number of completions used to assert an interrupt */
+ uint32_t uCccNr;
+ /** Current number of completed commands */
+ uint32_t uCccCurrentNr;
+
+ /** Register structure per port */
+ AHCIPort ahciPort[AHCI_MAX_NR_PORTS_IMPL];
+
+ /** The critical section. */
+ PDMCRITSECT lock;
+
+ /** Bitmask of ports which asserted an interrupt. */
+ volatile uint32_t u32PortsInterrupted;
+ /** Number of I/O threads currently active - used for async controller reset handling. */
+ volatile uint32_t cThreadsActive;
+ /** Device is in a reset state. */
+ bool fReset;
+ /** Supports 64bit addressing */
+ bool f64BitAddr;
+ /** GC enabled. */
+ bool fGCEnabled;
+ /** R0 enabled. */
+ bool fR0Enabled;
+ /** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when
+ * a port is entering the idle state. */
+ bool volatile fSignalIdle;
+ /** Flag whether the controller has BIOS access enabled. */
+ bool fBootable;
+ /** Flag whether the legacy port reset method should be used to make it work with saved states. */
+ bool fLegacyPortResetMethod;
+ /** Enable tiger (10.4.x) SSTS hack or not. */
+ bool fTigerHack;
+
+ /** Number of usable ports on this controller. */
+ uint32_t cPortsImpl;
+ /** Number of usable command slots for each port. */
+ uint32_t cCmdSlotsAvail;
+
+ /** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */
+ volatile bool f8ByteMMIO4BytesWrittenSuccessfully;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment7;
+#endif
+
+ /** The support driver session handle. */
+ R3R0PTRTYPE(PSUPDRVSESSION) pSupDrvSession;
+} AHCI;
+/** Pointer to the state of an AHCI device. */
+typedef AHCI *PAHCI;
+
+AssertCompileMemberAlignment(AHCI, ahciPort, 8);
+
+/**
+ * Scatter gather list entry.
+ */
+typedef struct
+{
+ /** Data Base Address. */
+ uint32_t u32DBA;
+ /** Data Base Address - Upper 32-bits. */
+ uint32_t u32DBAUp;
+ /** Reserved */
+ uint32_t u32Reserved;
+ /** Description information. */
+ uint32_t u32DescInf;
+} SGLEntry;
+AssertCompileSize(SGLEntry, 16);
+
+#ifdef IN_RING3
+/**
+ * Memory buffer callback.
+ *
+ * @returns nothing.
+ * @param pThis The NVME controller instance.
+ * @param GCPhys The guest physical address of the memory buffer.
+ * @param pSgBuf The pointer to the host R3 S/G buffer.
+ * @param cbCopy How many bytes to copy between the two buffers.
+ * @param pcbSkip Initially contains the amount of bytes to skip
+ * starting from the guest physical address before
+ * accessing the S/G buffer and start copying data.
+ * On return this contains the remaining amount if
+ * cbCopy < *pcbSkip or 0 otherwise.
+ */
+typedef DECLCALLBACK(void) AHCIR3MEMCOPYCALLBACK(PAHCI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf, size_t cbCopy,
+ size_t *pcbSkip);
+/** Pointer to a memory copy buffer callback. */
+typedef AHCIR3MEMCOPYCALLBACK *PAHCIR3MEMCOPYCALLBACK;
+#endif
+
+/** Defines for a scatter gather list entry. */
+#define SGLENTRY_DBA_READONLY ~(RT_BIT(0))
+#define SGLENTRY_DESCINF_I RT_BIT(31)
+#define SGLENTRY_DESCINF_DBC 0x3fffff
+#define SGLENTRY_DESCINF_READONLY 0x803fffff
+
+/* Defines for the global host control registers for the HBA. */
+
+#define AHCI_HBA_GLOBAL_SIZE 0x100
+
+/* Defines for the HBA Capabilities - Readonly */
+#define AHCI_HBA_CAP_S64A RT_BIT(31)
+#define AHCI_HBA_CAP_SNCQ RT_BIT(30)
+#define AHCI_HBA_CAP_SIS RT_BIT(28)
+#define AHCI_HBA_CAP_SSS RT_BIT(27)
+#define AHCI_HBA_CAP_SALP RT_BIT(26)
+#define AHCI_HBA_CAP_SAL RT_BIT(25)
+#define AHCI_HBA_CAP_SCLO RT_BIT(24)
+#define AHCI_HBA_CAP_ISS (RT_BIT(23) | RT_BIT(22) | RT_BIT(21) | RT_BIT(20))
+# define AHCI_HBA_CAP_ISS_SHIFT(x) (((x) << 20) & AHCI_HBA_CAP_ISS)
+# define AHCI_HBA_CAP_ISS_GEN1 RT_BIT(0)
+# define AHCI_HBA_CAP_ISS_GEN2 RT_BIT(1)
+#define AHCI_HBA_CAP_SNZO RT_BIT(19)
+#define AHCI_HBA_CAP_SAM RT_BIT(18)
+#define AHCI_HBA_CAP_SPM RT_BIT(17)
+#define AHCI_HBA_CAP_PMD RT_BIT(15)
+#define AHCI_HBA_CAP_SSC RT_BIT(14)
+#define AHCI_HBA_CAP_PSC RT_BIT(13)
+#define AHCI_HBA_CAP_NCS (RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_HBA_CAP_NCS_SET(x) (((x-1) << 8) & AHCI_HBA_CAP_NCS) /* 0's based */
+#define AHCI_HBA_CAP_CCCS RT_BIT(7)
+#define AHCI_HBA_CAP_NP (RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_HBA_CAP_NP_SET(x) ((x-1) & AHCI_HBA_CAP_NP) /* 0's based */
+
+/* Defines for the HBA Control register - Read/Write */
+#define AHCI_HBA_CTRL_AE RT_BIT(31)
+#define AHCI_HBA_CTRL_IE RT_BIT(1)
+#define AHCI_HBA_CTRL_HR RT_BIT(0)
+#define AHCI_HBA_CTRL_RW_MASK (RT_BIT(0) | RT_BIT(1)) /* Mask for the used bits */
+
+/* Defines for the HBA Version register - Readonly (We support AHCI 1.0) */
+#define AHCI_HBA_VS_MJR (1 << 16)
+#define AHCI_HBA_VS_MNR 0x100
+
+/* Defines for the command completion coalescing control register */
+#define AHCI_HBA_CCC_CTL_TV 0xffff0000
+#define AHCI_HBA_CCC_CTL_TV_SET(x) (x << 16)
+#define AHCI_HBA_CCC_CTL_TV_GET(x) ((x & AHCI_HBA_CCC_CTL_TV) >> 16)
+
+#define AHCI_HBA_CCC_CTL_CC 0xff00
+#define AHCI_HBA_CCC_CTL_CC_SET(x) (x << 8)
+#define AHCI_HBA_CCC_CTL_CC_GET(x) ((x & AHCI_HBA_CCC_CTL_CC) >> 8)
+
+#define AHCI_HBA_CCC_CTL_INT 0xf8
+#define AHCI_HBA_CCC_CTL_INT_SET(x) (x << 3)
+#define AHCI_HBA_CCC_CTL_INT_GET(x) ((x & AHCI_HBA_CCC_CTL_INT) >> 3)
+
+#define AHCI_HBA_CCC_CTL_EN RT_BIT(0)
+
+/* Defines for the port registers. */
+
+#define AHCI_PORT_REGISTER_SIZE 0x80
+
+#define AHCI_PORT_CLB_RESERVED 0xfffffc00 /* For masking out the reserved bits. */
+
+#define AHCI_PORT_FB_RESERVED 0xffffff00 /* For masking out the reserved bits. */
+
+#define AHCI_PORT_IS_CPDS RT_BIT(31)
+#define AHCI_PORT_IS_TFES RT_BIT(30)
+#define AHCI_PORT_IS_HBFS RT_BIT(29)
+#define AHCI_PORT_IS_HBDS RT_BIT(28)
+#define AHCI_PORT_IS_IFS RT_BIT(27)
+#define AHCI_PORT_IS_INFS RT_BIT(26)
+#define AHCI_PORT_IS_OFS RT_BIT(24)
+#define AHCI_PORT_IS_IPMS RT_BIT(23)
+#define AHCI_PORT_IS_PRCS RT_BIT(22)
+#define AHCI_PORT_IS_DIS RT_BIT(7)
+#define AHCI_PORT_IS_PCS RT_BIT(6)
+#define AHCI_PORT_IS_DPS RT_BIT(5)
+#define AHCI_PORT_IS_UFS RT_BIT(4)
+#define AHCI_PORT_IS_SDBS RT_BIT(3)
+#define AHCI_PORT_IS_DSS RT_BIT(2)
+#define AHCI_PORT_IS_PSS RT_BIT(1)
+#define AHCI_PORT_IS_DHRS RT_BIT(0)
+#define AHCI_PORT_IS_READONLY 0xfd8000af /* Readonly mask including reserved bits. */
+
+#define AHCI_PORT_IE_CPDE RT_BIT(31)
+#define AHCI_PORT_IE_TFEE RT_BIT(30)
+#define AHCI_PORT_IE_HBFE RT_BIT(29)
+#define AHCI_PORT_IE_HBDE RT_BIT(28)
+#define AHCI_PORT_IE_IFE RT_BIT(27)
+#define AHCI_PORT_IE_INFE RT_BIT(26)
+#define AHCI_PORT_IE_OFE RT_BIT(24)
+#define AHCI_PORT_IE_IPME RT_BIT(23)
+#define AHCI_PORT_IE_PRCE RT_BIT(22)
+#define AHCI_PORT_IE_DIE RT_BIT(7) /* Not supported for now, readonly. */
+#define AHCI_PORT_IE_PCE RT_BIT(6)
+#define AHCI_PORT_IE_DPE RT_BIT(5)
+#define AHCI_PORT_IE_UFE RT_BIT(4)
+#define AHCI_PORT_IE_SDBE RT_BIT(3)
+#define AHCI_PORT_IE_DSE RT_BIT(2)
+#define AHCI_PORT_IE_PSE RT_BIT(1)
+#define AHCI_PORT_IE_DHRE RT_BIT(0)
+#define AHCI_PORT_IE_READONLY (0xfdc000ff) /* Readonly mask including reserved bits. */
+
+#define AHCI_PORT_CMD_ICC (RT_BIT(28) | RT_BIT(29) | RT_BIT(30) | RT_BIT(31))
+#define AHCI_PORT_CMD_ICC_SHIFT(x) ((x) << 28)
+# define AHCI_PORT_CMD_ICC_IDLE 0x0
+# define AHCI_PORT_CMD_ICC_ACTIVE 0x1
+# define AHCI_PORT_CMD_ICC_PARTIAL 0x2
+# define AHCI_PORT_CMD_ICC_SLUMBER 0x6
+#define AHCI_PORT_CMD_ASP RT_BIT(27) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_ALPE RT_BIT(26) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_DLAE RT_BIT(25)
+#define AHCI_PORT_CMD_ATAPI RT_BIT(24)
+#define AHCI_PORT_CMD_CPD RT_BIT(20)
+#define AHCI_PORT_CMD_ISP RT_BIT(19) /* Readonly */
+#define AHCI_PORT_CMD_HPCP RT_BIT(18)
+#define AHCI_PORT_CMD_PMA RT_BIT(17) /* Not supported - Readonly */
+#define AHCI_PORT_CMD_CPS RT_BIT(16)
+#define AHCI_PORT_CMD_CR RT_BIT(15) /* Readonly */
+#define AHCI_PORT_CMD_FR RT_BIT(14) /* Readonly */
+#define AHCI_PORT_CMD_ISS RT_BIT(13) /* Readonly */
+#define AHCI_PORT_CMD_CCS (RT_BIT(8) | RT_BIT(9) | RT_BIT(10) | RT_BIT(11) | RT_BIT(12))
+#define AHCI_PORT_CMD_CCS_SHIFT(x) (x << 8) /* Readonly */
+#define AHCI_PORT_CMD_FRE RT_BIT(4)
+#define AHCI_PORT_CMD_CLO RT_BIT(3)
+#define AHCI_PORT_CMD_POD RT_BIT(2)
+#define AHCI_PORT_CMD_SUD RT_BIT(1)
+#define AHCI_PORT_CMD_ST RT_BIT(0)
+#define AHCI_PORT_CMD_READONLY (0xff02001f & ~(AHCI_PORT_CMD_ASP | AHCI_PORT_CMD_ALPE | AHCI_PORT_CMD_PMA))
+
+#define AHCI_PORT_SCTL_IPM (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_PORT_SCTL_IPM_GET(x) ((x & AHCI_PORT_SCTL_IPM) >> 8)
+#define AHCI_PORT_SCTL_SPD (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))
+#define AHCI_PORT_SCTL_SPD_GET(x) ((x & AHCI_PORT_SCTL_SPD) >> 4)
+#define AHCI_PORT_SCTL_DET (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_PORT_SCTL_DET_GET(x) (x & AHCI_PORT_SCTL_DET)
+#define AHCI_PORT_SCTL_DET_NINIT 0
+#define AHCI_PORT_SCTL_DET_INIT 1
+#define AHCI_PORT_SCTL_DET_OFFLINE 4
+#define AHCI_PORT_SCTL_READONLY 0xfff
+
+#define AHCI_PORT_SSTS_IPM (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8))
+#define AHCI_PORT_SSTS_IPM_GET(x) ((x & AHCI_PORT_SCTL_IPM) >> 8)
+#define AHCI_PORT_SSTS_SPD (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))
+#define AHCI_PORT_SSTS_SPD_GET(x) ((x & AHCI_PORT_SCTL_SPD) >> 4)
+#define AHCI_PORT_SSTS_DET (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0))
+#define AHCI_PORT_SSTS_DET_GET(x) (x & AHCI_PORT_SCTL_DET)
+
+#define AHCI_PORT_TFD_BSY RT_BIT(7)
+#define AHCI_PORT_TFD_DRQ RT_BIT(3)
+#define AHCI_PORT_TFD_ERR RT_BIT(0)
+
+#define AHCI_PORT_SERR_X RT_BIT(26)
+#define AHCI_PORT_SERR_W RT_BIT(18)
+#define AHCI_PORT_SERR_N RT_BIT(16)
+
+/* Signatures for attached storage devices. */
+#define AHCI_PORT_SIG_DISK 0x00000101
+#define AHCI_PORT_SIG_ATAPI 0xeb140101
+
+/*
+ * The AHCI spec defines an area of memory where the HBA posts received FIS's from the device.
+ * regFB points to the base of this area.
+ * Every FIS type has an offset where it is posted in this area.
+ */
+#define AHCI_RECFIS_DSFIS_OFFSET 0x00 /* DMA Setup FIS */
+#define AHCI_RECFIS_PSFIS_OFFSET 0x20 /* PIO Setup FIS */
+#define AHCI_RECFIS_RFIS_OFFSET 0x40 /* D2H Register FIS */
+#define AHCI_RECFIS_SDBFIS_OFFSET 0x58 /* Set Device Bits FIS */
+#define AHCI_RECFIS_UFIS_OFFSET 0x60 /* Unknown FIS type */
+
+/** Mask to get the LBA value from a LBA range. */
+#define AHCI_RANGE_LBA_MASK UINT64_C(0xffffffffffff)
+/** Mas to get the length value from a LBA range. */
+#define AHCI_RANGE_LENGTH_MASK UINT64_C(0xffff000000000000)
+/** Returns the length of the range in sectors. */
+#define AHCI_RANGE_LENGTH_GET(val) (((val) & AHCI_RANGE_LENGTH_MASK) >> 48)
+
+/**
+ * AHCI register operator.
+ */
+typedef struct ahci_opreg
+{
+ const char *pszName;
+ int (*pfnRead )(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value);
+ int (*pfnWrite)(PAHCI pAhci, uint32_t iReg, uint32_t u32Value);
+} AHCIOPREG;
+
+/**
+ * AHCI port register operator.
+ */
+typedef struct pAhciPort_opreg
+{
+ const char *pszName;
+ int (*pfnRead )(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value);
+ int (*pfnWrite)(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value);
+} AHCIPORTOPREG;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+static void ahciHBAReset(PAHCI pThis);
+static int ahciPostFisIntoMemory(PAHCIPort pAhciPort, unsigned uFisType, uint8_t *cmdFis);
+static void ahciPostFirstD2HFisIntoMemory(PAHCIPort pAhciPort);
+static size_t ahciR3CopyBufferToPrdtl(PAHCI pThis, PAHCIREQ pAhciReq, const void *pvSrc,
+ size_t cbSrc, size_t cbSkip);
+static bool ahciCancelActiveTasks(PAHCIPort pAhciPort);
+#endif
+RT_C_DECLS_END
+
+#define PCIDEV_2_PAHCI(pPciDev) ( (PAHCI)(pPciDev) )
+#define PDMIBASE_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_UOFFSETOF(AHCIPort, IBase)) )
+#define PDMIMEDIAPORT_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_UOFFSETOF(AHCIPort, IPort)) )
+#define PDMIBASE_2_PAHCI(pInterface) ( (PAHCI)((uintptr_t)(pInterface) - RT_UOFFSETOF(AHCI, IBase)) )
+#define PDMILEDPORTS_2_PAHCI(pInterface) ( (PAHCI)((uintptr_t)(pInterface) - RT_UOFFSETOF(AHCI, ILeds)) )
+
+#define AHCI_RTGCPHYS_FROM_U32(Hi, Lo) ( (RTGCPHYS)RT_MAKE_U64(Lo, Hi) )
+
+#ifdef IN_RING3
+
+# ifdef LOG_USE_C99
+# define ahciLog(a) \
+ Log(("R3 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+# define ahciLog(a) \
+ do { Log(("R3 P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#elif defined(IN_RING0)
+
+# ifdef LOG_USE_C99
+# define ahciLog(a) \
+ Log(("R0 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+# define ahciLog(a) \
+ do { Log(("R0 P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#elif defined(IN_RC)
+
+# ifdef LOG_USE_C99
+# define ahciLog(a) \
+ Log(("GC P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a))
+# else
+# define ahciLog(a) \
+ do { Log(("GC P%u: ", pAhciPort->iLUN)); Log(a); } while(0)
+# endif
+
+#endif
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ahciHbaClearInterrupt(PAHCI pAhci)
+{
+ Log(("%s: Clearing interrupt\n", __FUNCTION__));
+ PDMDevHlpPCISetIrq(pAhci->CTX_SUFF(pDevIns), 0, 0);
+}
+
+/**
+ * Updates the IRQ level and sets port bit in the global interrupt status register of the HBA.
+ */
+static int ahciHbaSetInterrupt(PAHCI pAhci, uint8_t iPort, int rcBusy)
+{
+ Log(("P%u: %s: Setting interrupt\n", iPort, __FUNCTION__));
+
+ int rc = PDMCritSectEnter(&pAhci->lock, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if (pAhci->regHbaCtrl & AHCI_HBA_CTRL_IE)
+ {
+ if ((pAhci->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN) && (pAhci->regHbaCccPorts & (1 << iPort)))
+ {
+ pAhci->uCccCurrentNr++;
+ if (pAhci->uCccCurrentNr >= pAhci->uCccNr)
+ {
+ /* Reset command completion coalescing state. */
+ TMTimerSetMillies(pAhci->CTX_SUFF(pHbaCccTimer), pAhci->uCccTimeout);
+ pAhci->uCccCurrentNr = 0;
+
+ pAhci->u32PortsInterrupted |= (1 << pAhci->uCccPortNr);
+ if (!(pAhci->u32PortsInterrupted & ~(1 << pAhci->uCccPortNr)))
+ {
+ Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+ PDMDevHlpPCISetIrq(pAhci->CTX_SUFF(pDevIns), 0, 1);
+ }
+ }
+ }
+ else
+ {
+ /* If only the bit of the actual port is set assert an interrupt
+ * because the interrupt status register was already read by the guest
+ * and we need to send a new notification.
+ * Otherwise an interrupt is still pending.
+ */
+ ASMAtomicOrU32((volatile uint32_t *)&pAhci->u32PortsInterrupted, (1 << iPort));
+ if (!(pAhci->u32PortsInterrupted & ~(1 << iPort)))
+ {
+ Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+ PDMDevHlpPCISetIrq(pAhci->CTX_SUFF(pDevIns), 0, 1);
+ }
+ }
+ }
+
+ PDMCritSectLeave(&pAhci->lock);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/*
+ * Assert irq when an CCC timeout occurs
+ */
+static DECLCALLBACK(void) ahciCccTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, pTimer);
+ PAHCI pAhci = (PAHCI)pvUser;
+
+ int rc = ahciHbaSetInterrupt(pAhci, pAhci->uCccPortNr, VERR_IGNORED);
+ AssertRC(rc);
+}
+
+/**
+ * Finishes the port reset of the given port.
+ *
+ * @returns nothing.
+ * @param pAhciPort The port to finish the reset on.
+ */
+static void ahciPortResetFinish(PAHCIPort pAhciPort)
+{
+ ahciLog(("%s: Initiated.\n", __FUNCTION__));
+
+ /* Cancel all tasks first. */
+ bool fAllTasksCanceled = ahciCancelActiveTasks(pAhciPort);
+ Assert(fAllTasksCanceled); NOREF(fAllTasksCanceled);
+
+ /* Signature for SATA device. */
+ if (pAhciPort->fATAPI)
+ pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+ else
+ pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+
+ /* We received a COMINIT from the device. Tell the guest. */
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_PCS);
+ pAhciPort->regSERR |= AHCI_PORT_SERR_X;
+ pAhciPort->regTFD |= ATA_STAT_BUSY;
+
+ if ((pAhciPort->regCMD & AHCI_PORT_CMD_FRE) && (!pAhciPort->fFirstD2HFisSent))
+ {
+ ahciPostFirstD2HFisIntoMemory(pAhciPort);
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS);
+
+ if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+ {
+ int rc = ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+
+ pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+ (0x03 << 0); /* Device detected and communication established. */
+
+ /*
+ * Use the maximum allowed speed.
+ * (Not that it changes anything really)
+ */
+ switch (AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL))
+ {
+ case 0x01:
+ pAhciPort->regSSTS |= (0x01 << 4); /* Generation 1 (1.5GBps) speed. */
+ break;
+ case 0x02:
+ case 0x00:
+ default:
+ pAhciPort->regSSTS |= (0x02 << 4); /* Generation 2 (3.0GBps) speed. */
+ break;
+ }
+
+ ASMAtomicXchgBool(&pAhciPort->fPortReset, false);
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Kicks the I/O thread from RC or R0.
+ *
+ * @returns nothing.
+ * @param pAhci The AHCI controller instance.
+ * @param pAhciPort The port to kick.
+ */
+static void ahciIoThreadKick(PAHCI pAhci, PAHCIPort pAhciPort)
+{
+#ifdef IN_RC
+ PDEVPORTNOTIFIERQUEUEITEM pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(pAhci->CTX_SUFF(pNotifierQueue));
+ AssertMsg(VALID_PTR(pItem), ("Allocating item for queue failed\n"));
+
+ if (pItem)
+ {
+ pItem->iPort = pAhciPort->iLUN;
+ PDMQueueInsert(pAhci->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+#else
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = SUPSemEventSignal(pAhci->pSupDrvSession, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+#endif
+}
+
+static int PortCmdIssue_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ /* Update the CI register first. */
+ uint32_t uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0);
+ pAhciPort->regCI &= ~uCIValue;
+
+ if ( (pAhciPort->regCMD & AHCI_PORT_CMD_CR)
+ && u32Value > 0)
+ {
+ /*
+ * Clear all tasks which are already marked as busy. The guest
+ * shouldn't write already busy tasks actually.
+ */
+ u32Value &= ~pAhciPort->regCI;
+
+ ASMAtomicOrU32(&pAhciPort->u32TasksNew, u32Value);
+
+ /* Send a notification to R3 if u32TasksNew was 0 before our write. */
+ if (ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping))
+ ahciIoThreadKick(pAhci, pAhciPort);
+ else
+ ahciLog(("%s: Worker thread busy, no need to kick.\n", __FUNCTION__));
+ }
+ else
+ ahciLog(("%s: Nothing to do (CMD=%08x).\n", __FUNCTION__, pAhciPort->regCMD));
+
+ pAhciPort->regCI |= u32Value;
+
+ return VINF_SUCCESS;
+}
+
+static int PortCmdIssue_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+
+ uint32_t uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0);
+ ahciLog(("%s: read regCI=%#010x uCIValue=%#010x\n", __FUNCTION__, pAhciPort->regCI, uCIValue));
+
+ pAhciPort->regCI &= ~uCIValue;
+ *pu32Value = pAhciPort->regCI;
+
+ return VINF_SUCCESS;
+}
+
+static int PortSActive_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ pAhciPort->regSACT |= u32Value;
+
+ return VINF_SUCCESS;
+}
+
+static int PortSActive_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+
+ uint32_t u32TasksFinished = ASMAtomicXchgU32(&pAhciPort->u32QueuedTasksFinished, 0);
+ pAhciPort->regSACT &= ~u32TasksFinished;
+
+ ahciLog(("%s: read regSACT=%#010x regCI=%#010x u32TasksFinished=%#010x\n",
+ __FUNCTION__, pAhciPort->regSACT, pAhciPort->regCI, u32TasksFinished));
+
+ *pu32Value = pAhciPort->regSACT;
+
+ return VINF_SUCCESS;
+}
+
+static int PortSError_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ if ( (u32Value & AHCI_PORT_SERR_X)
+ && (pAhciPort->regSERR & AHCI_PORT_SERR_X))
+ {
+ ASMAtomicAndU32(&pAhciPort->regIS, ~AHCI_PORT_IS_PCS);
+ pAhciPort->regTFD |= ATA_STAT_ERR;
+ pAhciPort->regTFD &= ~(ATA_STAT_DRQ | ATA_STAT_BUSY);
+ }
+
+ if ( (u32Value & AHCI_PORT_SERR_N)
+ && (pAhciPort->regSERR & AHCI_PORT_SERR_N))
+ ASMAtomicAndU32(&pAhciPort->regIS, ~AHCI_PORT_IS_PRCS);
+
+ pAhciPort->regSERR &= ~u32Value;
+
+ return VINF_SUCCESS;
+}
+
+static int PortSError_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regSERR=%#010x\n", __FUNCTION__, pAhciPort->regSERR));
+ *pu32Value = pAhciPort->regSERR;
+ return VINF_SUCCESS;
+}
+
+static int PortSControl_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+ AHCI_PORT_SCTL_IPM_GET(u32Value), AHCI_PORT_SCTL_SPD_GET(u32Value), AHCI_PORT_SCTL_DET_GET(u32Value)));
+
+#ifndef IN_RING3
+ RT_NOREF2(pAhciPort, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ if ((u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT)
+ {
+ if (!ASMAtomicXchgBool(&pAhciPort->fPortReset, true))
+ LogRel(("AHCI#%u: Port %d reset\n", pAhci->CTX_SUFF(pDevIns)->iInstance,
+ pAhciPort->iLUN));
+
+ pAhciPort->regSSTS = 0;
+ pAhciPort->regSIG = UINT32_MAX;
+ pAhciPort->regTFD = 0x7f;
+ pAhciPort->fFirstD2HFisSent = false;
+ pAhciPort->regSCTL = u32Value;
+ }
+ else if ( (u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_NINIT
+ && (pAhciPort->regSCTL & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT
+ && pAhciPort->pDrvBase)
+ {
+ /* Do the port reset here, so the guest sees the new status immediately. */
+ if (pAhci->fLegacyPortResetMethod)
+ {
+ ahciPortResetFinish(pAhciPort);
+ pAhciPort->regSCTL = u32Value; /* Update after finishing the reset, so the I/O thread doesn't get a chance to do the reset. */
+ }
+ else
+ {
+ if (!pAhci->fTigerHack)
+ pAhciPort->regSSTS = 0x1; /* Indicate device presence detected but communication not established. */
+ else
+ pAhciPort->regSSTS = 0x0; /* Indicate no device detected after COMRESET. [tiger hack] */
+ pAhciPort->regSCTL = u32Value; /* Update before kicking the I/O thread. */
+
+ /* Kick the thread to finish the reset. */
+ ahciIoThreadKick(pAhci, pAhciPort);
+ }
+ }
+ else /* Just update the value if there is no device attached. */
+ pAhciPort->regSCTL = u32Value;
+
+ return VINF_SUCCESS;
+#endif
+}
+
+static int PortSControl_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regSCTL=%#010x\n", __FUNCTION__, pAhciPort->regSCTL));
+ ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+ AHCI_PORT_SCTL_IPM_GET(pAhciPort->regSCTL), AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL),
+ AHCI_PORT_SCTL_DET_GET(pAhciPort->regSCTL)));
+
+ *pu32Value = pAhciPort->regSCTL;
+ return VINF_SUCCESS;
+}
+
+static int PortSStatus_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regSSTS=%#010x\n", __FUNCTION__, pAhciPort->regSSTS));
+ ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__,
+ AHCI_PORT_SSTS_IPM_GET(pAhciPort->regSSTS), AHCI_PORT_SSTS_SPD_GET(pAhciPort->regSSTS),
+ AHCI_PORT_SSTS_DET_GET(pAhciPort->regSSTS)));
+
+ *pu32Value = pAhciPort->regSSTS;
+ return VINF_SUCCESS;
+}
+
+static int PortSignature_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regSIG=%#010x\n", __FUNCTION__, pAhciPort->regSIG));
+ *pu32Value = pAhciPort->regSIG;
+ return VINF_SUCCESS;
+}
+
+static int PortTaskFileData_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regTFD=%#010x\n", __FUNCTION__, pAhciPort->regTFD));
+ ahciLog(("%s: ERR=%x BSY=%d DRQ=%d ERR=%d\n", __FUNCTION__,
+ (pAhciPort->regTFD >> 8), (pAhciPort->regTFD & AHCI_PORT_TFD_BSY) >> 7,
+ (pAhciPort->regTFD & AHCI_PORT_TFD_DRQ) >> 3, (pAhciPort->regTFD & AHCI_PORT_TFD_ERR)));
+ *pu32Value = pAhciPort->regTFD;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command register.
+ */
+static int PortCmd_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regCMD=%#010x\n", __FUNCTION__, pAhciPort->regCMD | AHCI_PORT_CMD_CCS_SHIFT(pAhciPort->u32CurrentCommandSlot)));
+ ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n",
+ __FUNCTION__, (pAhciPort->regCMD & AHCI_PORT_CMD_ICC) >> 28, (pAhciPort->regCMD & AHCI_PORT_CMD_ASP) >> 27,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_ALPE) >> 26, (pAhciPort->regCMD & AHCI_PORT_CMD_DLAE) >> 25,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_ATAPI) >> 24, (pAhciPort->regCMD & AHCI_PORT_CMD_CPD) >> 20,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_ISP) >> 19, (pAhciPort->regCMD & AHCI_PORT_CMD_HPCP) >> 18,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_PMA) >> 17, (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) >> 16,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_CR) >> 15, (pAhciPort->regCMD & AHCI_PORT_CMD_FR) >> 14,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_ISS) >> 13, pAhciPort->u32CurrentCommandSlot,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) >> 4, (pAhciPort->regCMD & AHCI_PORT_CMD_CLO) >> 3,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_POD) >> 2, (pAhciPort->regCMD & AHCI_PORT_CMD_SUD) >> 1,
+ (pAhciPort->regCMD & AHCI_PORT_CMD_ST)));
+ *pu32Value = pAhciPort->regCMD | AHCI_PORT_CMD_CCS_SHIFT(pAhciPort->u32CurrentCommandSlot);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command register.
+ * This is the register where all the data transfer is started
+ */
+static int PortCmd_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n",
+ __FUNCTION__, (u32Value & AHCI_PORT_CMD_ICC) >> 28, (u32Value & AHCI_PORT_CMD_ASP) >> 27,
+ (u32Value & AHCI_PORT_CMD_ALPE) >> 26, (u32Value & AHCI_PORT_CMD_DLAE) >> 25,
+ (u32Value & AHCI_PORT_CMD_ATAPI) >> 24, (u32Value & AHCI_PORT_CMD_CPD) >> 20,
+ (u32Value & AHCI_PORT_CMD_ISP) >> 19, (u32Value & AHCI_PORT_CMD_HPCP) >> 18,
+ (u32Value & AHCI_PORT_CMD_PMA) >> 17, (u32Value & AHCI_PORT_CMD_CPS) >> 16,
+ (u32Value & AHCI_PORT_CMD_CR) >> 15, (u32Value & AHCI_PORT_CMD_FR) >> 14,
+ (u32Value & AHCI_PORT_CMD_ISS) >> 13, (u32Value & AHCI_PORT_CMD_CCS) >> 8,
+ (u32Value & AHCI_PORT_CMD_FRE) >> 4, (u32Value & AHCI_PORT_CMD_CLO) >> 3,
+ (u32Value & AHCI_PORT_CMD_POD) >> 2, (u32Value & AHCI_PORT_CMD_SUD) >> 1,
+ (u32Value & AHCI_PORT_CMD_ST)));
+
+ /* The PxCMD.CCS bits are R/O and maintained separately. */
+ u32Value &= ~AHCI_PORT_CMD_CCS;
+
+ if (pAhciPort->fPoweredOn && pAhciPort->fSpunUp)
+ {
+ if (u32Value & AHCI_PORT_CMD_CLO)
+ {
+ ahciLog(("%s: Command list override requested\n", __FUNCTION__));
+ u32Value &= ~(AHCI_PORT_TFD_BSY | AHCI_PORT_TFD_DRQ);
+ /* Clear the CLO bit. */
+ u32Value &= ~(AHCI_PORT_CMD_CLO);
+ }
+
+ if (u32Value & AHCI_PORT_CMD_ST)
+ {
+ /*
+ * Set engine state to running if there is a device attached and
+ * IS.PCS is clear.
+ */
+ if ( pAhciPort->pDrvBase
+ && !(pAhciPort->regIS & AHCI_PORT_IS_PCS))
+ {
+ ahciLog(("%s: Engine starts\n", __FUNCTION__));
+ u32Value |= AHCI_PORT_CMD_CR;
+
+ /* If there is something in CI, kick the I/O thread. */
+ if ( pAhciPort->regCI > 0
+ && ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping))
+ {
+ ASMAtomicOrU32(&pAhciPort->u32TasksNew, pAhciPort->regCI);
+#ifdef IN_RC
+ PDEVPORTNOTIFIERQUEUEITEM pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(pAhci->CTX_SUFF(pNotifierQueue));
+ AssertMsg(VALID_PTR(pItem), ("Allocating item for queue failed\n"));
+
+ pItem->iPort = pAhciPort->iLUN;
+ PDMQueueInsert(pAhci->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+#else
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = SUPSemEventSignal(pAhci->pSupDrvSession, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+#endif
+ }
+ }
+ else
+ {
+ if (!pAhciPort->pDrvBase)
+ ahciLog(("%s: No pDrvBase, clearing PxCMD.CR!\n", __FUNCTION__));
+ else
+ ahciLog(("%s: PxIS.PCS set (PxIS=%#010x), clearing PxCMD.CR!\n", __FUNCTION__, pAhciPort->regIS));
+
+ u32Value &= ~AHCI_PORT_CMD_CR;
+ }
+ }
+ else
+ {
+ ahciLog(("%s: Engine stops\n", __FUNCTION__));
+ /* Clear command issue register. */
+ pAhciPort->regCI = 0;
+ pAhciPort->regSACT = 0;
+ /* Clear current command slot. */
+ pAhciPort->u32CurrentCommandSlot = 0;
+ u32Value &= ~AHCI_PORT_CMD_CR;
+ }
+ }
+ else if (pAhciPort->pDrvBase)
+ {
+ if ((u32Value & AHCI_PORT_CMD_POD) && (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) && !pAhciPort->fPoweredOn)
+ {
+ ahciLog(("%s: Power on the device\n", __FUNCTION__));
+ pAhciPort->fPoweredOn = true;
+
+ /*
+ * Set states in the Port Signature and SStatus registers.
+ */
+ if (pAhciPort->fATAPI)
+ pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+ else
+ pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+ pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+ (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
+ (0x03 << 0); /* Device detected and communication established. */
+
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ ahciPostFirstD2HFisIntoMemory(pAhciPort);
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS);
+
+ if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+ {
+ int rc = ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+#endif
+ }
+ }
+
+ if ((u32Value & AHCI_PORT_CMD_SUD) && pAhciPort->fPoweredOn && !pAhciPort->fSpunUp)
+ {
+ ahciLog(("%s: Spin up the device\n", __FUNCTION__));
+ pAhciPort->fSpunUp = true;
+ }
+ }
+ else
+ ahciLog(("%s: No pDrvBase, no fPoweredOn + fSpunUp, doing nothing!\n", __FUNCTION__));
+
+ if (u32Value & AHCI_PORT_CMD_FRE)
+ {
+ ahciLog(("%s: FIS receive enabled\n", __FUNCTION__));
+
+ u32Value |= AHCI_PORT_CMD_FR;
+
+ /* Send the first D2H FIS only if it wasn't already sent. */
+ if ( !pAhciPort->fFirstD2HFisSent
+ && pAhciPort->pDrvBase)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ ahciPostFirstD2HFisIntoMemory(pAhciPort);
+ pAhciPort->fFirstD2HFisSent = true;
+#endif
+ }
+ }
+ else if (!(u32Value & AHCI_PORT_CMD_FRE))
+ {
+ ahciLog(("%s: FIS receive disabled\n", __FUNCTION__));
+ u32Value &= ~AHCI_PORT_CMD_FR;
+ }
+
+ pAhciPort->regCMD = u32Value;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port interrupt enable register.
+ */
+static int PortIntrEnable_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regIE=%#010x\n", __FUNCTION__, pAhciPort->regIE));
+ ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n",
+ __FUNCTION__, (pAhciPort->regIE & AHCI_PORT_IE_CPDE) >> 31, (pAhciPort->regIE & AHCI_PORT_IE_TFEE) >> 30,
+ (pAhciPort->regIE & AHCI_PORT_IE_HBFE) >> 29, (pAhciPort->regIE & AHCI_PORT_IE_HBDE) >> 28,
+ (pAhciPort->regIE & AHCI_PORT_IE_IFE) >> 27, (pAhciPort->regIE & AHCI_PORT_IE_INFE) >> 26,
+ (pAhciPort->regIE & AHCI_PORT_IE_OFE) >> 24, (pAhciPort->regIE & AHCI_PORT_IE_IPME) >> 23,
+ (pAhciPort->regIE & AHCI_PORT_IE_PRCE) >> 22, (pAhciPort->regIE & AHCI_PORT_IE_DIE) >> 7,
+ (pAhciPort->regIE & AHCI_PORT_IE_PCE) >> 6, (pAhciPort->regIE & AHCI_PORT_IE_DPE) >> 5,
+ (pAhciPort->regIE & AHCI_PORT_IE_UFE) >> 4, (pAhciPort->regIE & AHCI_PORT_IE_SDBE) >> 3,
+ (pAhciPort->regIE & AHCI_PORT_IE_DSE) >> 2, (pAhciPort->regIE & AHCI_PORT_IE_PSE) >> 1,
+ (pAhciPort->regIE & AHCI_PORT_IE_DHRE)));
+ *pu32Value = pAhciPort->regIE;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port interrupt enable register.
+ */
+static int PortIntrEnable_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n",
+ __FUNCTION__, (u32Value & AHCI_PORT_IE_CPDE) >> 31, (u32Value & AHCI_PORT_IE_TFEE) >> 30,
+ (u32Value & AHCI_PORT_IE_HBFE) >> 29, (u32Value & AHCI_PORT_IE_HBDE) >> 28,
+ (u32Value & AHCI_PORT_IE_IFE) >> 27, (u32Value & AHCI_PORT_IE_INFE) >> 26,
+ (u32Value & AHCI_PORT_IE_OFE) >> 24, (u32Value & AHCI_PORT_IE_IPME) >> 23,
+ (u32Value & AHCI_PORT_IE_PRCE) >> 22, (u32Value & AHCI_PORT_IE_DIE) >> 7,
+ (u32Value & AHCI_PORT_IE_PCE) >> 6, (u32Value & AHCI_PORT_IE_DPE) >> 5,
+ (u32Value & AHCI_PORT_IE_UFE) >> 4, (u32Value & AHCI_PORT_IE_SDBE) >> 3,
+ (u32Value & AHCI_PORT_IE_DSE) >> 2, (u32Value & AHCI_PORT_IE_PSE) >> 1,
+ (u32Value & AHCI_PORT_IE_DHRE)));
+
+ u32Value &= AHCI_PORT_IE_READONLY;
+
+ /* Check if some a interrupt status bit changed*/
+ uint32_t u32IntrStatus = ASMAtomicReadU32(&pAhciPort->regIS);
+
+ int rc = VINF_SUCCESS;
+ if (u32Value & u32IntrStatus)
+ rc = ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN, VINF_IOM_R3_MMIO_WRITE);
+
+ if (rc == VINF_SUCCESS)
+ pAhciPort->regIE = u32Value;
+
+ return rc;
+}
+
+/**
+ * Read from the port interrupt status register.
+ */
+static int PortIntrSts_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regIS=%#010x\n", __FUNCTION__, pAhciPort->regIS));
+ ahciLog(("%s: CPDS=%d TFES=%d HBFS=%d HBDS=%d IFS=%d INFS=%d OFS=%d IPMS=%d PRCS=%d DIS=%d PCS=%d DPS=%d UFS=%d SDBS=%d DSS=%d PSS=%d DHRS=%d\n",
+ __FUNCTION__, (pAhciPort->regIS & AHCI_PORT_IS_CPDS) >> 31, (pAhciPort->regIS & AHCI_PORT_IS_TFES) >> 30,
+ (pAhciPort->regIS & AHCI_PORT_IS_HBFS) >> 29, (pAhciPort->regIS & AHCI_PORT_IS_HBDS) >> 28,
+ (pAhciPort->regIS & AHCI_PORT_IS_IFS) >> 27, (pAhciPort->regIS & AHCI_PORT_IS_INFS) >> 26,
+ (pAhciPort->regIS & AHCI_PORT_IS_OFS) >> 24, (pAhciPort->regIS & AHCI_PORT_IS_IPMS) >> 23,
+ (pAhciPort->regIS & AHCI_PORT_IS_PRCS) >> 22, (pAhciPort->regIS & AHCI_PORT_IS_DIS) >> 7,
+ (pAhciPort->regIS & AHCI_PORT_IS_PCS) >> 6, (pAhciPort->regIS & AHCI_PORT_IS_DPS) >> 5,
+ (pAhciPort->regIS & AHCI_PORT_IS_UFS) >> 4, (pAhciPort->regIS & AHCI_PORT_IS_SDBS) >> 3,
+ (pAhciPort->regIS & AHCI_PORT_IS_DSS) >> 2, (pAhciPort->regIS & AHCI_PORT_IS_PSS) >> 1,
+ (pAhciPort->regIS & AHCI_PORT_IS_DHRS)));
+ *pu32Value = pAhciPort->regIS;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port interrupt status register.
+ */
+static int PortIntrSts_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ ASMAtomicAndU32(&pAhciPort->regIS, ~(u32Value & AHCI_PORT_IS_READONLY));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port FIS base address upper 32bit register.
+ */
+static int PortFisAddrUp_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regFBU=%#010x\n", __FUNCTION__, pAhciPort->regFBU));
+ *pu32Value = pAhciPort->regFBU;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port FIS base address upper 32bit register.
+ */
+static int PortFisAddrUp_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ pAhciPort->regFBU = u32Value;
+ pAhciPort->GCPhysAddrFb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regFBU, pAhciPort->regFB);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port FIS base address register.
+ */
+static int PortFisAddr_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regFB=%#010x\n", __FUNCTION__, pAhciPort->regFB));
+ *pu32Value = pAhciPort->regFB;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port FIS base address register.
+ */
+static int PortFisAddr_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ Assert(!(u32Value & ~AHCI_PORT_FB_RESERVED));
+
+ pAhciPort->regFB = (u32Value & AHCI_PORT_FB_RESERVED);
+ pAhciPort->GCPhysAddrFb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regFBU, pAhciPort->regFB);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command list base address upper 32bit register.
+ */
+static int PortCmdLstAddrUp_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ pAhciPort->regCLBU = u32Value;
+ pAhciPort->GCPhysAddrClb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regCLBU, pAhciPort->regCLB);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command list base address upper 32bit register.
+ */
+static int PortCmdLstAddrUp_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regCLBU=%#010x\n", __FUNCTION__, pAhciPort->regCLBU));
+ *pu32Value = pAhciPort->regCLBU;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the port command list base address register.
+ */
+static int PortCmdLstAddr_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: read regCLB=%#010x\n", __FUNCTION__, pAhciPort->regCLB));
+ *pu32Value = pAhciPort->regCLB;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the port command list base address register.
+ */
+static int PortCmdLstAddr_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF2(pAhci, iReg);
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ Assert(!(u32Value & ~AHCI_PORT_CLB_RESERVED));
+
+ pAhciPort->regCLB = (u32Value & AHCI_PORT_CLB_RESERVED);
+ pAhciPort->GCPhysAddrClb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regCLBU, pAhciPort->regCLB);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global Version register.
+ */
+static int HbaVersion_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaVs=%#010x\n", __FUNCTION__, pAhci->regHbaVs));
+ *pu32Value = pAhci->regHbaVs;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global Ports implemented register.
+ */
+static int HbaPortsImplemented_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaPi=%#010x\n", __FUNCTION__, pAhci->regHbaPi));
+ *pu32Value = pAhci->regHbaPi;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global interrupt status register.
+ */
+static int HbaInterruptStatus_w(PAHCI pAhci, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ int rc = PDMCritSectEnter(&pAhci->lock, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ pAhci->regHbaIs &= ~(u32Value);
+
+ /*
+ * Update interrupt status register and check for ports who
+ * set the interrupt inbetween.
+ */
+ bool fClear = true;
+ pAhci->regHbaIs |= ASMAtomicXchgU32(&pAhci->u32PortsInterrupted, 0);
+ if (!pAhci->regHbaIs)
+ {
+ unsigned i = 0;
+
+ /* Check if the cleared ports have a interrupt status bit set. */
+ while ((u32Value > 0) && (i < AHCI_MAX_NR_PORTS_IMPL))
+ {
+ if (u32Value & 0x01)
+ {
+ PAHCIPort pAhciPort = &pAhci->ahciPort[i];
+
+ if (pAhciPort->regIE & pAhciPort->regIS)
+ {
+ Log(("%s: Interrupt status of port %u set -> Set interrupt again\n", __FUNCTION__, i));
+ ASMAtomicOrU32(&pAhci->u32PortsInterrupted, 1 << i);
+ fClear = false;
+ break;
+ }
+ }
+ u32Value >>= 1;
+ i++;
+ }
+ }
+ else
+ fClear = false;
+
+ if (fClear)
+ ahciHbaClearInterrupt(pAhci);
+ else
+ {
+ Log(("%s: Not clearing interrupt: u32PortsInterrupted=%#010x\n", __FUNCTION__, pAhci->u32PortsInterrupted));
+ /*
+ * We need to set the interrupt again because the I/O APIC does not set it again even if the
+ * line is still high.
+ * We need to clear it first because the PCI bus only calls the interrupt controller if the state changes.
+ */
+ PDMDevHlpPCISetIrq(pAhci->CTX_SUFF(pDevIns), 0, 0);
+ PDMDevHlpPCISetIrq(pAhci->CTX_SUFF(pDevIns), 0, 1);
+ }
+
+ PDMCritSectLeave(&pAhci->lock);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global interrupt status register.
+ */
+static int HbaInterruptStatus_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+
+ int rc = PDMCritSectEnter(&pAhci->lock, VINF_IOM_R3_MMIO_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint32_t u32PortsInterrupted = ASMAtomicXchgU32(&pAhci->u32PortsInterrupted, 0);
+
+ PDMCritSectLeave(&pAhci->lock);
+ Log(("%s: read regHbaIs=%#010x u32PortsInterrupted=%#010x\n", __FUNCTION__, pAhci->regHbaIs, u32PortsInterrupted));
+
+ pAhci->regHbaIs |= u32PortsInterrupted;
+
+#ifdef LOG_ENABLED
+ Log(("%s:", __FUNCTION__));
+ unsigned i;
+ for (i = 0; i < pAhci->cPortsImpl; i++)
+ {
+ if ((pAhci->regHbaIs >> i) & 0x01)
+ Log((" P%d", i));
+ }
+ Log(("\n"));
+#endif
+
+ *pu32Value = pAhci->regHbaIs;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global control register.
+ */
+static int HbaControl_w(PAHCI pAhci, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: write u32Value=%#010x\n"
+ "%s: AE=%d IE=%d HR=%d\n",
+ __FUNCTION__, u32Value,
+ __FUNCTION__, (u32Value & AHCI_HBA_CTRL_AE) >> 31, (u32Value & AHCI_HBA_CTRL_IE) >> 1,
+ (u32Value & AHCI_HBA_CTRL_HR)));
+
+#ifndef IN_RING3
+ RT_NOREF2(pAhci, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ /*
+ * Increase the active thread counter because we might set the host controller
+ * reset bit.
+ */
+ ASMAtomicIncU32(&pAhci->cThreadsActive);
+ ASMAtomicWriteU32(&pAhci->regHbaCtrl, (u32Value & AHCI_HBA_CTRL_RW_MASK) | AHCI_HBA_CTRL_AE);
+
+ /*
+ * Do the HBA reset if requested and there is no other active thread at the moment,
+ * the work is deferred to the last active thread otherwise.
+ */
+ uint32_t cThreadsActive = ASMAtomicDecU32(&pAhci->cThreadsActive);
+ if ( (u32Value & AHCI_HBA_CTRL_HR)
+ && !cThreadsActive)
+ ahciHBAReset(pAhci);
+
+ return VINF_SUCCESS;
+#endif
+}
+
+/**
+ * Read the global control register.
+ */
+static int HbaControl_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaCtrl=%#010x\n"
+ "%s: AE=%d IE=%d HR=%d\n",
+ __FUNCTION__, pAhci->regHbaCtrl,
+ __FUNCTION__, (pAhci->regHbaCtrl & AHCI_HBA_CTRL_AE) >> 31, (pAhci->regHbaCtrl & AHCI_HBA_CTRL_IE) >> 1,
+ (pAhci->regHbaCtrl & AHCI_HBA_CTRL_HR)));
+ *pu32Value = pAhci->regHbaCtrl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global capabilities register.
+ */
+static int HbaCapabilities_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaCap=%#010x\n"
+ "%s: S64A=%d SNCQ=%d SIS=%d SSS=%d SALP=%d SAL=%d SCLO=%d ISS=%d SNZO=%d SAM=%d SPM=%d PMD=%d SSC=%d PSC=%d NCS=%d NP=%d\n",
+ __FUNCTION__, pAhci->regHbaCap,
+ __FUNCTION__, (pAhci->regHbaCap & AHCI_HBA_CAP_S64A) >> 31, (pAhci->regHbaCap & AHCI_HBA_CAP_SNCQ) >> 30,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SIS) >> 28, (pAhci->regHbaCap & AHCI_HBA_CAP_SSS) >> 27,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SALP) >> 26, (pAhci->regHbaCap & AHCI_HBA_CAP_SAL) >> 25,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SCLO) >> 24, (pAhci->regHbaCap & AHCI_HBA_CAP_ISS) >> 20,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SNZO) >> 19, (pAhci->regHbaCap & AHCI_HBA_CAP_SAM) >> 18,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SPM) >> 17, (pAhci->regHbaCap & AHCI_HBA_CAP_PMD) >> 15,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_SSC) >> 14, (pAhci->regHbaCap & AHCI_HBA_CAP_PSC) >> 13,
+ (pAhci->regHbaCap & AHCI_HBA_CAP_NCS) >> 8, (pAhci->regHbaCap & AHCI_HBA_CAP_NP)));
+ *pu32Value = pAhci->regHbaCap;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing control register.
+ */
+static int HbaCccCtl_w(PAHCI pAhci, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: write u32Value=%#010x\n"
+ "%s: TV=%d CC=%d INT=%d EN=%d\n",
+ __FUNCTION__, u32Value,
+ __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(u32Value), AHCI_HBA_CCC_CTL_CC_GET(u32Value),
+ AHCI_HBA_CCC_CTL_INT_GET(u32Value), (u32Value & AHCI_HBA_CCC_CTL_EN)));
+
+ pAhci->regHbaCccCtl = u32Value;
+ pAhci->uCccTimeout = AHCI_HBA_CCC_CTL_TV_GET(u32Value);
+ pAhci->uCccPortNr = AHCI_HBA_CCC_CTL_INT_GET(u32Value);
+ pAhci->uCccNr = AHCI_HBA_CCC_CTL_CC_GET(u32Value);
+
+ if (u32Value & AHCI_HBA_CCC_CTL_EN)
+ TMTimerSetMillies(pAhci->CTX_SUFF(pHbaCccTimer), pAhci->uCccTimeout); /* Arm the timer */
+ else
+ TMTimerStop(pAhci->CTX_SUFF(pHbaCccTimer));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing control register.
+ */
+static int HbaCccCtl_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaCccCtl=%#010x\n"
+ "%s: TV=%d CC=%d INT=%d EN=%d\n",
+ __FUNCTION__, pAhci->regHbaCccCtl,
+ __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(pAhci->regHbaCccCtl), AHCI_HBA_CCC_CTL_CC_GET(pAhci->regHbaCccCtl),
+ AHCI_HBA_CCC_CTL_INT_GET(pAhci->regHbaCccCtl), (pAhci->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)));
+ *pu32Value = pAhci->regHbaCccCtl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing ports register.
+ */
+static int HbaCccPorts_w(PAHCI pAhci, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ pAhci->regHbaCccPorts = u32Value;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing ports register.
+ */
+static int HbaCccPorts_r(PAHCI pAhci, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(iReg);
+ Log(("%s: read regHbaCccPorts=%#010x\n", __FUNCTION__, pAhci->regHbaCccPorts));
+
+#ifdef LOG_ENABLED
+ Log(("%s:", __FUNCTION__));
+ unsigned i;
+ for (i = 0; i < pAhci->cPortsImpl; i++)
+ {
+ if ((pAhci->regHbaCccPorts >> i) & 0x01)
+ Log((" P%d", i));
+ }
+ Log(("\n"));
+#endif
+
+ *pu32Value = pAhci->regHbaCccPorts;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid write to global register
+ */
+static int HbaInvalid_w(PAHCI pAhci, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF3(pAhci, iReg, u32Value);
+ Log(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port write.
+ */
+static int PortInvalid_w(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF4(pAhci, pAhciPort, iReg, u32Value);
+ ahciLog(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port read.
+ */
+static int PortInvalid_r(PAHCI pAhci, PAHCIPort pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF4(pAhci, pAhciPort, iReg, pu32Value);
+ ahciLog(("%s: Read denied!!! iReg=%u\n", __FUNCTION__, iReg));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Register descriptor table for global HBA registers
+ */
+static const AHCIOPREG g_aOpRegs[] =
+{
+ {"HbaCapabilites", HbaCapabilities_r, HbaInvalid_w}, /* Readonly */
+ {"HbaControl" , HbaControl_r, HbaControl_w},
+ {"HbaInterruptStatus", HbaInterruptStatus_r, HbaInterruptStatus_w},
+ {"HbaPortsImplemented", HbaPortsImplemented_r, HbaInvalid_w}, /* Readonly */
+ {"HbaVersion", HbaVersion_r, HbaInvalid_w}, /* ReadOnly */
+ {"HbaCccCtl", HbaCccCtl_r, HbaCccCtl_w},
+ {"HbaCccPorts", HbaCccPorts_r, HbaCccPorts_w},
+};
+
+/**
+ * Register descriptor table for port registers
+ */
+static const AHCIPORTOPREG g_aPortOpRegs[] =
+{
+ {"PortCmdLstAddr", PortCmdLstAddr_r, PortCmdLstAddr_w},
+ {"PortCmdLstAddrUp", PortCmdLstAddrUp_r, PortCmdLstAddrUp_w},
+ {"PortFisAddr", PortFisAddr_r, PortFisAddr_w},
+ {"PortFisAddrUp", PortFisAddrUp_r, PortFisAddrUp_w},
+ {"PortIntrSts", PortIntrSts_r, PortIntrSts_w},
+ {"PortIntrEnable", PortIntrEnable_r, PortIntrEnable_w},
+ {"PortCmd", PortCmd_r, PortCmd_w},
+ {"PortReserved1", PortInvalid_r, PortInvalid_w}, /* Not used. */
+ {"PortTaskFileData", PortTaskFileData_r, PortInvalid_w}, /* Readonly */
+ {"PortSignature", PortSignature_r, PortInvalid_w}, /* Readonly */
+ {"PortSStatus", PortSStatus_r, PortInvalid_w}, /* Readonly */
+ {"PortSControl", PortSControl_r, PortSControl_w},
+ {"PortSError", PortSError_r, PortSError_w},
+ {"PortSActive", PortSActive_r, PortSActive_w},
+ {"PortCmdIssue", PortCmdIssue_r, PortCmdIssue_w},
+ {"PortReserved2", PortInvalid_r, PortInvalid_w}, /* Not used. */
+};
+
+#ifdef IN_RING3
+/**
+ * Reset initiated by system software for one port.
+ *
+ * @param pAhciPort The port to reset.
+ */
+static void ahciPortSwReset(PAHCIPort pAhciPort)
+{
+ bool fAllTasksCanceled;
+
+ /* Cancel all tasks first. */
+ fAllTasksCanceled = ahciCancelActiveTasks(pAhciPort);
+ Assert(fAllTasksCanceled);
+
+ Assert(pAhciPort->cTasksActive == 0);
+
+ pAhciPort->regIS = 0;
+ pAhciPort->regIE = 0;
+ pAhciPort->regCMD = AHCI_PORT_CMD_CPD | /* Cold presence detection */
+ AHCI_PORT_CMD_SUD | /* Device has spun up. */
+ AHCI_PORT_CMD_POD; /* Port is powered on. */
+
+ /* Hotplugging supported?. */
+ if (pAhciPort->fHotpluggable)
+ pAhciPort->regCMD |= AHCI_PORT_CMD_HPCP;
+
+ pAhciPort->regTFD = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR;
+ pAhciPort->regSIG = UINT32_MAX;
+ pAhciPort->regSSTS = 0;
+ pAhciPort->regSCTL = 0;
+ pAhciPort->regSERR = 0;
+ pAhciPort->regSACT = 0;
+ pAhciPort->regCI = 0;
+
+ pAhciPort->fResetDevice = false;
+ pAhciPort->fPoweredOn = true;
+ pAhciPort->fSpunUp = true;
+ pAhciPort->cMultSectors = ATA_MAX_MULT_SECTORS;
+ pAhciPort->uATATransferMode = ATA_MODE_UDMA | 6;
+
+ pAhciPort->u32TasksNew = 0;
+ pAhciPort->u32TasksRedo = 0;
+ pAhciPort->u32TasksFinished = 0;
+ pAhciPort->u32QueuedTasksFinished = 0;
+ pAhciPort->u32CurrentCommandSlot = 0;
+
+ if (pAhciPort->pDrvBase)
+ {
+ pAhciPort->regCMD |= AHCI_PORT_CMD_CPS; /* Indicate that there is a device on that port */
+
+ if (pAhciPort->fPoweredOn)
+ {
+ /*
+ * Set states in the Port Signature and SStatus registers.
+ */
+ if (pAhciPort->fATAPI)
+ pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+ else
+ pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+ pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+ (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
+ (0x03 << 0); /* Device detected and communication established. */
+ }
+ }
+}
+
+/**
+ * Hardware reset used for machine power on and reset.
+ *
+ * @param pAhciPort The port to reset.
+ */
+static void ahciPortHwReset(PAHCIPort pAhciPort)
+{
+ /* Reset the address registers. */
+ pAhciPort->regCLB = 0;
+ pAhciPort->regCLBU = 0;
+ pAhciPort->regFB = 0;
+ pAhciPort->regFBU = 0;
+
+ /* Reset calculated addresses. */
+ pAhciPort->GCPhysAddrClb = 0;
+ pAhciPort->GCPhysAddrFb = 0;
+}
+
+/**
+ * Create implemented ports bitmap.
+ *
+ * @returns 32bit bitmask with a bit set for every implemented port.
+ * @param cPorts Number of ports.
+ */
+static uint32_t ahciGetPortsImplemented(unsigned cPorts)
+{
+ uint32_t uPortsImplemented = 0;
+
+ for (unsigned i = 0; i < cPorts; i++)
+ uPortsImplemented |= (1 << i);
+
+ return uPortsImplemented;
+}
+
+/**
+ * Reset the entire HBA.
+ *
+ * @param pThis The HBA state.
+ */
+static void ahciHBAReset(PAHCI pThis)
+{
+ unsigned i;
+ int rc = VINF_SUCCESS;
+
+ LogRel(("AHCI#%u: Reset the HBA\n", pThis->CTX_SUFF(pDevIns)->iInstance));
+
+ /* Stop the CCC timer. */
+ if (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)
+ {
+ rc = TMTimerStop(pThis->CTX_SUFF(pHbaCccTimer));
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("%s: Failed to stop timer!\n", __FUNCTION__));
+ }
+
+ /* Reset every port */
+ for (i = 0; i < pThis->cPortsImpl; i++)
+ {
+ PAHCIPort pAhciPort = &pThis->ahciPort[i];
+
+ pAhciPort->iLUN = i;
+ ahciPortSwReset(pAhciPort);
+ }
+
+ /* Init Global registers */
+ pThis->regHbaCap = AHCI_HBA_CAP_ISS_SHIFT(AHCI_HBA_CAP_ISS_GEN2) |
+ AHCI_HBA_CAP_S64A | /* 64bit addressing supported */
+ AHCI_HBA_CAP_SAM | /* AHCI mode only */
+ AHCI_HBA_CAP_SNCQ | /* Support native command queuing */
+ AHCI_HBA_CAP_SSS | /* Staggered spin up */
+ AHCI_HBA_CAP_CCCS | /* Support command completion coalescing */
+ AHCI_HBA_CAP_NCS_SET(pThis->cCmdSlotsAvail) | /* Number of command slots we support */
+ AHCI_HBA_CAP_NP_SET(pThis->cPortsImpl); /* Number of supported ports */
+ pThis->regHbaCtrl = AHCI_HBA_CTRL_AE;
+ pThis->regHbaPi = ahciGetPortsImplemented(pThis->cPortsImpl);
+ pThis->regHbaVs = AHCI_HBA_VS_MJR | AHCI_HBA_VS_MNR;
+ pThis->regHbaCccCtl = 0;
+ pThis->regHbaCccPorts = 0;
+ pThis->uCccTimeout = 0;
+ pThis->uCccPortNr = 0;
+ pThis->uCccNr = 0;
+
+ /* Clear pending interrupts. */
+ pThis->regHbaIs = 0;
+ pThis->u32PortsInterrupted = 0;
+ ahciHbaClearInterrupt(pThis);
+
+ pThis->f64BitAddr = false;
+ pThis->u32PortsInterrupted = 0;
+ pThis->f8ByteMMIO4BytesWrittenSuccessfully = false;
+ /* Clear the HBA Reset bit */
+ pThis->regHbaCtrl &= ~AHCI_HBA_CTRL_HR;
+}
+#endif
+
+/**
+ * Reads from a AHCI controller register.
+ *
+ * @returns VBox status code.
+ *
+ * @param pAhci The AHCI instance.
+ * @param uReg The register to write.
+ * @param pv Where to store the result.
+ * @param cb Number of bytes read.
+ */
+static int ahciRegisterRead(PAHCI pAhci, uint32_t uReg, void *pv, unsigned cb)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t iReg;
+
+ /*
+ * If the access offset is smaller than AHCI_HBA_GLOBAL_SIZE the guest accesses the global registers.
+ * Otherwise it accesses the registers of a port.
+ */
+ if (uReg < AHCI_HBA_GLOBAL_SIZE)
+ {
+ iReg = uReg >> 2;
+ Log3(("%s: Trying to read from global register %u\n", __FUNCTION__, iReg));
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const AHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnRead(pAhci, iReg, (uint32_t *)pv);
+ }
+ else
+ {
+ Log3(("%s: Trying to read global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
+ *(uint32_t *)pv = 0;
+ }
+ }
+ else
+ {
+ uint32_t iRegOffset;
+ uint32_t iPort;
+
+ /* Calculate accessed port. */
+ uReg -= AHCI_HBA_GLOBAL_SIZE;
+ iPort = uReg / AHCI_PORT_REGISTER_SIZE;
+ iRegOffset = (uReg % AHCI_PORT_REGISTER_SIZE);
+ iReg = iRegOffset >> 2;
+
+ Log3(("%s: Trying to read from port %u and register %u\n", __FUNCTION__, iPort, iReg));
+
+ if (RT_LIKELY( iPort < pAhci->cPortsImpl
+ && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+ {
+ const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+ rc = pPortReg->pfnRead(pAhci, &pAhci->ahciPort[iPort], iReg, (uint32_t *)pv);
+ }
+ else
+ {
+ Log3(("%s: Trying to read port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
+ rc = VINF_IOM_MMIO_UNUSED_00;
+ }
+
+ /*
+ * Windows Vista tries to read one byte from some registers instead of four.
+ * Correct the value according to the read size.
+ */
+ if (RT_SUCCESS(rc) && cb != sizeof(uint32_t))
+ {
+ switch (cb)
+ {
+ case 1:
+ {
+ uint8_t uNewValue;
+ uint8_t *p = (uint8_t *)pv;
+
+ iRegOffset &= 3;
+ Log3(("%s: iRegOffset=%u\n", __FUNCTION__, iRegOffset));
+ uNewValue = p[iRegOffset];
+ /* Clear old value */
+ *(uint32_t *)pv = 0;
+ *(uint8_t *)pv = uNewValue;
+ break;
+ }
+ default:
+ AssertMsgFailed(("%s: unsupported access width cb=%d iPort=%x iRegOffset=%x iReg=%x!!!\n",
+ __FUNCTION__, cb, iPort, iRegOffset, iReg));
+ }
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Writes a value to one of the AHCI controller registers.
+ *
+ * @returns VBox status code.
+ *
+ * @param pAhci The AHCI instance.
+ * @param offReg The offset of the register to write to.
+ * @param u32Value The value to write.
+ */
+static int ahciRegisterWrite(PAHCI pAhci, uint32_t offReg, uint32_t u32Value)
+{
+ int rc;
+ uint32_t iReg;
+
+ /*
+ * If the access offset is smaller than 100h the guest accesses the global registers.
+ * Otherwise it accesses the registers of a port.
+ */
+ if (offReg < AHCI_HBA_GLOBAL_SIZE)
+ {
+ Log3(("Write global HBA register\n"));
+ iReg = offReg >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const AHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnWrite(pAhci, iReg, u32Value);
+ }
+ else
+ {
+ Log3(("%s: Trying to write global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ uint32_t iPort;
+ Log3(("Write Port register\n"));
+ /* Calculate accessed port. */
+ offReg -= AHCI_HBA_GLOBAL_SIZE;
+ iPort = offReg / AHCI_PORT_REGISTER_SIZE;
+ iReg = (offReg % AHCI_PORT_REGISTER_SIZE) >> 2;
+ Log3(("%s: Trying to write to port %u and register %u\n", __FUNCTION__, iPort, iReg));
+ if (RT_LIKELY( iPort < pAhci->cPortsImpl
+ && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+ {
+ const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+ rc = pPortReg->pfnWrite(pAhci, &pAhci->ahciPort[iPort], iReg, u32Value);
+ }
+ else
+ {
+ Log3(("%s: Trying to write port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Memory mapped I/O Handler for read operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param GCPhysAddr Physical address (in GC) where the read starts.
+ * @param pv Where to store the result.
+ * @param cb Number of bytes read.
+ */
+PDMBOTHCBDECL(int) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+ Log2(("#%d ahciMMIORead: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr));
+ RT_NOREF1(pvUser);
+
+ int rc = ahciRegisterRead(pAhci, GCPhysAddr - pAhci->MMIOBase, pv, cb);
+
+ Log2(("#%d ahciMMIORead: return pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp rc=%Rrc\n",
+ pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr, rc));
+ return rc;
+}
+
+
+/**
+ * Memory mapped I/O Handler for write operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param GCPhysAddr Physical address (in GC) where the read starts.
+ * @param pv Where to fetch the result.
+ * @param cb Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+ Assert(cb == 4 || cb == 8);
+ Assert(!(GCPhysAddr & (cb - 1)));
+
+ /* Break up 64 bits writes into two dword writes. */
+ /** @todo Eliminate this code once the IOM/EM starts taking care of these
+ * situations. */
+ if (cb == 8)
+ {
+ /*
+ * Only write the first 4 bytes if they weren't already.
+ * It is possible that the last write to the register caused a world
+ * switch and we entered this function again.
+ * Writing the first 4 bytes again could cause indeterminate behavior
+ * which can cause errors in the guest.
+ */
+ int rc = VINF_SUCCESS;
+ if (!pAhci->f8ByteMMIO4BytesWrittenSuccessfully)
+ {
+ rc = ahciMMIOWrite(pDevIns, pvUser, GCPhysAddr, pv, 4);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ pAhci->f8ByteMMIO4BytesWrittenSuccessfully = true;
+ }
+
+ rc = ahciMMIOWrite(pDevIns, pvUser, GCPhysAddr + 4, (uint8_t *)pv + 4, 4);
+ /*
+ * Reset flag again so that the first 4 bytes are written again on the next
+ * 8byte MMIO access.
+ */
+ if (rc == VINF_SUCCESS)
+ pAhci->f8ByteMMIO4BytesWrittenSuccessfully = false;
+
+ return rc;
+ }
+
+ /* Do the access. */
+ Log2(("#%d ahciMMIOWrite: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, GCPhysAddr));
+ return ahciRegisterWrite(pAhci, GCPhysAddr - pAhci->MMIOBase, *(uint32_t const *)pv);
+}
+
+PDMBOTHCBDECL(int) ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ RT_NOREF5(pDevIns, pvUser, Port, u32, cb);
+ AssertMsgFailed(("Should not happen\n"));
+ return VINF_SUCCESS;
+}
+
+PDMBOTHCBDECL(int) ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF5(pDevIns, pvUser, Port, pu32, cb);
+ AssertMsgFailed(("Should not happen\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * I/O port handler for writes to the index/data register pair.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param Port Port address where the write starts.
+ * @param u32 Where to fetch the result.
+ * @param cb Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) ahciIdxDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+ int rc = VINF_SUCCESS;
+ RT_NOREF2(pvUser, cb);
+
+ if (Port - pAhci->IOPortBase >= 8)
+ {
+ unsigned iReg = (Port - pAhci->IOPortBase - 8) / 4;
+
+ Assert(cb == 4);
+
+ if (iReg == 0)
+ {
+ /* Write the index register. */
+ pAhci->regIdx = u32;
+ }
+ else
+ {
+ /** @todo range check? */
+ Assert(iReg == 1);
+ rc = ahciRegisterWrite(pAhci, pAhci->regIdx, u32);
+ if (rc == VINF_IOM_R3_MMIO_WRITE)
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ }
+ }
+ /* else: ignore */
+
+ Log2(("#%d ahciIdxDataWrite: pu32=%p:{%.*Rhxs} cb=%d Port=%#x rc=%Rrc\n",
+ pDevIns->iInstance, &u32, cb, &u32, cb, Port, rc));
+ return rc;
+}
+
+/**
+ * I/O port handler for reads from the index/data register pair.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param Port Port address where the read starts.
+ * @param pu32 Where to fetch the result.
+ * @param cb Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) ahciIdxDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+ int rc = VINF_SUCCESS;
+ RT_NOREF1(pvUser);
+
+ if (Port - pAhci->IOPortBase >= 8)
+ {
+ unsigned iReg = (Port - pAhci->IOPortBase - 8) / 4;
+
+ Assert(cb == 4);
+
+ if (iReg == 0)
+ {
+ /* Read the index register. */
+ *pu32 = pAhci->regIdx;
+ }
+ else
+ {
+ Assert(iReg == 1);
+ /** @todo range check? */
+ rc = ahciRegisterRead(pAhci, pAhci->regIdx, pu32, cb);
+ if (rc == VINF_IOM_R3_MMIO_READ)
+ rc = VINF_IOM_R3_IOPORT_READ;
+ else if (rc == VINF_IOM_MMIO_UNUSED_00)
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+ else
+ *pu32 = UINT32_C(0xffffffff);
+
+ Log2(("#%d ahciIdxDataRead: pu32=%p:{%.*Rhxs} cb=%d Port=%#x rc=%Rrc\n",
+ pDevIns->iInstance, pu32, cb, pu32, cb, Port, rc));
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) ahciR3MMIOMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, enmType);
+ PAHCI pThis = PCIDEV_2_PAHCI(pPciDev);
+
+ Log2(("%s: registering MMIO area at GCPhysAddr=%RGp cb=%RGp\n", __FUNCTION__, GCPhysAddress, cb));
+
+ Assert(enmType == PCI_ADDRESS_SPACE_MEM);
+ Assert(cb >= 4352);
+
+ /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+ /** @todo change this to IOMMMIO_FLAGS_WRITE_ONLY_DWORD once EM/IOM starts
+ * handling 2nd DWORD failures on split accesses correctly. */
+ int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_ONLY_DWORD_QWORD,
+ ahciMMIOWrite, ahciMMIORead, "AHCI");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/, "ahciMMIOWrite", "ahciMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/, "ahciMMIOWrite", "ahciMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->MMIOBase = GCPhysAddress;
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP,
+ * Map the legacy I/O port ranges to make Solaris work with the
+ * controller.}
+ */
+static DECLCALLBACK(int) ahciR3LegacyFakeIORangeMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, enmType);
+ PAHCI pThis = PCIDEV_2_PAHCI(pPciDev);
+ int rc = VINF_SUCCESS;
+
+ Log2(("%s: registering fake I/O area at GCPhysAddr=%RGp cb=%RGp\n", __FUNCTION__, GCPhysAddress, cb));
+
+ Assert(enmType == PCI_ADDRESS_SPACE_IO);
+
+ /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+ rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, cb, NULL,
+ ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL, NULL, "AHCI Fake");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+ "ahciLegacyFakeWrite", "ahciLegacyFakeRead", NULL, NULL, "AHCI Fake");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+ "ahciLegacyFakeWrite", "ahciLegacyFakeRead", NULL, NULL, "AHCI Fake");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP,
+ * Map the BMDMA I/O port range (used for the Index/Data pair register access)}
+ */
+static DECLCALLBACK(int) ahciR3IdxDataIORangeMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, enmType);
+ PAHCI pThis = PCIDEV_2_PAHCI(pPciDev);
+ int rc = VINF_SUCCESS;
+
+ Log2(("%s: registering fake I/O area at GCPhysAddr=%RGp cb=%RGp\n", __FUNCTION__, GCPhysAddress, cb));
+
+ Assert(enmType == PCI_ADDRESS_SPACE_IO);
+
+ /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+ rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, cb, NULL,
+ ahciIdxDataWrite, ahciIdxDataRead, NULL, NULL, "AHCI IDX/DATA");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+ "ahciIdxDataWrite", "ahciIdxDataRead", NULL, NULL, "AHCI IDX/DATA");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, (RTIOPORT)GCPhysAddress, cb, 0,
+ "ahciIdxDataWrite", "ahciIdxDataRead", NULL, NULL, "AHCI IDX/DATA");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->IOPortBase = (RTIOPORT)GCPhysAddress;
+ return rc;
+}
+
+/* -=-=-=-=-=- PAHCI::ILeds -=-=-=-=-=- */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ahciR3Status_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PAHCI pAhci = PDMILEDPORTS_2_PAHCI(pInterface);
+ if (iLUN < AHCI_MAX_NR_PORTS_IMPL)
+ {
+ *ppLed = &pAhci->ahciPort[iLUN].Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ahciR3Status_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PAHCI pThis = PDMIBASE_2_PAHCI(pInterface);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->ILeds);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ahciR3PortQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PAHCIPort pAhciPort = PDMIBASE_2_PAHCIPORT(pInterface);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pAhciPort->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pAhciPort->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pAhciPort->IMediaExPort);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) ahciR3PortQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PAHCIPort pAhciPort = PDMIMEDIAPORT_2_PAHCIPORT(pInterface);
+ PPDMDEVINS pDevIns = pAhciPort->CTX_SUFF(pDevIns);
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pAhciPort->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryScsiInqStrings}
+ */
+static DECLCALLBACK(int) ahciR3PortQueryScsiInqStrings(PPDMIMEDIAPORT pInterface, const char **ppszVendorId,
+ const char **ppszProductId, const char **ppszRevision)
+{
+ PAHCIPort pAhciPort = PDMIMEDIAPORT_2_PAHCIPORT(pInterface);
+
+ if (ppszVendorId)
+ *ppszVendorId = &pAhciPort->szInquiryVendorId[0];
+ if (ppszProductId)
+ *ppszProductId = &pAhciPort->szInquiryProductId[0];
+ if (ppszRevision)
+ *ppszRevision = &pAhciPort->szInquiryRevision[0];
+ return VINF_SUCCESS;
+}
+
+#ifdef LOG_ENABLED
+
+/**
+ * Dump info about the FIS
+ *
+ * @returns nothing
+ * @param pAhciPort The port the command FIS was read from.
+ * @param cmdFis The FIS to print info from.
+ */
+static void ahciDumpFisInfo(PAHCIPort pAhciPort, uint8_t *cmdFis)
+{
+ ahciLog(("%s: *** Begin FIS info dump. ***\n", __FUNCTION__));
+ /* Print FIS type. */
+ switch (cmdFis[AHCI_CMDFIS_TYPE])
+ {
+ case AHCI_CMDFIS_TYPE_H2D:
+ {
+ ahciLog(("%s: Command Fis type: H2D\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d bytes\n", __FUNCTION__, AHCI_CMDFIS_TYPE_H2D_SIZE));
+ if (cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C)
+ ahciLog(("%s: Command register update\n", __FUNCTION__));
+ else
+ ahciLog(("%s: Control register update\n", __FUNCTION__));
+ ahciLog(("%s: CMD=%#04x \"%s\"\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CMD], ATACmdText(cmdFis[AHCI_CMDFIS_CMD])));
+ ahciLog(("%s: FEAT=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FET]));
+ ahciLog(("%s: SECTN=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTN]));
+ ahciLog(("%s: CYLL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLL]));
+ ahciLog(("%s: CYLH=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLH]));
+ ahciLog(("%s: HEAD=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_HEAD]));
+
+ ahciLog(("%s: SECTNEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTNEXP]));
+ ahciLog(("%s: CYLLEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLLEXP]));
+ ahciLog(("%s: CYLHEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLHEXP]));
+ ahciLog(("%s: FETEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FETEXP]));
+
+ ahciLog(("%s: SECTC=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTC]));
+ ahciLog(("%s: SECTCEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTCEXP]));
+ ahciLog(("%s: CTL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CTL]));
+ if (cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST)
+ ahciLog(("%s: Reset bit is set\n", __FUNCTION__));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_D2H:
+ {
+ ahciLog(("%s: Command Fis type D2H\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_D2H_SIZE));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_SETDEVBITS:
+ {
+ ahciLog(("%s: Command Fis type Set Device Bits\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_DMAACTD2H:
+ {
+ ahciLog(("%s: Command Fis type DMA Activate H2D\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_DMASETUP:
+ {
+ ahciLog(("%s: Command Fis type DMA Setup\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMASETUP_SIZE));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_PIOSETUP:
+ {
+ ahciLog(("%s: Command Fis type PIO Setup\n", __FUNCTION__));
+ ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_PIOSETUP_SIZE));
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_DATA:
+ {
+ ahciLog(("%s: Command Fis type Data\n", __FUNCTION__));
+ break;
+ }
+ default:
+ ahciLog(("%s: ERROR Unknown command FIS type\n", __FUNCTION__));
+ break;
+ }
+ ahciLog(("%s: *** End FIS info dump. ***\n", __FUNCTION__));
+}
+
+/**
+ * Dump info about the command header
+ *
+ * @returns nothing
+ * @param pAhciPort Pointer to the port the command header was read from.
+ * @param pCmdHdr The command header to print info from.
+ */
+static void ahciDumpCmdHdrInfo(PAHCIPort pAhciPort, CmdHdr *pCmdHdr)
+{
+ ahciLog(("%s: *** Begin command header info dump. ***\n", __FUNCTION__));
+ ahciLog(("%s: Number of Scatter/Gatther List entries: %u\n", __FUNCTION__, AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf)));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_C)
+ ahciLog(("%s: Clear busy upon R_OK\n", __FUNCTION__));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_B)
+ ahciLog(("%s: BIST Fis\n", __FUNCTION__));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_R)
+ ahciLog(("%s: Device Reset Fis\n", __FUNCTION__));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_P)
+ ahciLog(("%s: Command prefetchable\n", __FUNCTION__));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_W)
+ ahciLog(("%s: Device write\n", __FUNCTION__));
+ else
+ ahciLog(("%s: Device read\n", __FUNCTION__));
+ if (pCmdHdr->u32DescInf & AHCI_CMDHDR_A)
+ ahciLog(("%s: ATAPI command\n", __FUNCTION__));
+ else
+ ahciLog(("%s: ATA command\n", __FUNCTION__));
+
+ ahciLog(("%s: Command FIS length %u DW\n", __FUNCTION__, (pCmdHdr->u32DescInf & AHCI_CMDHDR_CFL_MASK)));
+ ahciLog(("%s: *** End command header info dump. ***\n", __FUNCTION__));
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Post the first D2H FIS from the device into guest memory.
+ *
+ * @returns nothing
+ * @param pAhciPort Pointer to the port which "receives" the FIS.
+ */
+static void ahciPostFirstD2HFisIntoMemory(PAHCIPort pAhciPort)
+{
+ uint8_t d2hFis[AHCI_CMDFIS_TYPE_D2H_SIZE];
+
+ pAhciPort->fFirstD2HFisSent = true;
+
+ ahciLog(("%s: Sending First D2H FIS from FIFO\n", __FUNCTION__));
+ memset(&d2hFis[0], 0, sizeof(d2hFis));
+ d2hFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_D2H;
+ d2hFis[AHCI_CMDFIS_ERR] = 0x01;
+
+ d2hFis[AHCI_CMDFIS_STS] = 0x00;
+
+ /* Set the signature based on the device type. */
+ if (pAhciPort->fATAPI)
+ {
+ d2hFis[AHCI_CMDFIS_CYLL] = 0x14;
+ d2hFis[AHCI_CMDFIS_CYLH] = 0xeb;
+ }
+ else
+ {
+ d2hFis[AHCI_CMDFIS_CYLL] = 0x00;
+ d2hFis[AHCI_CMDFIS_CYLH] = 0x00;
+ }
+
+ d2hFis[AHCI_CMDFIS_HEAD] = 0x00;
+ d2hFis[AHCI_CMDFIS_SECTN] = 0x01;
+ d2hFis[AHCI_CMDFIS_SECTC] = 0x01;
+
+ pAhciPort->regTFD = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR;
+ if (!pAhciPort->fATAPI)
+ pAhciPort->regTFD |= ATA_STAT_READY;
+
+ ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis);
+}
+
+/**
+ * Post the FIS in the memory area allocated by the guest and set interrupt if necessary.
+ *
+ * @returns VBox status code
+ * @param pAhciPort The port which "receives" the FIS.
+ * @param uFisType The type of the FIS.
+ * @param pCmdFis Pointer to the FIS which is to be posted into memory.
+ */
+static int ahciPostFisIntoMemory(PAHCIPort pAhciPort, unsigned uFisType, uint8_t *pCmdFis)
+{
+ int rc = VINF_SUCCESS;
+ RTGCPHYS GCPhysAddrRecFis = pAhciPort->GCPhysAddrFb;
+ unsigned cbFis = 0;
+
+ ahciLog(("%s: pAhciPort=%p uFisType=%u pCmdFis=%p\n", __FUNCTION__, pAhciPort, uFisType, pCmdFis));
+
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ {
+ AssertMsg(GCPhysAddrRecFis, ("%s: GCPhysAddrRecFis is 0\n", __FUNCTION__));
+
+ /* Determine the offset and size of the FIS based on uFisType. */
+ switch (uFisType)
+ {
+ case AHCI_CMDFIS_TYPE_D2H:
+ {
+ GCPhysAddrRecFis += AHCI_RECFIS_RFIS_OFFSET;
+ cbFis = AHCI_CMDFIS_TYPE_D2H_SIZE;
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_SETDEVBITS:
+ {
+ GCPhysAddrRecFis += AHCI_RECFIS_SDBFIS_OFFSET;
+ cbFis = AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE;
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_DMASETUP:
+ {
+ GCPhysAddrRecFis += AHCI_RECFIS_DSFIS_OFFSET;
+ cbFis = AHCI_CMDFIS_TYPE_DMASETUP_SIZE;
+ break;
+ }
+ case AHCI_CMDFIS_TYPE_PIOSETUP:
+ {
+ GCPhysAddrRecFis += AHCI_RECFIS_PSFIS_OFFSET;
+ cbFis = AHCI_CMDFIS_TYPE_PIOSETUP_SIZE;
+ break;
+ }
+ default:
+ /*
+ * We should post the unknown FIS into memory too but this never happens because
+ * we know which FIS types we generate. ;)
+ */
+ AssertMsgFailed(("%s: Unknown FIS type!\n", __FUNCTION__));
+ }
+
+ /* Post the FIS into memory. */
+ ahciLog(("%s: PDMDevHlpPCIPhysWrite GCPhysAddrRecFis=%RGp cbFis=%u\n", __FUNCTION__, GCPhysAddrRecFis, cbFis));
+ PDMDevHlpPCIPhysWrite(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrRecFis, pCmdFis, cbFis);
+ }
+
+ return rc;
+}
+
+DECLINLINE(void) ahciReqSetStatus(PAHCIREQ pAhciReq, uint8_t u8Error, uint8_t u8Status)
+{
+ pAhciReq->cmdFis[AHCI_CMDFIS_ERR] = u8Error;
+ pAhciReq->cmdFis[AHCI_CMDFIS_STS] = u8Status;
+}
+
+static void ataPadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+ for (uint32_t i = 0; i < cbSize; i++)
+ {
+ if (*pbSrc)
+ pbDst[i ^ 1] = *pbSrc++;
+ else
+ pbDst[i ^ 1] = ' ';
+ }
+}
+
+static uint32_t ataChecksum(void* ptr, size_t count)
+{
+ uint8_t u8Sum = 0xa5, *p = (uint8_t*)ptr;
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ {
+ u8Sum += *p++;
+ }
+
+ return (uint8_t)-(int32_t)u8Sum;
+}
+
+static int ahciIdentifySS(PAHCIPort pAhciPort, void *pvBuf)
+{
+ uint16_t *p = (uint16_t *)pvBuf;
+ memset(p, 0, 512);
+ p[0] = RT_H2LE_U16(0x0040);
+ p[1] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383));
+ p[3] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads);
+ /* Block size; obsolete, but required for the BIOS. */
+ p[5] = RT_H2LE_U16(512);
+ p[6] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors);
+ ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+ ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataPadString((uint8_t *)(p + 27), pAhciPort->szModelNumber, AHCI_MODEL_NUMBER_LENGTH); /* model */
+#if ATA_MAX_MULT_SECTORS > 1
+ p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+#endif
+ p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+ p[54] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383));
+ p[55] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads);
+ p[56] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors);
+ p[57] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors);
+ p[58] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors >> 16);
+ if (pAhciPort->cMultSectors)
+ p[59] = RT_H2LE_U16(0x100 | pAhciPort->cMultSectors);
+ if (pAhciPort->cTotalSectors <= (1 << 28) - 1)
+ {
+ p[60] = RT_H2LE_U16(pAhciPort->cTotalSectors);
+ p[61] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 16);
+ }
+ else
+ {
+ /* Report maximum number of sectors possible with LBA28 */
+ p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+ p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+ }
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ if ( pAhciPort->fTrimEnabled
+ || pAhciPort->cbSector != 512
+ || pAhciPort->pDrvMedia->pfnIsNonRotational(pAhciPort->pDrvMedia))
+ {
+ p[80] = RT_H2LE_U16(0x1f0); /* support everything up to ATA/ATAPI-8 ACS */
+ p[81] = RT_H2LE_U16(0x28); /* conforms to ATA/ATAPI-8 ACS */
+ }
+ else
+ {
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ }
+ p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management, write cache and look-ahead */
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management, write cache and look-ahead */
+ p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16(0x00);
+ p[100] = RT_H2LE_U16(pAhciPort->cTotalSectors);
+ p[101] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 16);
+ p[102] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 32);
+ p[103] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 48);
+
+ /* valid information, more than one logical sector per physical sector, 2^cLogSectorsPerPhysicalExp logical sectors per physical sector */
+ if (pAhciPort->cLogSectorsPerPhysicalExp)
+ p[106] = RT_H2LE_U16(RT_BIT(14) | RT_BIT(13) | pAhciPort->cLogSectorsPerPhysicalExp);
+
+ if (pAhciPort->cbSector != 512)
+ {
+ uint32_t cSectorSizeInWords = pAhciPort->cbSector / sizeof(uint16_t);
+ /* Enable reporting of logical sector size. */
+ p[106] |= RT_H2LE_U16(RT_BIT(12) | RT_BIT(14));
+ p[117] = RT_H2LE_U16(cSectorSizeInWords);
+ p[118] = RT_H2LE_U16(cSectorSizeInWords >> 16);
+ }
+
+ if (pAhciPort->pDrvMedia->pfnIsNonRotational(pAhciPort->pDrvMedia))
+ p[217] = RT_H2LE_U16(1); /* Non-rotational medium */
+
+ if (pAhciPort->fTrimEnabled) /** @todo Set bit 14 in word 69 too? (Deterministic read after TRIM). */
+ p[169] = RT_H2LE_U16(1); /* DATA SET MANAGEMENT command supported. */
+
+ /* The following are SATA specific */
+ p[75] = RT_H2LE_U16(pAhciPort->CTX_SUFF(pAhci)->cCmdSlotsAvail-1); /* Number of commands we support, 0's based */
+ p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */
+
+ uint32_t uCsum = ataChecksum(p, 510);
+ p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */
+
+ return VINF_SUCCESS;
+}
+
+static int ahciR3AtapiIdentify(PAHCIREQ pAhciReq, PAHCIPort pAhciPort, size_t cbData, size_t *pcbData)
+{
+ uint16_t p[256];
+
+ memset(p, 0, 512);
+ /* Removable CDROM, 50us response, 12 byte packets */
+ p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0);
+ ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataPadString((uint8_t *)(p + 27), pAhciPort->szModelNumber, AHCI_MODEL_NUMBER_LENGTH); /* model */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+ p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+ p[83] = RT_H2LE_U16(1 << 14);
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+ p[86] = RT_H2LE_U16(0);
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((pAhciPort->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+
+ /* The following are SATA specific */
+ p[75] = RT_H2LE_U16(31); /* We support 32 commands */
+ p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */
+
+ /* Copy the buffer in to the scatter gather list. */
+ *pcbData = ahciR3CopyBufferToPrdtl(pAhciPort->CTX_SUFF(pAhci), pAhciReq, (void *)&p[0],
+ RT_MIN(cbData, sizeof(p)), 0 /* cbSkip */);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reset all values after a reset of the attached storage device.
+ *
+ * @returns nothing
+ * @param pAhciPort The port the device is attached to.
+ * @param pAhciReq The state to get the tag number from.
+ */
+static void ahciFinishStorageDeviceReset(PAHCIPort pAhciPort, PAHCIREQ pAhciReq)
+{
+ int rc;
+
+ /* Send a status good D2H FIS. */
+ pAhciPort->fResetDevice = false;
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ ahciPostFirstD2HFisIntoMemory(pAhciPort);
+
+ /* As this is the first D2H FIS after the reset update the signature in the SIG register of the port. */
+ if (pAhciPort->fATAPI)
+ pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+ else
+ pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+ ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciReq->uTag));
+
+ rc = ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+}
+
+/**
+ * Initiates a device reset caused by ATA_DEVICE_RESET (ATAPI only).
+ *
+ * @returns nothing.
+ * @param pAhciPort The device to reset.
+ * @param pAhciReq The task state.
+ */
+static void ahciDeviceReset(PAHCIPort pAhciPort, PAHCIREQ pAhciReq)
+{
+ ASMAtomicWriteBool(&pAhciPort->fResetDevice, true);
+
+ /*
+ * Because this ATAPI only and ATAPI can't have
+ * more than one command active at a time the task counter should be 0
+ * and it is possible to finish the reset now.
+ */
+ Assert(ASMAtomicReadU32(&pAhciPort->cTasksActive) == 0);
+ ahciFinishStorageDeviceReset(pAhciPort, pAhciReq);
+}
+
+/**
+ * Create a PIO setup FIS and post it into the memory area of the guest.
+ *
+ * @returns nothing.
+ * @param pAhciPort The port of the SATA controller.
+ * @param cbTransfer Transfer size of the request.
+ * @param pCmdFis Pointer to the command FIS from the guest.
+ * @param fRead Flag whether this is a read request.
+ * @param fInterrupt If an interrupt should be send to the guest.
+ */
+static void ahciSendPioSetupFis(PAHCIPort pAhciPort, size_t cbTransfer, uint8_t *pCmdFis,
+ bool fRead, bool fInterrupt)
+{
+ uint8_t abPioSetupFis[20];
+ bool fAssertIntr = false;
+ PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+
+ ahciLog(("%s: building PIO setup Fis\n", __FUNCTION__));
+
+ AssertMsg( cbTransfer > 0
+ && cbTransfer <= 65534,
+ ("Can't send PIO setup FIS for requests with 0 bytes to transfer or greater than 65534\n"));
+
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ {
+ memset(&abPioSetupFis[0], 0, sizeof(abPioSetupFis));
+ abPioSetupFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_PIOSETUP;
+ abPioSetupFis[AHCI_CMDFIS_BITS] = (fInterrupt ? AHCI_CMDFIS_I : 0);
+ if (fRead)
+ abPioSetupFis[AHCI_CMDFIS_BITS] |= AHCI_CMDFIS_D;
+ abPioSetupFis[AHCI_CMDFIS_STS] = pCmdFis[AHCI_CMDFIS_STS];
+ abPioSetupFis[AHCI_CMDFIS_ERR] = pCmdFis[AHCI_CMDFIS_ERR];
+ abPioSetupFis[AHCI_CMDFIS_SECTN] = pCmdFis[AHCI_CMDFIS_SECTN];
+ abPioSetupFis[AHCI_CMDFIS_CYLL] = pCmdFis[AHCI_CMDFIS_CYLL];
+ abPioSetupFis[AHCI_CMDFIS_CYLH] = pCmdFis[AHCI_CMDFIS_CYLH];
+ abPioSetupFis[AHCI_CMDFIS_HEAD] = pCmdFis[AHCI_CMDFIS_HEAD];
+ abPioSetupFis[AHCI_CMDFIS_SECTNEXP] = pCmdFis[AHCI_CMDFIS_SECTNEXP];
+ abPioSetupFis[AHCI_CMDFIS_CYLLEXP] = pCmdFis[AHCI_CMDFIS_CYLLEXP];
+ abPioSetupFis[AHCI_CMDFIS_CYLHEXP] = pCmdFis[AHCI_CMDFIS_CYLHEXP];
+ abPioSetupFis[AHCI_CMDFIS_SECTC] = pCmdFis[AHCI_CMDFIS_SECTC];
+ abPioSetupFis[AHCI_CMDFIS_SECTCEXP] = pCmdFis[AHCI_CMDFIS_SECTCEXP];
+
+ /* Set transfer count. */
+ abPioSetupFis[16] = (cbTransfer >> 8) & 0xff;
+ abPioSetupFis[17] = cbTransfer & 0xff;
+
+ /* Update registers. */
+ pAhciPort->regTFD = (pCmdFis[AHCI_CMDFIS_ERR] << 8) | pCmdFis[AHCI_CMDFIS_STS];
+
+ ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_PIOSETUP, abPioSetupFis);
+
+ if (fInterrupt)
+ {
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_PSS);
+ /* Check if we should assert an interrupt */
+ if (pAhciPort->regIE & AHCI_PORT_IE_PSE)
+ fAssertIntr = true;
+ }
+
+ if (fAssertIntr)
+ {
+ int rc = ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+}
+
+/**
+ * Build a D2H FIS and post into the memory area of the guest.
+ *
+ * @returns Nothing
+ * @param pAhciPort The port of the SATA controller.
+ * @param uTag The tag of the request.
+ * @param pCmdFis Pointer to the command FIS from the guest.
+ * @param fInterrupt If an interrupt should be send to the guest.
+ */
+static void ahciSendD2HFis(PAHCIPort pAhciPort, uint32_t uTag, uint8_t *pCmdFis, bool fInterrupt)
+{
+ uint8_t d2hFis[20];
+ bool fAssertIntr = false;
+ PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+
+ ahciLog(("%s: building D2H Fis\n", __FUNCTION__));
+
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ {
+ memset(&d2hFis[0], 0, sizeof(d2hFis));
+ d2hFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_D2H;
+ d2hFis[AHCI_CMDFIS_BITS] = (fInterrupt ? AHCI_CMDFIS_I : 0);
+ d2hFis[AHCI_CMDFIS_STS] = pCmdFis[AHCI_CMDFIS_STS];
+ d2hFis[AHCI_CMDFIS_ERR] = pCmdFis[AHCI_CMDFIS_ERR];
+ d2hFis[AHCI_CMDFIS_SECTN] = pCmdFis[AHCI_CMDFIS_SECTN];
+ d2hFis[AHCI_CMDFIS_CYLL] = pCmdFis[AHCI_CMDFIS_CYLL];
+ d2hFis[AHCI_CMDFIS_CYLH] = pCmdFis[AHCI_CMDFIS_CYLH];
+ d2hFis[AHCI_CMDFIS_HEAD] = pCmdFis[AHCI_CMDFIS_HEAD];
+ d2hFis[AHCI_CMDFIS_SECTNEXP] = pCmdFis[AHCI_CMDFIS_SECTNEXP];
+ d2hFis[AHCI_CMDFIS_CYLLEXP] = pCmdFis[AHCI_CMDFIS_CYLLEXP];
+ d2hFis[AHCI_CMDFIS_CYLHEXP] = pCmdFis[AHCI_CMDFIS_CYLHEXP];
+ d2hFis[AHCI_CMDFIS_SECTC] = pCmdFis[AHCI_CMDFIS_SECTC];
+ d2hFis[AHCI_CMDFIS_SECTCEXP] = pCmdFis[AHCI_CMDFIS_SECTCEXP];
+
+ /* Update registers. */
+ pAhciPort->regTFD = (pCmdFis[AHCI_CMDFIS_ERR] << 8) | pCmdFis[AHCI_CMDFIS_STS];
+
+ ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis);
+
+ if (pCmdFis[AHCI_CMDFIS_STS] & ATA_STAT_ERR)
+ {
+ /* Error bit is set. */
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_TFES);
+ if (pAhciPort->regIE & AHCI_PORT_IE_TFEE)
+ fAssertIntr = true;
+ /*
+ * Don't mark the command slot as completed because the guest
+ * needs it to identify the failed command.
+ */
+ }
+ else if (fInterrupt)
+ {
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS);
+ /* Check if we should assert an interrupt */
+ if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+ fAssertIntr = true;
+
+ /* Mark command as completed. */
+ ASMAtomicOrU32(&pAhciPort->u32TasksFinished, RT_BIT_32(uTag));
+ }
+
+ if (fAssertIntr)
+ {
+ int rc = ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+}
+
+/**
+ * Build a SDB Fis and post it into the memory area of the guest.
+ *
+ * @returns Nothing
+ * @param pAhciPort The port for which the SDB Fis is send.
+ * @param uFinishedTasks Bitmask of finished tasks.
+ * @param fInterrupt If an interrupt should be asserted.
+ */
+static void ahciSendSDBFis(PAHCIPort pAhciPort, uint32_t uFinishedTasks, bool fInterrupt)
+{
+ uint32_t sdbFis[2];
+ bool fAssertIntr = false;
+ PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+ PAHCIREQ pTaskErr = ASMAtomicReadPtrT(&pAhciPort->pTaskErr, PAHCIREQ);
+
+ ahciLog(("%s: Building SDB FIS\n", __FUNCTION__));
+
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ {
+ memset(&sdbFis[0], 0, sizeof(sdbFis));
+ sdbFis[0] = AHCI_CMDFIS_TYPE_SETDEVBITS;
+ sdbFis[0] |= (fInterrupt ? (1 << 14) : 0);
+ if (RT_UNLIKELY(pTaskErr))
+ {
+ sdbFis[0] = pTaskErr->cmdFis[AHCI_CMDFIS_ERR];
+ sdbFis[0] |= (pTaskErr->cmdFis[AHCI_CMDFIS_STS] & 0x77) << 16; /* Some bits are marked as reserved and thus are masked out. */
+
+ /* Update registers. */
+ pAhciPort->regTFD = (pTaskErr->cmdFis[AHCI_CMDFIS_ERR] << 8) | pTaskErr->cmdFis[AHCI_CMDFIS_STS];
+ }
+ else
+ {
+ sdbFis[0] = 0;
+ sdbFis[0] |= (ATA_STAT_READY | ATA_STAT_SEEK) << 16;
+ pAhciPort->regTFD = ATA_STAT_READY | ATA_STAT_SEEK;
+ }
+
+ sdbFis[1] = pAhciPort->u32QueuedTasksFinished | uFinishedTasks;
+
+ ahciPostFisIntoMemory(pAhciPort, AHCI_CMDFIS_TYPE_SETDEVBITS, (uint8_t *)sdbFis);
+
+ if (RT_UNLIKELY(pTaskErr))
+ {
+ /* Error bit is set. */
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_TFES);
+ if (pAhciPort->regIE & AHCI_PORT_IE_TFEE)
+ fAssertIntr = true;
+ }
+
+ if (fInterrupt)
+ {
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_SDBS);
+ /* Check if we should assert an interrupt */
+ if (pAhciPort->regIE & AHCI_PORT_IE_SDBE)
+ fAssertIntr = true;
+ }
+
+ ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, uFinishedTasks);
+
+ if (fAssertIntr)
+ {
+ int rc = ahciHbaSetInterrupt(pAhci, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+}
+
+static uint32_t ahciGetNSectors(uint8_t *pCmdFis, bool fLBA48)
+{
+ /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+ if (fLBA48)
+ {
+ if (!pCmdFis[AHCI_CMDFIS_SECTC] && !pCmdFis[AHCI_CMDFIS_SECTCEXP])
+ return 65536;
+ else
+ return pCmdFis[AHCI_CMDFIS_SECTCEXP] << 8 | pCmdFis[AHCI_CMDFIS_SECTC];
+ }
+ else
+ {
+ if (!pCmdFis[AHCI_CMDFIS_SECTC])
+ return 256;
+ else
+ return pCmdFis[AHCI_CMDFIS_SECTC];
+ }
+}
+
+static uint64_t ahciGetSector(PAHCIPort pAhciPort, uint8_t *pCmdFis, bool fLBA48)
+{
+ uint64_t iLBA;
+ if (pCmdFis[AHCI_CMDFIS_HEAD] & 0x40)
+ {
+ /* any LBA variant */
+ if (fLBA48)
+ {
+ /* LBA48 */
+ iLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) |
+ pCmdFis[AHCI_CMDFIS_SECTN];
+ }
+ else
+ {
+ /* LBA */
+ iLBA = ((pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) << 24) | (pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+ (pCmdFis[AHCI_CMDFIS_CYLL] << 8) | pCmdFis[AHCI_CMDFIS_SECTN];
+ }
+ }
+ else
+ {
+ /* CHS */
+ iLBA = ((pCmdFis[AHCI_CMDFIS_CYLH] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors +
+ (pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) * pAhciPort->PCHSGeometry.cSectors +
+ (pCmdFis[AHCI_CMDFIS_SECTN] - 1);
+ }
+ return iLBA;
+}
+
+static uint64_t ahciGetSectorQueued(uint8_t *pCmdFis)
+{
+ uint64_t uLBA;
+
+ uLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) |
+ ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) |
+ pCmdFis[AHCI_CMDFIS_SECTN];
+
+ return uLBA;
+}
+
+DECLINLINE(uint32_t) ahciGetNSectorsQueued(uint8_t *pCmdFis)
+{
+ if (!pCmdFis[AHCI_CMDFIS_FETEXP] && !pCmdFis[AHCI_CMDFIS_FET])
+ return 65536;
+ else
+ return pCmdFis[AHCI_CMDFIS_FETEXP] << 8 | pCmdFis[AHCI_CMDFIS_FET];
+}
+
+/**
+ * Copy from guest to host memory worker.
+ *
+ * @copydoc AHCIR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) ahciR3CopyBufferFromGuestWorker(PAHCI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc AHCIR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) ahciR3CopyBufferToGuestWorker(PAHCI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Walks the PRDTL list copying data between the guest and host memory buffers.
+ *
+ * @returns Amount of bytes copied.
+ * @param pThis The AHCI controller device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pfnCopyWorker The copy method to apply for each guest buffer.
+ * @param pSgBuf The host S/G buffer.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t ahciR3PrdtlWalk(PAHCI pThis, PAHCIREQ pAhciReq,
+ PAHCIR3MEMCOPYCALLBACK pfnCopyWorker,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl;
+ unsigned cPrdtlEntries = pAhciReq->cPrdtlEntries;
+ size_t cbCopied = 0;
+
+ /*
+ * Add the amount to skip to the host buffer size to avoid a
+ * few conditionals later on.
+ */
+ cbCopy += cbSkip;
+
+ AssertMsgReturn(cPrdtlEntries > 0, ("Copying 0 bytes is not possible\n"), 0);
+
+ do
+ {
+ SGLEntry aPrdtlEntries[32];
+ uint32_t cPrdtlEntriesRead = cPrdtlEntries < RT_ELEMENTS(aPrdtlEntries)
+ ? cPrdtlEntries
+ : RT_ELEMENTS(aPrdtlEntries);
+
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhysPrdtl, &aPrdtlEntries[0],
+ cPrdtlEntriesRead * sizeof(SGLEntry));
+
+ for (uint32_t i = 0; (i < cPrdtlEntriesRead) && cbCopy; i++)
+ {
+ RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aPrdtlEntries[i].u32DBAUp, aPrdtlEntries[i].u32DBA);
+ uint32_t cbThisCopy = (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+ cbThisCopy = (uint32_t)RT_MIN(cbThisCopy, cbCopy);
+
+ /* Copy into SG entry. */
+ pfnCopyWorker(pThis, GCPhysAddrDataBase, pSgBuf, cbThisCopy, &cbSkip);
+
+ cbCopy -= cbThisCopy;
+ cbCopied += cbThisCopy;
+ }
+
+ GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry);
+ cPrdtlEntries -= cPrdtlEntriesRead;
+ } while (cPrdtlEntries && cbCopy);
+
+ if (cbCopied < cbCopy)
+ pAhciReq->fFlags |= AHCI_REQ_OVERFLOW;
+
+ return cbCopied;
+}
+
+/**
+ * Copies a data buffer into the S/G buffer set up by the guest.
+ *
+ * @returns Amount of bytes copied to the PRDTL.
+ * @param pThis The AHCI controller device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pSgBuf The S/G buffer to copy from.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t ahciR3CopySgBufToPrdtl(PAHCI pThis, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return ahciR3PrdtlWalk(pThis, pAhciReq, ahciR3CopyBufferToGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copies the S/G buffer into a data buffer.
+ *
+ * @returns Amount of bytes copied from the PRDTL.
+ * @param pThis The AHCI controller device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pSgBuf The S/G buffer to copy into.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t ahciR3CopySgBufFromPrdtl(PAHCI pThis, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return ahciR3PrdtlWalk(pThis, pAhciReq, ahciR3CopyBufferFromGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copy a simple memory buffer to the guest memory buffer.
+ *
+ * @returns Amount of bytes copied from the PRDTL.
+ * @param pThis The AHCI controller device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pvSrc The buffer to copy from.
+ * @param cbSrc How many bytes to copy.
+ * @param cbSkip How many bytes to skip initially.
+ */
+static size_t ahciR3CopyBufferToPrdtl(PAHCI pThis, PAHCIREQ pAhciReq, const void *pvSrc,
+ size_t cbSrc, size_t cbSkip)
+{
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+ Seg.pvSeg = (void *)pvSrc;
+ Seg.cbSeg = cbSrc;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return ahciR3CopySgBufToPrdtl(pThis, pAhciReq, &SgBuf, cbSkip, cbSrc);
+}
+
+/**
+ * Calculates the size of the guest buffer described by the PRDT.
+ *
+ * @returns VBox status code.
+ * @param pThis The AHCI controller device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pcbPrdt Where to store the size of the guest buffer.
+ */
+static int ahciR3PrdtQuerySize(PAHCI pThis, PAHCIREQ pAhciReq, size_t *pcbPrdt)
+{
+ RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl;
+ unsigned cPrdtlEntries = pAhciReq->cPrdtlEntries;
+ size_t cbPrdt = 0;
+
+ do
+ {
+ SGLEntry aPrdtlEntries[32];
+ uint32_t cPrdtlEntriesRead = cPrdtlEntries < RT_ELEMENTS(aPrdtlEntries)
+ ? cPrdtlEntries
+ : RT_ELEMENTS(aPrdtlEntries);
+
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhysPrdtl, &aPrdtlEntries[0],
+ cPrdtlEntriesRead * sizeof(SGLEntry));
+
+ for (uint32_t i = 0; i < cPrdtlEntriesRead; i++)
+ cbPrdt += (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+ GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry);
+ cPrdtlEntries -= cPrdtlEntriesRead;
+ } while (cPrdtlEntries);
+
+ *pcbPrdt = cbPrdt;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Cancels all active tasks on the port.
+ *
+ * @returns Whether all active tasks were canceled.
+ * @param pAhciPort The AHCI port.
+ */
+static bool ahciCancelActiveTasks(PAHCIPort pAhciPort)
+{
+ if (pAhciPort->pDrvMediaEx)
+ {
+ int rc = pAhciPort->pDrvMediaEx->pfnIoReqCancelAll(pAhciPort->pDrvMediaEx);
+ AssertRC(rc);
+ }
+ return true; /* always true for now because tasks don't use guest memory as the buffer which makes canceling a task impossible. */
+}
+
+/**
+ * Creates the array of ranges to trim.
+ *
+ * @returns VBox status code.
+ * @param pAhciPort AHCI port state.
+ * @param pAhciReq The request handling the TRIM request.
+ * @param idxRangeStart Index of the first range to start copying.
+ * @param paRanges Where to store the ranges.
+ * @param cRanges Number of ranges fitting into the array.
+ * @param pcRanges Where to store the amount of ranges actually copied on success.
+ */
+static int ahciTrimRangesCreate(PAHCIPort pAhciPort, PAHCIREQ pAhciReq, uint32_t idxRangeStart,
+ PRTRANGE paRanges, uint32_t cRanges, uint32_t *pcRanges)
+{
+ SGLEntry aPrdtlEntries[32];
+ uint64_t aRanges[64];
+ uint32_t cPrdtlEntries = pAhciReq->cPrdtlEntries;
+ RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl;
+ PPDMDEVINS pDevIns = pAhciPort->CTX_SUFF(pDevIns);
+ int rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+ uint32_t idxRange = 0;
+
+ LogFlowFunc(("pAhciPort=%#p pAhciReq=%#p\n", pAhciPort, pAhciReq));
+
+ AssertMsgReturn(pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD, ("This is not a trim request\n"), VERR_INVALID_PARAMETER);
+
+ if (!cPrdtlEntries)
+ pAhciReq->fFlags |= AHCI_REQ_OVERFLOW;
+
+ /* Convert the ranges from ATA to our format. */
+ while ( cPrdtlEntries
+ && idxRange < cRanges)
+ {
+ uint32_t cPrdtlEntriesRead = RT_MIN(cPrdtlEntries, RT_ELEMENTS(aPrdtlEntries));
+
+ rc = VINF_SUCCESS;
+ PDMDevHlpPhysRead(pDevIns, GCPhysPrdtl, &aPrdtlEntries[0], cPrdtlEntriesRead * sizeof(SGLEntry));
+
+ for (uint32_t i = 0; i < cPrdtlEntriesRead && idxRange < cRanges; i++)
+ {
+ RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aPrdtlEntries[i].u32DBAUp, aPrdtlEntries[i].u32DBA);
+ uint32_t cbThisCopy = (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+ cbThisCopy = RT_MIN(cbThisCopy, sizeof(aRanges));
+
+ /* Copy into buffer. */
+ PDMDevHlpPhysRead(pDevIns, GCPhysAddrDataBase, aRanges, cbThisCopy);
+
+ for (unsigned idxRangeSrc = 0; idxRangeSrc < RT_ELEMENTS(aRanges) && idxRange < cRanges; idxRangeSrc++)
+ {
+ /* Skip range if told to do so. */
+ if (!idxRangeStart)
+ {
+ aRanges[idxRangeSrc] = RT_H2LE_U64(aRanges[idxRangeSrc]);
+ if (AHCI_RANGE_LENGTH_GET(aRanges[idxRangeSrc]) != 0)
+ {
+ paRanges[idxRange].offStart = (aRanges[idxRangeSrc] & AHCI_RANGE_LBA_MASK) * pAhciPort->cbSector;
+ paRanges[idxRange].cbRange = AHCI_RANGE_LENGTH_GET(aRanges[idxRangeSrc]) * pAhciPort->cbSector;
+ idxRange++;
+ }
+ else
+ break;
+ }
+ else
+ idxRangeStart--;
+ }
+ }
+
+ GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry);
+ cPrdtlEntries -= cPrdtlEntriesRead;
+ }
+
+ *pcRanges = idxRange;
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Allocates a new AHCI request.
+ *
+ * @returns A new AHCI request structure or NULL if out of memory.
+ * @param pAhciPort The AHCI port.
+ * @param uTag The tag to assign.
+ */
+static PAHCIREQ ahciR3ReqAlloc(PAHCIPort pAhciPort, uint32_t uTag)
+{
+ PAHCIREQ pAhciReq = NULL;
+ PDMMEDIAEXIOREQ hIoReq = NULL;
+
+ int rc = pAhciPort->pDrvMediaEx->pfnIoReqAlloc(pAhciPort->pDrvMediaEx, &hIoReq, (void **)&pAhciReq,
+ uTag, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ if (RT_SUCCESS(rc))
+ {
+ pAhciReq->hIoReq = hIoReq;
+ pAhciReq->fMapped = false;
+ }
+ else
+ pAhciReq = NULL;
+ return pAhciReq;
+}
+
+/**
+ * Frees a given AHCI request structure.
+ *
+ * @returns nothing.
+ * @param pAhciPort The AHCI port.
+ * @param pAhciReq The request to free.
+ */
+static void ahciR3ReqFree(PAHCIPort pAhciPort, PAHCIREQ pAhciReq)
+{
+ if ( pAhciReq
+ && !(pAhciReq->fFlags & AHCI_REQ_IS_ON_STACK))
+ {
+ int rc = pAhciPort->pDrvMediaEx->pfnIoReqFree(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq);
+ AssertRC(rc);
+ }
+}
+
+/**
+ * Complete a data transfer task by freeing all occupied resources
+ * and notifying the guest.
+ *
+ * @returns Flag whether the given request was canceled inbetween;
+ *
+ * @param pAhciPort Pointer to the port where to request completed.
+ * @param pAhciReq Pointer to the task which finished.
+ * @param rcReq IPRT status code of the completed request.
+ */
+static bool ahciTransferComplete(PAHCIPort pAhciPort, PAHCIREQ pAhciReq, int rcReq)
+{
+ bool fCanceled = false;
+
+ LogFlowFunc(("pAhciPort=%p pAhciReq=%p rcReq=%d\n",
+ pAhciPort, pAhciReq, rcReq));
+
+ VBOXDD_AHCI_REQ_COMPLETED(pAhciReq, rcReq, pAhciReq->uOffset, pAhciReq->cbTransfer);
+
+ if (pAhciReq->fMapped)
+ PDMDevHlpPhysReleasePageMappingLock(pAhciPort->CTX_SUFF(pAhci)->CTX_SUFF(pDevIns),
+ &pAhciReq->PgLck);
+
+ if (rcReq != VERR_PDM_MEDIAEX_IOREQ_CANCELED)
+ {
+ if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ pAhciPort->Led.Actual.s.fReading = 0;
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ pAhciPort->Led.Actual.s.fWriting = 0;
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ pAhciPort->Led.Actual.s.fWriting = 0;
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_SCSI)
+ {
+ pAhciPort->Led.Actual.s.fWriting = 0;
+ pAhciPort->Led.Actual.s.fReading = 0;
+ }
+
+ if (RT_FAILURE(rcReq))
+ {
+ /* Log the error. */
+ if (pAhciPort->cErrors++ < MAX_LOG_REL_ERRORS)
+ {
+ if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("AHCI#%uP%u: Flush returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN, rcReq));
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("AHCI#%uP%u: Trim returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN, rcReq));
+ else
+ LogRel(("AHCI#%uP%u: %s at offset %llu (%zu bytes left) returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN,
+ pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "Read"
+ : "Write",
+ pAhciReq->uOffset,
+ pAhciReq->cbTransfer, rcReq));
+ }
+
+ ahciReqSetStatus(pAhciReq, ID_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ /*
+ * We have to duplicate the request here as the underlying I/O
+ * request will be freed later.
+ */
+ PAHCIREQ pReqDup = (PAHCIREQ)RTMemDup(pAhciReq, sizeof(AHCIREQ));
+ if ( pReqDup
+ && !ASMAtomicCmpXchgPtr(&pAhciPort->pTaskErr, pReqDup, NULL))
+ RTMemFree(pReqDup);
+ }
+ else
+ {
+ /* Status will be set already for non I/O requests. */
+ if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_SCSI)
+ {
+ if (pAhciReq->u8ScsiSts == SCSI_STATUS_OK)
+ {
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7)
+ | ((pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST) ? ATAPI_INT_REASON_IO : 0)
+ | (!pAhciReq->cbTransfer ? ATAPI_INT_REASON_CD : 0);
+ }
+ else
+ {
+ ahciReqSetStatus(pAhciReq, pAhciPort->abATAPISense[2] << 4, ATA_STAT_READY | ATA_STAT_ERR);
+ pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7) |
+ ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ pAhciReq->cbTransfer = 0;
+ LogFlowFunc(("SCSI request completed with %u status\n", pAhciReq->u8ScsiSts));
+ }
+ }
+ else if (pAhciReq->enmType != PDMMEDIAEXIOREQTYPE_INVALID)
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+
+ /* Write updated command header into memory of the guest. */
+ uint32_t u32PRDBC = 0;
+ if (pAhciReq->enmType != PDMMEDIAEXIOREQTYPE_INVALID)
+ {
+ size_t cbXfer = 0;
+ int rc = pAhciPort->pDrvMediaEx->pfnIoReqQueryXferSize(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq, &cbXfer);
+ AssertRC(rc);
+ u32PRDBC = (uint32_t)RT_MIN(cbXfer, pAhciReq->cbTransfer);
+ }
+ else
+ u32PRDBC = (uint32_t)pAhciReq->cbTransfer;
+
+ PDMDevHlpPCIPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciReq->GCPhysCmdHdrAddr + RT_UOFFSETOF(CmdHdr, u32PRDBC),
+ &u32PRDBC, sizeof(u32PRDBC));
+
+ if (pAhciReq->fFlags & AHCI_REQ_OVERFLOW)
+ {
+ /*
+ * The guest tried to transfer more data than there is space in the buffer.
+ * Terminate task and set the overflow bit.
+ */
+ /* Notify the guest. */
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_OFS);
+ if (pAhciPort->regIE & AHCI_PORT_IE_OFE)
+ ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ }
+ }
+
+ /*
+ * Make a copy of the required data now and free the request. Otherwise the guest
+ * might issue a new request with the same tag and we run into a conflict when allocating
+ * a new request with the same tag later on.
+ */
+ uint32_t fFlags = pAhciReq->fFlags;
+ uint32_t uTag = pAhciReq->uTag;
+ size_t cbTransfer = pAhciReq->cbTransfer;
+ bool fRead = pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ;
+ uint8_t cmdFis[AHCI_CMDFIS_TYPE_H2D_SIZE];
+ memcpy(&cmdFis[0], &pAhciReq->cmdFis[0], sizeof(cmdFis));
+
+ ahciR3ReqFree(pAhciPort, pAhciReq);
+
+ /* Post a PIO setup FIS first if this is a PIO command which transfers data. */
+ if (fFlags & AHCI_REQ_PIO_DATA)
+ ahciSendPioSetupFis(pAhciPort, cbTransfer, &cmdFis[0], fRead, false /* fInterrupt */);
+
+ if (fFlags & AHCI_REQ_CLEAR_SACT)
+ {
+ if (RT_SUCCESS(rcReq) && !ASMAtomicReadPtrT(&pAhciPort->pTaskErr, PAHCIREQ))
+ ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, RT_BIT_32(uTag));
+ }
+
+ if (fFlags & AHCI_REQ_IS_QUEUED)
+ {
+ /*
+ * Always raise an interrupt after task completion; delaying
+ * this (interrupt coalescing) increases latency and has a significant
+ * impact on performance (see @bugref{5071})
+ */
+ ahciSendSDBFis(pAhciPort, 0, true);
+ }
+ else
+ ahciSendD2HFis(pAhciPort, uTag, &cmdFis[0], true);
+ }
+ else
+ {
+ /*
+ * Task was canceled, do the cleanup but DO NOT access the guest memory!
+ * The guest might use it for other things now because it doesn't know about that task anymore.
+ */
+ fCanceled = true;
+
+ /* Leave a log message about the canceled request. */
+ if (pAhciPort->cErrors++ < MAX_LOG_REL_ERRORS)
+ {
+ if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("AHCI#%uP%u: Canceled flush returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN, rcReq));
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("AHCI#%uP%u: Canceled trim returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance,pAhciPort->iLUN, rcReq));
+ else
+ LogRel(("AHCI#%uP%u: Canceled %s at offset %llu (%zu bytes left) returned rc=%Rrc\n",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN,
+ pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "read"
+ : "write",
+ pAhciReq->uOffset,
+ pAhciReq->cbTransfer, rcReq));
+ }
+
+ ahciR3ReqFree(pAhciPort, pAhciReq);
+ }
+
+ /*
+ * Decrement the active task counter as the last step or we might run into a
+ * hang during power off otherwise (see @bugref{7859}).
+ * Before it could happen that we signal PDM that we are done while we still have to
+ * copy the data to the guest but EMT might be busy destroying the driver chains
+ * below us while we have to delegate copying data to EMT instead of doing it
+ * on this thread.
+ */
+ ASMAtomicDecU32(&pAhciPort->cTasksActive);
+
+ if (pAhciPort->cTasksActive == 0 && pAhciPort->pAhciR3->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pAhciPort->pDevInsR3);
+
+ return fCanceled;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) ahciR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ int rc = VINF_SUCCESS;
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+
+ ahciR3CopySgBufToPrdtl(pAhciPort->CTX_SUFF(pAhci), pIoReq, pSgBuf, offDst, cbCopy);
+
+ if (pIoReq->fFlags & AHCI_REQ_OVERFLOW)
+ rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) ahciR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ int rc = VINF_SUCCESS;
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+
+ ahciR3CopySgBufFromPrdtl(pAhciPort->CTX_SUFF(pAhci), pIoReq, pSgBuf, offSrc, cbCopy);
+ if (pIoReq->fFlags & AHCI_REQ_OVERFLOW)
+ rc = VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryBuf}
+ */
+static DECLCALLBACK(int) ahciR3IoReqQueryBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, void **ppvBuf, size_t *pcbBuf)
+{
+ RT_NOREF(hIoReq);
+ int rc = VERR_NOT_SUPPORTED;
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ PAHCI pThis = pAhciPort->CTX_SUFF(pAhci);
+
+ /* Only allow single 4KB page aligned buffers at the moment. */
+ if ( pIoReq->cPrdtlEntries == 1
+ && pIoReq->cbTransfer == _4K)
+ {
+ RTGCPHYS GCPhysPrdt = pIoReq->GCPhysPrdtl;
+ SGLEntry PrdtEntry;
+
+ PDMDevHlpPhysRead(pThis->pDevInsR3, GCPhysPrdt, &PrdtEntry, sizeof(SGLEntry));
+
+ RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(PrdtEntry.u32DBAUp, PrdtEntry.u32DBA);
+ uint32_t cbData = (PrdtEntry.u32DescInf & SGLENTRY_DESCINF_DBC) + 1;
+
+ if ( cbData >= _4K
+ && !(GCPhysAddrDataBase & (_4K - 1)))
+ {
+ rc = PDMDevHlpPhysGCPhys2CCPtr(pThis->pDevInsR3, GCPhysAddrDataBase,
+ 0, ppvBuf, &pIoReq->PgLck);
+ if (RT_SUCCESS(rc))
+ {
+ pIoReq->fMapped = true;
+ *pcbBuf = cbData;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryDiscardRanges}
+ */
+static DECLCALLBACK(int) ahciR3IoReqQueryDiscardRanges(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t idxRangeStart,
+ uint32_t cRanges, PRTRANGE paRanges,
+ uint32_t *pcRanges)
+{
+ RT_NOREF1(hIoReq);
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+
+ return ahciTrimRangesCreate(pAhciPort, pIoReq, idxRangeStart, paRanges, cRanges, pcRanges);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) ahciR3IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF(hIoReq);
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ ahciTransferComplete(pAhciPort, (PAHCIREQ)pvIoReqAlloc, rcReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) ahciR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF2(hIoReq, pvIoReqAlloc);
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ /* Make sure the request is not accounted for so the VM can suspend successfully. */
+ uint32_t cTasksActive = ASMAtomicDecU32(&pAhciPort->cTasksActive);
+ if (!cTasksActive && pAhciPort->pAhciR3->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pAhciPort->pDevInsR3);
+ break;
+ }
+ case PDMMEDIAEXIOREQSTATE_ACTIVE:
+ /* Make sure the request is accounted for so the VM suspends only when the request is complete. */
+ ASMAtomicIncU32(&pAhciPort->cTasksActive);
+ break;
+ default:
+ AssertMsgFailed(("Invalid request state given %u\n", enmState));
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) ahciR3MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ PAHCIPort pAhciPort = RT_FROM_MEMBER(pInterface, AHCIPort, IMediaExPort);
+ PAHCI pThis = pAhciPort->CTX_SUFF(pAhci);
+
+ if (pThis->pMediaNotify)
+ {
+ int rc = VMR3ReqCallNoWait(PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns)), VMCPUID_ANY,
+ (PFNRT)pThis->pMediaNotify->pfnEjected, 2,
+ pThis->pMediaNotify, pAhciPort->iLUN);
+ AssertRC(rc);
+ }
+}
+
+/**
+ * Process an non read/write ATA command.
+ *
+ * @returns The direction of the data transfer
+ * @param pAhciPort The AHCI port of the request.
+ * @param pAhciReq The AHCI request state.
+ * @param pCmdFis Pointer to the command FIS.
+ */
+static PDMMEDIAEXIOREQTYPE ahciProcessCmd(PAHCIPort pAhciPort, PAHCIREQ pAhciReq, uint8_t *pCmdFis)
+{
+ PDMMEDIAEXIOREQTYPE enmType = PDMMEDIAEXIOREQTYPE_INVALID;
+ bool fLBA48 = false;
+
+ AssertMsg(pCmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, ("FIS is not a host to device Fis!!\n"));
+
+ pAhciReq->cbTransfer = 0;
+
+ switch (pCmdFis[AHCI_CMDFIS_CMD])
+ {
+ case ATA_IDENTIFY_DEVICE:
+ {
+ if (pAhciPort->pDrvMedia && !pAhciPort->fATAPI)
+ {
+ uint16_t u16Temp[256];
+
+ /* Fill the buffer. */
+ ahciIdentifySS(pAhciPort, u16Temp);
+
+ /* Copy the buffer. */
+ size_t cbCopied = ahciR3CopyBufferToPrdtl(pAhciPort->CTX_SUFF(pAhci), pAhciReq,
+ &u16Temp[0], sizeof(u16Temp), 0 /* cbSkip */);
+
+ pAhciReq->fFlags |= AHCI_REQ_PIO_DATA;
+ pAhciReq->cbTransfer = cbCopied;
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ }
+ else
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_SEEK | ATA_STAT_ERR);
+ break;
+ }
+ case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+ case ATA_READ_NATIVE_MAX_ADDRESS:
+ break;
+ case ATA_SET_FEATURES:
+ {
+ switch (pCmdFis[AHCI_CMDFIS_FET])
+ {
+ case 0x02: /* write cache enable */
+ case 0xaa: /* read look-ahead enable */
+ case 0x55: /* read look-ahead disable */
+ case 0xcc: /* reverting to power-on defaults enable */
+ case 0x66: /* reverting to power-on defaults disable */
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ break;
+ case 0x82: /* write cache disable */
+ enmType = PDMMEDIAEXIOREQTYPE_FLUSH;
+ break;
+ case 0x03:
+ {
+ /* set transfer mode */
+ Log2(("%s: transfer mode %#04x\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC]));
+ switch (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8)
+ {
+ case 0x00: /* PIO default */
+ case 0x08: /* PIO mode */
+ break;
+ case ATA_MODE_MDMA: /* MDMA mode */
+ pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_MDMA_MODE_MAX);
+ break;
+ case ATA_MODE_UDMA: /* UDMA mode */
+ pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_UDMA_MODE_MAX);
+ break;
+ }
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ break;
+ }
+ default:
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ }
+ break;
+ }
+ case ATA_DEVICE_RESET:
+ {
+ if (!pAhciPort->fATAPI)
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ else
+ {
+ /* Reset the device. */
+ ahciDeviceReset(pAhciPort, pAhciReq);
+ }
+ break;
+ }
+ case ATA_FLUSH_CACHE_EXT:
+ case ATA_FLUSH_CACHE:
+ enmType = PDMMEDIAEXIOREQTYPE_FLUSH;
+ break;
+ case ATA_PACKET:
+ if (!pAhciPort->fATAPI)
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ else
+ enmType = PDMMEDIAEXIOREQTYPE_SCSI;
+ break;
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ if (!pAhciPort->fATAPI)
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ else
+ {
+ size_t cbData;
+ ahciR3AtapiIdentify(pAhciReq, pAhciPort, 512, &cbData);
+
+ pAhciReq->fFlags |= AHCI_REQ_PIO_DATA;
+ pAhciReq->cbTransfer = cbData;
+ pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7)
+ | ((pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST) ? ATAPI_INT_REASON_IO : 0)
+ | (!pAhciReq->cbTransfer ? ATAPI_INT_REASON_CD : 0);
+
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ }
+ break;
+ case ATA_SET_MULTIPLE_MODE:
+ if ( pCmdFis[AHCI_CMDFIS_SECTC] != 0
+ && ( pCmdFis[AHCI_CMDFIS_SECTC] > ATA_MAX_MULT_SECTORS
+ || (pCmdFis[AHCI_CMDFIS_SECTC] & (pCmdFis[AHCI_CMDFIS_SECTC] - 1)) != 0))
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ else
+ {
+ Log2(("%s: set multi sector count to %d\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC]));
+ pAhciPort->cMultSectors = pCmdFis[AHCI_CMDFIS_SECTC];
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ }
+ break;
+ case ATA_STANDBY_IMMEDIATE:
+ break; /* Do nothing. */
+ case ATA_CHECK_POWER_MODE:
+ pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] = 0xff; /* drive active or idle */
+ RT_FALL_THRU();
+ case ATA_INITIALIZE_DEVICE_PARAMETERS:
+ case ATA_IDLE_IMMEDIATE:
+ case ATA_RECALIBRATE:
+ case ATA_NOP:
+ case ATA_READ_VERIFY_SECTORS_EXT:
+ case ATA_READ_VERIFY_SECTORS:
+ case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+ case ATA_SLEEP:
+ ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK);
+ break;
+ case ATA_READ_DMA_EXT:
+ fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_DMA:
+ {
+ pAhciReq->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * pAhciPort->cbSector;
+ pAhciReq->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * pAhciPort->cbSector;
+ enmType = PDMMEDIAEXIOREQTYPE_READ;
+ break;
+ }
+ case ATA_WRITE_DMA_EXT:
+ fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_DMA:
+ {
+ pAhciReq->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * pAhciPort->cbSector;
+ pAhciReq->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * pAhciPort->cbSector;
+ enmType = PDMMEDIAEXIOREQTYPE_WRITE;
+ break;
+ }
+ case ATA_READ_FPDMA_QUEUED:
+ {
+ pAhciReq->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * pAhciPort->cbSector;
+ pAhciReq->uOffset = ahciGetSectorQueued(pCmdFis) * pAhciPort->cbSector;
+ pAhciReq->fFlags |= AHCI_REQ_IS_QUEUED;
+ enmType = PDMMEDIAEXIOREQTYPE_READ;
+ break;
+ }
+ case ATA_WRITE_FPDMA_QUEUED:
+ {
+ pAhciReq->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * pAhciPort->cbSector;
+ pAhciReq->uOffset = ahciGetSectorQueued(pCmdFis) * pAhciPort->cbSector;
+ pAhciReq->fFlags |= AHCI_REQ_IS_QUEUED;
+ enmType = PDMMEDIAEXIOREQTYPE_WRITE;
+ break;
+ }
+ case ATA_READ_LOG_EXT:
+ {
+ size_t cbLogRead = ((pCmdFis[AHCI_CMDFIS_SECTCEXP] << 8) | pCmdFis[AHCI_CMDFIS_SECTC]) * 512;
+ unsigned offLogRead = ((pCmdFis[AHCI_CMDFIS_CYLLEXP] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * 512;
+ unsigned iPage = pCmdFis[AHCI_CMDFIS_SECTN];
+
+ LogFlow(("Trying to read %zu bytes starting at offset %u from page %u\n", cbLogRead, offLogRead, iPage));
+
+ uint8_t aBuf[512];
+
+ memset(aBuf, 0, sizeof(aBuf));
+
+ if (offLogRead + cbLogRead <= sizeof(aBuf))
+ {
+ switch (iPage)
+ {
+ case 0x10:
+ {
+ LogFlow(("Reading error page\n"));
+ PAHCIREQ pTaskErr = ASMAtomicXchgPtrT(&pAhciPort->pTaskErr, NULL, PAHCIREQ);
+ if (pTaskErr)
+ {
+ aBuf[0] = (pTaskErr->fFlags & AHCI_REQ_IS_QUEUED) ? pTaskErr->uTag : (1 << 7);
+ aBuf[2] = pTaskErr->cmdFis[AHCI_CMDFIS_STS];
+ aBuf[3] = pTaskErr->cmdFis[AHCI_CMDFIS_ERR];
+ aBuf[4] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTN];
+ aBuf[5] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLL];
+ aBuf[6] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLH];
+ aBuf[7] = pTaskErr->cmdFis[AHCI_CMDFIS_HEAD];
+ aBuf[8] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTNEXP];
+ aBuf[9] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLLEXP];
+ aBuf[10] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLHEXP];
+ aBuf[12] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTC];
+ aBuf[13] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTCEXP];
+
+ /* Calculate checksum */
+ uint8_t uChkSum = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(aBuf)-1; i++)
+ uChkSum += aBuf[i];
+
+ aBuf[511] = (uint8_t)-(int8_t)uChkSum;
+
+ /* Finally free the error task state structure because it is completely unused now. */
+ RTMemFree(pTaskErr);
+ }
+
+ /*
+ * Reading this log page results in an abort of all outstanding commands
+ * and clearing the SActive register and TaskFile register.
+ *
+ * See SATA2 1.2 spec chapter 4.2.3.4
+ */
+ bool fAbortedAll = ahciCancelActiveTasks(pAhciPort);
+ Assert(fAbortedAll); NOREF(fAbortedAll);
+ ahciSendSDBFis(pAhciPort, 0xffffffff, true);
+
+ break;
+ }
+ }
+
+ /* Copy the buffer. */
+ size_t cbCopied = ahciR3CopyBufferToPrdtl(pAhciPort->CTX_SUFF(pAhci), pAhciReq,
+ &aBuf[offLogRead], cbLogRead, 0 /* cbSkip */);
+
+ pAhciReq->fFlags |= AHCI_REQ_PIO_DATA;
+ pAhciReq->cbTransfer = cbCopied;
+ }
+
+ break;
+ }
+ case ATA_DATA_SET_MANAGEMENT:
+ {
+ if (pAhciPort->fTrimEnabled)
+ {
+ /* Check that the trim bit is set and all other bits are 0. */
+ if ( !(pAhciReq->cmdFis[AHCI_CMDFIS_FET] & UINT16_C(0x01))
+ || (pAhciReq->cmdFis[AHCI_CMDFIS_FET] & ~UINT16_C(0x1)))
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ else
+ enmType = PDMMEDIAEXIOREQTYPE_DISCARD;
+ break;
+ }
+ /* else: fall through and report error to the guest. */
+ }
+ RT_FALL_THRU();
+ /* All not implemented commands go below. */
+ case ATA_SECURITY_FREEZE_LOCK:
+ case ATA_SMART:
+ case ATA_NV_CACHE:
+ case ATA_IDLE:
+ case ATA_TRUSTED_RECEIVE_DMA: /* Windows 8+ */
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ break;
+ default: /* For debugging purposes. */
+ AssertMsgFailed(("Unknown command issued (%#x)\n", pCmdFis[AHCI_CMDFIS_CMD]));
+ ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR);
+ }
+
+ return enmType;
+}
+
+/**
+ * Retrieve a command FIS from guest memory.
+ *
+ * @returns whether the H2D FIS was successfully read from the guest memory.
+ * @param pAhciPort The AHCI port of the request.
+ * @param pAhciReq The state of the actual task.
+ */
+static bool ahciPortTaskGetCommandFis(PAHCIPort pAhciPort, PAHCIREQ pAhciReq)
+{
+ AssertMsgReturn(pAhciPort->GCPhysAddrClb && pAhciPort->GCPhysAddrFb,
+ ("%s: GCPhysAddrClb and/or GCPhysAddrFb are 0\n", __FUNCTION__),
+ false);
+
+ /*
+ * First we are reading the command header pointed to by regCLB.
+ * From this we get the address of the command table which we are reading too.
+ * We can process the Command FIS afterwards.
+ */
+ CmdHdr cmdHdr;
+ pAhciReq->GCPhysCmdHdrAddr = pAhciPort->GCPhysAddrClb + pAhciReq->uTag * sizeof(CmdHdr);
+ LogFlow(("%s: PDMDevHlpPhysRead GCPhysAddrCmdLst=%RGp cbCmdHdr=%u\n", __FUNCTION__,
+ pAhciReq->GCPhysCmdHdrAddr, sizeof(CmdHdr)));
+ PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), pAhciReq->GCPhysCmdHdrAddr, &cmdHdr, sizeof(CmdHdr));
+
+#ifdef LOG_ENABLED
+ /* Print some infos about the command header. */
+ ahciDumpCmdHdrInfo(pAhciPort, &cmdHdr);
+#endif
+
+ RTGCPHYS GCPhysAddrCmdTbl = AHCI_RTGCPHYS_FROM_U32(cmdHdr.u32CmdTblAddrUp, cmdHdr.u32CmdTblAddr);
+
+ AssertMsgReturn((cmdHdr.u32DescInf & AHCI_CMDHDR_CFL_MASK) * sizeof(uint32_t) == AHCI_CMDFIS_TYPE_H2D_SIZE,
+ ("This is not a command FIS!!\n"),
+ false);
+
+ /* Read the command Fis. */
+ LogFlow(("%s: PDMDevHlpPhysRead GCPhysAddrCmdTbl=%RGp cbCmdFis=%u\n", __FUNCTION__, GCPhysAddrCmdTbl, AHCI_CMDFIS_TYPE_H2D_SIZE));
+ PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciReq->cmdFis[0], AHCI_CMDFIS_TYPE_H2D_SIZE);
+
+ AssertMsgReturn(pAhciReq->cmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D,
+ ("This is not a command FIS\n"),
+ false);
+
+ /* Set transfer direction. */
+ pAhciReq->fFlags |= (cmdHdr.u32DescInf & AHCI_CMDHDR_W) ? 0 : AHCI_REQ_XFER_2_HOST;
+
+ /* If this is an ATAPI command read the atapi command. */
+ if (cmdHdr.u32DescInf & AHCI_CMDHDR_A)
+ {
+ GCPhysAddrCmdTbl += AHCI_CMDHDR_ACMD_OFFSET;
+ PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciReq->aATAPICmd[0], ATAPI_PACKET_SIZE);
+ }
+
+ /* We "received" the FIS. Clear the BSY bit in regTFD. */
+ if ((cmdHdr.u32DescInf & AHCI_CMDHDR_C) && (pAhciReq->fFlags & AHCI_REQ_CLEAR_SACT))
+ {
+ /*
+ * We need to send a FIS which clears the busy bit if this is a queued command so that the guest can queue other commands.
+ * but this FIS does not assert an interrupt
+ */
+ ahciSendD2HFis(pAhciPort, pAhciReq->uTag, pAhciReq->cmdFis, false);
+ pAhciPort->regTFD &= ~AHCI_PORT_TFD_BSY;
+ }
+
+ pAhciReq->GCPhysPrdtl = AHCI_RTGCPHYS_FROM_U32(cmdHdr.u32CmdTblAddrUp, cmdHdr.u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
+ pAhciReq->cPrdtlEntries = AHCI_CMDHDR_PRDTL_ENTRIES(cmdHdr.u32DescInf);
+
+#ifdef LOG_ENABLED
+ /* Print some infos about the FIS. */
+ ahciDumpFisInfo(pAhciPort, &pAhciReq->cmdFis[0]);
+
+ /* Print the PRDT */
+ ahciLog(("PRDT address %RGp number of entries %u\n", pAhciReq->GCPhysPrdtl, pAhciReq->cPrdtlEntries));
+ RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl;
+
+ for (unsigned i = 0; i < pAhciReq->cPrdtlEntries; i++)
+ {
+ SGLEntry SGEntry;
+
+ ahciLog(("Entry %u at address %RGp\n", i, GCPhysPrdtl));
+ PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysPrdtl, &SGEntry, sizeof(SGLEntry));
+
+ RTGCPHYS GCPhysDataAddr = AHCI_RTGCPHYS_FROM_U32(SGEntry.u32DBAUp, SGEntry.u32DBA);
+ ahciLog(("GCPhysAddr=%RGp Size=%u\n", GCPhysDataAddr, SGEntry.u32DescInf & SGLENTRY_DESCINF_DBC));
+
+ GCPhysPrdtl += sizeof(SGLEntry);
+ }
+#endif
+
+ return true;
+}
+
+/**
+ * Submits a given request for execution.
+ *
+ * @returns Flag whether the request was canceled inbetween.
+ * @param pAhciPort The port the request is for.
+ * @param pAhciReq The request to submit.
+ * @param enmType The request type.
+ */
+static bool ahciR3ReqSubmit(PAHCIPort pAhciPort, PAHCIREQ pAhciReq, PDMMEDIAEXIOREQTYPE enmType)
+{
+ int rc = VINF_SUCCESS;
+ bool fReqCanceled = false;
+
+ VBOXDD_AHCI_REQ_SUBMIT(pAhciReq, pAhciReq->enmType, pAhciReq->uOffset, pAhciReq->cbTransfer);
+
+ if (enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ rc = pAhciPort->pDrvMediaEx->pfnIoReqFlush(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq);
+ else if (enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ {
+ uint32_t cRangesMax;
+
+ /* The data buffer contains LBA range entries. Each range is 8 bytes big. */
+ if (!pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] && !pAhciReq->cmdFis[AHCI_CMDFIS_SECTCEXP])
+ cRangesMax = 65536 * 512 / 8;
+ else
+ cRangesMax = pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] * 512 / 8;
+
+ pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+ rc = pAhciPort->pDrvMediaEx->pfnIoReqDiscard(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq,
+ cRangesMax);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_READ)
+ {
+ pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1;
+ rc = pAhciPort->pDrvMediaEx->pfnIoReqRead(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq,
+ pAhciReq->uOffset, pAhciReq->cbTransfer);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+ rc = pAhciPort->pDrvMediaEx->pfnIoReqWrite(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq,
+ pAhciReq->uOffset, pAhciReq->cbTransfer);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_SCSI)
+ {
+ size_t cbBuf = 0;
+
+ if (pAhciReq->cPrdtlEntries)
+ rc = ahciR3PrdtQuerySize(pAhciPort->CTX_SUFF(pAhci), pAhciReq, &cbBuf);
+ pAhciReq->cbTransfer = cbBuf;
+ if (RT_SUCCESS(rc))
+ {
+ if (cbBuf && (pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST))
+ pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1;
+ else if (cbBuf)
+ pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+ rc = pAhciPort->pDrvMediaEx->pfnIoReqSendScsiCmd(pAhciPort->pDrvMediaEx, pAhciReq->hIoReq,
+ 0, &pAhciReq->aATAPICmd[0], ATAPI_PACKET_SIZE,
+ PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN, cbBuf,
+ &pAhciPort->abATAPISense[0], sizeof(pAhciPort->abATAPISense),
+ &pAhciReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ }
+ }
+
+ if (rc == VINF_SUCCESS)
+ fReqCanceled = ahciTransferComplete(pAhciPort, pAhciReq, VINF_SUCCESS);
+ else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ fReqCanceled = ahciTransferComplete(pAhciPort, pAhciReq, rc);
+
+ return fReqCanceled;
+}
+
+/**
+ * Prepares the command for execution coping it from guest memory and doing a few
+ * validation checks on it.
+ *
+ * @returns Whether the command was successfully fetched from guest memory and
+ * can be continued.
+ * @param pAhciPort The AHCI port the request is for.
+ * @param pAhciReq Request structure to copy the command to.
+ */
+static bool ahciR3CmdPrepare(PAHCIPort pAhciPort, PAHCIREQ pAhciReq)
+{
+ /* Set current command slot */
+ ASMAtomicWriteU32(&pAhciPort->u32CurrentCommandSlot, pAhciReq->uTag);
+
+ bool fContinue = ahciPortTaskGetCommandFis(pAhciPort, pAhciReq);
+ if (fContinue)
+ {
+ /* Mark the task as processed by the HBA if this is a queued task so that it doesn't occur in the CI register anymore. */
+ if (pAhciPort->regSACT & RT_BIT_32(pAhciReq->uTag))
+ {
+ pAhciReq->fFlags |= AHCI_REQ_CLEAR_SACT;
+ ASMAtomicOrU32(&pAhciPort->u32TasksFinished, RT_BIT_32(pAhciReq->uTag));
+ }
+
+ if (pAhciReq->cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C)
+ {
+ /*
+ * It is possible that the request counter can get one higher than the maximum because
+ * the request counter is decremented after the guest was notified about the completed
+ * request (see @bugref{7859}). If the completing thread is preempted in between the
+ * guest might already issue another request before the request counter is decremented
+ * which would trigger the following assertion incorrectly in the past.
+ */
+ AssertLogRelMsg(ASMAtomicReadU32(&pAhciPort->cTasksActive) <= AHCI_NR_COMMAND_SLOTS,
+ ("AHCI#%uP%u: There are more than %u (+1) requests active",
+ pAhciPort->CTX_SUFF(pDevIns)->iInstance, pAhciPort->iLUN,
+ AHCI_NR_COMMAND_SLOTS));
+ ASMAtomicIncU32(&pAhciPort->cTasksActive);
+ }
+ else
+ {
+ /* If the reset bit is set put the device into reset state. */
+ if (pAhciReq->cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST)
+ {
+ ahciLog(("%s: Setting device into reset state\n", __FUNCTION__));
+ pAhciPort->fResetDevice = true;
+ ahciSendD2HFis(pAhciPort, pAhciReq->uTag, pAhciReq->cmdFis, true);
+ }
+ else if (pAhciPort->fResetDevice) /* The bit is not set and we are in a reset state. */
+ ahciFinishStorageDeviceReset(pAhciPort, pAhciReq);
+ else /* We are not in a reset state update the control registers. */
+ AssertMsgFailed(("%s: Update the control register\n", __FUNCTION__));
+
+ fContinue = false;
+ }
+ }
+ else
+ {
+ /*
+ * Couldn't find anything in either the AHCI or SATA spec which
+ * indicates what should be done if the FIS is not read successfully.
+ * The closest thing is in the state machine, stating that the device
+ * should go into idle state again (SATA spec 1.0 chapter 8.7.1).
+ * Do the same here and ignore any corrupt FIS types, after all
+ * the guest messed up everything and this behavior is undefined.
+ */
+ fContinue = false;
+ }
+
+ return fContinue;
+}
+
+/**
+ * Transmit queue consumer
+ * Queue a new async task.
+ *
+ * @returns Success indicator.
+ * If false the item will not be removed and the flushing will stop.
+ * @param pDevIns The device instance.
+ * @param pItem The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) ahciNotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+ PDEVPORTNOTIFIERQUEUEITEM pNotifierItem = (PDEVPORTNOTIFIERQUEUEITEM)pItem;
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPort pAhciPort = &pThis->ahciPort[pNotifierItem->iPort];
+ int rc = VINF_SUCCESS;
+
+ ahciLog(("%s: Got notification from GC\n", __FUNCTION__));
+ /* Notify the async IO thread. */
+ rc = SUPSemEventSignal(pThis->pSupDrvSession, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+
+ return true;
+}
+
+/* The async IO thread for one port. */
+static DECLCALLBACK(int) ahciAsyncIOLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pDevIns);
+ PAHCIPort pAhciPort = (PAHCIPort)pThread->pvUser;
+ PAHCI pAhci = pAhciPort->CTX_SUFF(pAhci);
+ int rc = VINF_SUCCESS;
+
+ ahciLog(("%s: Port %d entering async IO loop.\n", __FUNCTION__, pAhciPort->iLUN));
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ unsigned idx = 0;
+ uint32_t u32Tasks = 0;
+ uint32_t u32RegHbaCtrl = 0;
+
+ ASMAtomicWriteBool(&pAhciPort->fWrkThreadSleeping, true);
+ u32Tasks = ASMAtomicXchgU32(&pAhciPort->u32TasksNew, 0);
+ if (!u32Tasks)
+ {
+ Assert(ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping));
+ rc = SUPSemEventWaitNoResume(pAhci->pSupDrvSession, pAhciPort->hEvtProcess, RT_INDEFINITE_WAIT);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+ LogFlowFunc(("Woken up with rc=%Rrc\n", rc));
+ u32Tasks = ASMAtomicXchgU32(&pAhciPort->u32TasksNew, 0);
+ }
+
+ ASMAtomicWriteBool(&pAhciPort->fWrkThreadSleeping, false);
+ ASMAtomicIncU32(&pAhci->cThreadsActive);
+
+ /* Check whether the thread should be suspended. */
+ if (pAhci->fSignalIdle)
+ {
+ if (!ASMAtomicDecU32(&pAhci->cThreadsActive))
+ PDMDevHlpAsyncNotificationCompleted(pAhciPort->pDevInsR3);
+ continue;
+ }
+
+ /*
+ * Check whether the global host controller bit is set and go to sleep immediately again
+ * if it is set.
+ */
+ u32RegHbaCtrl = ASMAtomicReadU32(&pAhci->regHbaCtrl);
+ if ( u32RegHbaCtrl & AHCI_HBA_CTRL_HR
+ && !ASMAtomicDecU32(&pAhci->cThreadsActive))
+ {
+ ahciHBAReset(pAhci);
+ if (pAhci->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pAhciPort->pDevInsR3);
+ continue;
+ }
+
+ idx = ASMBitFirstSetU32(u32Tasks);
+ while ( idx
+ && !pAhciPort->fPortReset)
+ {
+ bool fReqCanceled = false;
+
+ /* Decrement to get the slot number. */
+ idx--;
+ ahciLog(("%s: Processing command at slot %d\n", __FUNCTION__, idx));
+
+ PAHCIREQ pAhciReq = ahciR3ReqAlloc(pAhciPort, idx);
+ if (RT_LIKELY(pAhciReq))
+ {
+ pAhciReq->uTag = idx;
+ pAhciReq->fFlags = 0;
+
+ bool fContinue = ahciR3CmdPrepare(pAhciPort, pAhciReq);
+ if (fContinue)
+ {
+ PDMMEDIAEXIOREQTYPE enmType = ahciProcessCmd(pAhciPort, pAhciReq, pAhciReq->cmdFis);
+ pAhciReq->enmType = enmType;
+
+ if (enmType != PDMMEDIAEXIOREQTYPE_INVALID)
+ fReqCanceled = ahciR3ReqSubmit(pAhciPort, pAhciReq, enmType);
+ else
+ fReqCanceled = ahciTransferComplete(pAhciPort, pAhciReq, VINF_SUCCESS);
+ } /* Command */
+ else
+ ahciR3ReqFree(pAhciPort, pAhciReq);
+ }
+ else /* !Request allocated, use on stack variant to signal the error. */
+ {
+ AHCIREQ Req;
+ Req.uTag = idx;
+ Req.fFlags = AHCI_REQ_IS_ON_STACK;
+ Req.fMapped = false;
+ Req.cbTransfer = 0;
+ Req.uOffset = 0;
+ Req.enmType = PDMMEDIAEXIOREQTYPE_INVALID;
+
+ bool fContinue = ahciR3CmdPrepare(pAhciPort, &Req);
+ if (fContinue)
+ fReqCanceled = ahciTransferComplete(pAhciPort, &Req, VERR_NO_MEMORY);
+ }
+
+ /*
+ * Don't process other requests if the last one was canceled,
+ * the others are not valid anymore.
+ */
+ if (fReqCanceled)
+ break;
+
+ u32Tasks &= ~RT_BIT_32(idx); /* Clear task bit. */
+ idx = ASMBitFirstSetU32(u32Tasks);
+ } /* while tasks available */
+
+ /* Check whether a port reset was active. */
+ if ( ASMAtomicReadBool(&pAhciPort->fPortReset)
+ && (pAhciPort->regSCTL & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_NINIT)
+ ahciPortResetFinish(pAhciPort);
+
+ /*
+ * Check whether a host controller reset is pending and execute the reset
+ * if this is the last active thread.
+ */
+ u32RegHbaCtrl = ASMAtomicReadU32(&pAhci->regHbaCtrl);
+ uint32_t cThreadsActive = ASMAtomicDecU32(&pAhci->cThreadsActive);
+ if ( (u32RegHbaCtrl & AHCI_HBA_CTRL_HR)
+ && !cThreadsActive)
+ ahciHBAReset(pAhci);
+
+ if (!cThreadsActive && pAhci->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pAhciPort->pDevInsR3);
+ } /* While running */
+
+ ahciLog(("%s: Port %d async IO thread exiting\n", __FUNCTION__, pAhciPort->iLUN));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unblock the async I/O thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) ahciAsyncIOLoopWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPort pAhciPort = (PAHCIPort)pThread->pvUser;
+ return SUPSemEventSignal(pThis->pSupDrvSession, pAhciPort->hEvtProcess);
+}
+
+/* -=-=-=-=- DBGF -=-=-=-=- */
+
+/**
+ * AHCI status info callback.
+ *
+ * @param pDevIns The device instance.
+ * @param pHlp The output helpers.
+ * @param pszArgs The arguments.
+ */
+static DECLCALLBACK(void) ahciR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ /*
+ * Show info.
+ */
+ pHlp->pfnPrintf(pHlp,
+ "%s#%d: mmio=%RGp ports=%u GC=%RTbool R0=%RTbool\n",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance,
+ pThis->MMIOBase,
+ pThis->cPortsImpl,
+ pThis->fGCEnabled ? true : false,
+ pThis->fR0Enabled ? true : false);
+
+ /*
+ * Show global registers.
+ */
+ pHlp->pfnPrintf(pHlp, "HbaCap=%#x\n", pThis->regHbaCap);
+ pHlp->pfnPrintf(pHlp, "HbaCtrl=%#x\n", pThis->regHbaCtrl);
+ pHlp->pfnPrintf(pHlp, "HbaIs=%#x\n", pThis->regHbaIs);
+ pHlp->pfnPrintf(pHlp, "HbaPi=%#x\n", pThis->regHbaPi);
+ pHlp->pfnPrintf(pHlp, "HbaVs=%#x\n", pThis->regHbaVs);
+ pHlp->pfnPrintf(pHlp, "HbaCccCtl=%#x\n", pThis->regHbaCccCtl);
+ pHlp->pfnPrintf(pHlp, "HbaCccPorts=%#x\n", pThis->regHbaCccPorts);
+ pHlp->pfnPrintf(pHlp, "PortsInterrupted=%#x\n", pThis->u32PortsInterrupted);
+
+ /*
+ * Per port data.
+ */
+ for (unsigned i = 0; i < pThis->cPortsImpl; i++)
+ {
+ PAHCIPort pThisPort = &pThis->ahciPort[i];
+
+ pHlp->pfnPrintf(pHlp, "Port %d: device-attached=%RTbool\n",
+ pThisPort->iLUN, pThisPort->pDrvBase != NULL);
+ pHlp->pfnPrintf(pHlp, "PortClb=%#x\n", pThisPort->regCLB);
+ pHlp->pfnPrintf(pHlp, "PortClbU=%#x\n", pThisPort->regCLBU);
+ pHlp->pfnPrintf(pHlp, "PortFb=%#x\n", pThisPort->regFB);
+ pHlp->pfnPrintf(pHlp, "PortFbU=%#x\n", pThisPort->regFBU);
+ pHlp->pfnPrintf(pHlp, "PortIs=%#x\n", pThisPort->regIS);
+ pHlp->pfnPrintf(pHlp, "PortIe=%#x\n", pThisPort->regIE);
+ pHlp->pfnPrintf(pHlp, "PortCmd=%#x\n", pThisPort->regCMD);
+ pHlp->pfnPrintf(pHlp, "PortTfd=%#x\n", pThisPort->regTFD);
+ pHlp->pfnPrintf(pHlp, "PortSig=%#x\n", pThisPort->regSIG);
+ pHlp->pfnPrintf(pHlp, "PortSSts=%#x\n", pThisPort->regSSTS);
+ pHlp->pfnPrintf(pHlp, "PortSCtl=%#x\n", pThisPort->regSCTL);
+ pHlp->pfnPrintf(pHlp, "PortSErr=%#x\n", pThisPort->regSERR);
+ pHlp->pfnPrintf(pHlp, "PortSAct=%#x\n", pThisPort->regSACT);
+ pHlp->pfnPrintf(pHlp, "PortCi=%#x\n", pThisPort->regCI);
+ pHlp->pfnPrintf(pHlp, "PortPhysClb=%RGp\n", pThisPort->GCPhysAddrClb);
+ pHlp->pfnPrintf(pHlp, "PortPhysFb=%RGp\n", pThisPort->GCPhysAddrFb);
+ pHlp->pfnPrintf(pHlp, "PortActTasksActive=%u\n", pThisPort->cTasksActive);
+ pHlp->pfnPrintf(pHlp, "PortPoweredOn=%RTbool\n", pThisPort->fPoweredOn);
+ pHlp->pfnPrintf(pHlp, "PortSpunUp=%RTbool\n", pThisPort->fSpunUp);
+ pHlp->pfnPrintf(pHlp, "PortFirstD2HFisSent=%RTbool\n", pThisPort->fFirstD2HFisSent);
+ pHlp->pfnPrintf(pHlp, "PortATAPI=%RTbool\n", pThisPort->fATAPI);
+ pHlp->pfnPrintf(pHlp, "PortTasksFinished=%#x\n", pThisPort->u32TasksFinished);
+ pHlp->pfnPrintf(pHlp, "PortQueuedTasksFinished=%#x\n", pThisPort->u32QueuedTasksFinished);
+ pHlp->pfnPrintf(pHlp, "PortTasksNew=%#x\n", pThisPort->u32TasksNew);
+ pHlp->pfnPrintf(pHlp, "\n");
+ }
+}
+
+/* -=-=-=-=- Helper -=-=-=-=- */
+
+/**
+ * Checks if all asynchronous I/O is finished, both AHCI and IDE.
+ *
+ * Used by ahciR3Reset, ahciR3Suspend and ahciR3PowerOff. ahciR3SavePrep makes
+ * use of it in strict builds (which is why it's up here).
+ *
+ * @returns true if quiesced, false if busy.
+ * @param pDevIns The device instance.
+ */
+static bool ahciR3AllAsyncIOIsFinished(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ if (pThis->cThreadsActive)
+ return false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->ahciPort); i++)
+ {
+ PAHCIPort pThisPort = &pThis->ahciPort[i];
+ if (pThisPort->pDrvBase)
+ {
+ if ( (pThisPort->cTasksActive != 0)
+ || (pThisPort->u32TasksNew != 0))
+ return false;
+ }
+ }
+ return true;
+}
+
+/* -=-=-=-=- Saved State -=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEPREP}
+ */
+static DECLCALLBACK(int) ahciR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pDevIns, pSSM);
+ Assert(ahciR3AllAsyncIOIsFinished(pDevIns));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADPREP}
+ */
+static DECLCALLBACK(int) ahciR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pDevIns, pSSM);
+ Assert(ahciR3AllAsyncIOIsFinished(pDevIns));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) ahciR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ /* config. */
+ SSMR3PutU32(pSSM, pThis->cPortsImpl);
+ for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].pDrvBase != NULL);
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].fHotpluggable);
+ SSMR3PutStrZ(pSSM, pThis->ahciPort[i].szSerialNumber);
+ SSMR3PutStrZ(pSSM, pThis->ahciPort[i].szFirmwareRevision);
+ SSMR3PutStrZ(pSSM, pThis->ahciPort[i].szModelNumber);
+ }
+
+ static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszIdeEmuPortNames); i++)
+ {
+ uint32_t iPort;
+ int rc = CFGMR3QueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i);
+ AssertRCReturn(rc, rc);
+ SSMR3PutU32(pSSM, iPort);
+ }
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ahciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ uint32_t i;
+ int rc;
+
+ Assert(!pThis->f8ByteMMIO4BytesWrittenSuccessfully);
+
+ /* The config */
+ rc = ahciR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+ AssertRCReturn(rc, rc);
+
+ /* The main device structure. */
+ SSMR3PutU32(pSSM, pThis->regHbaCap);
+ SSMR3PutU32(pSSM, pThis->regHbaCtrl);
+ SSMR3PutU32(pSSM, pThis->regHbaIs);
+ SSMR3PutU32(pSSM, pThis->regHbaPi);
+ SSMR3PutU32(pSSM, pThis->regHbaVs);
+ SSMR3PutU32(pSSM, pThis->regHbaCccCtl);
+ SSMR3PutU32(pSSM, pThis->regHbaCccPorts);
+ SSMR3PutU8(pSSM, pThis->uCccPortNr);
+ SSMR3PutU64(pSSM, pThis->uCccTimeout);
+ SSMR3PutU32(pSSM, pThis->uCccNr);
+ SSMR3PutU32(pSSM, pThis->uCccCurrentNr);
+ SSMR3PutU32(pSSM, pThis->u32PortsInterrupted);
+ SSMR3PutBool(pSSM, pThis->fReset);
+ SSMR3PutBool(pSSM, pThis->f64BitAddr);
+ SSMR3PutBool(pSSM, pThis->fR0Enabled);
+ SSMR3PutBool(pSSM, pThis->fGCEnabled);
+ SSMR3PutBool(pSSM, pThis->fLegacyPortResetMethod);
+
+ /* Now every port. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ Assert(pThis->ahciPort[i].cTasksActive == 0);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regCLB);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regCLBU);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regFB);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regFBU);
+ SSMR3PutGCPhys(pSSM, pThis->ahciPort[i].GCPhysAddrClb);
+ SSMR3PutGCPhys(pSSM, pThis->ahciPort[i].GCPhysAddrFb);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regIS);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regIE);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regCMD);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regTFD);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regSIG);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regSSTS);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regSCTL);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regSERR);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regSACT);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].regCI);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].PCHSGeometry.cCylinders);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].PCHSGeometry.cHeads);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].PCHSGeometry.cSectors);
+ SSMR3PutU64(pSSM, pThis->ahciPort[i].cTotalSectors);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].cMultSectors);
+ SSMR3PutU8(pSSM, pThis->ahciPort[i].uATATransferMode);
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].fResetDevice);
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].fPoweredOn);
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].fSpunUp);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].u32TasksFinished);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].u32QueuedTasksFinished);
+ SSMR3PutU32(pSSM, pThis->ahciPort[i].u32CurrentCommandSlot);
+
+ /* ATAPI saved state. */
+ SSMR3PutBool(pSSM, pThis->ahciPort[i].fATAPI);
+ SSMR3PutMem(pSSM, &pThis->ahciPort[i].abATAPISense[0], sizeof(pThis->ahciPort[i].abATAPISense));
+ }
+
+ return SSMR3PutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * Loads a saved legacy ATA emulated device state.
+ *
+ * @returns VBox status code.
+ * @param pSSM The handle to the saved state.
+ */
+static int ahciR3LoadLegacyEmulationState(PSSMHANDLE pSSM)
+{
+ int rc;
+ uint32_t u32Version;
+ uint32_t u32;
+ uint32_t u32IOBuffer;
+
+ /* Test for correct version. */
+ rc = SSMR3GetU32(pSSM, &u32Version);
+ AssertRCReturn(rc, rc);
+ LogFlow(("LoadOldSavedStates u32Version = %d\n", u32Version));
+
+ if ( u32Version != ATA_CTL_SAVED_STATE_VERSION
+ && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
+ && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ {
+ AssertMsgFailed(("u32Version=%d\n", u32Version));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ SSMR3Skip(pSSM, 19 + 5 * sizeof(bool) + 8 /* sizeof(BMDMAState) */);
+
+ for (uint32_t j = 0; j < 2; j++)
+ {
+ SSMR3Skip(pSSM, 88 + 5 * sizeof(bool) );
+
+ if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ SSMR3Skip(pSSM, 64);
+ else
+ SSMR3Skip(pSSM, 2);
+ /** @todo triple-check this hack after passthrough is working */
+ SSMR3Skip(pSSM, 1);
+
+ if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ SSMR3Skip(pSSM, 4);
+
+ SSMR3Skip(pSSM, sizeof(PDMLED));
+ SSMR3GetU32(pSSM, &u32IOBuffer);
+ if (u32IOBuffer)
+ SSMR3Skip(pSSM, u32IOBuffer);
+ }
+
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32 != ~0U)
+ {
+ AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+ rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ahciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ uint32_t u32;
+ int rc;
+
+ if ( uVersion > AHCI_SAVED_STATE_VERSION
+ || uVersion < AHCI_SAVED_STATE_VERSION_VBOX_30)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Deal with the priod after removing the saved IDE bits where the saved
+ state version remained unchanged. */
+ if ( uVersion == AHCI_SAVED_STATE_VERSION_IDE_EMULATION
+ && SSMR3HandleRevision(pSSM) >= 79045
+ && SSMR3HandleRevision(pSSM) < 79201)
+ uVersion++;
+
+ /*
+ * Check whether we have to resort to the legacy port reset method to
+ * prevent older BIOS versions from failing after a reset.
+ */
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES)
+ pThis->fLegacyPortResetMethod = true;
+
+ /* Verify config. */
+ if (uVersion > AHCI_SAVED_STATE_VERSION_VBOX_30)
+ {
+ rc = SSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(rc, rc);
+ if (u32 != pThis->cPortsImpl)
+ {
+ LogRel(("AHCI: Config mismatch: cPortsImpl - saved=%u config=%u\n", u32, pThis->cPortsImpl));
+ if ( u32 < pThis->cPortsImpl
+ || u32 > AHCI_MAX_NR_PORTS_IMPL)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: cPortsImpl - saved=%u config=%u"),
+ u32, pThis->cPortsImpl);
+ }
+
+ for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ bool fInUse;
+ rc = SSMR3GetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pThis->ahciPort[i].pDrvBase != NULL))
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS,
+ N_("The %s VM is missing a device on port %u. Please make sure the source and target VMs have compatible storage configurations"),
+ fInUse ? "target" : "source", i );
+
+ if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_HOTPLUG_FLAG)
+ {
+ bool fHotpluggable;
+ rc = SSMR3GetBool(pSSM, &fHotpluggable);
+ AssertRCReturn(rc, rc);
+ if (fHotpluggable != pThis->ahciPort[i].fHotpluggable)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS,
+ N_("AHCI: Port %u config mismatch: Hotplug flag - saved=%RTbool config=%RTbool\n"),
+ i, fHotpluggable, pThis->ahciPort[i].fHotpluggable);
+ }
+ else
+ Assert(pThis->ahciPort[i].fHotpluggable);
+
+ char szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szSerialNumber, sizeof(szSerialNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szSerialNumber, pThis->ahciPort[i].szSerialNumber))
+ LogRel(("AHCI: Port %u config mismatch: Serial number - saved='%s' config='%s'\n",
+ i, szSerialNumber, pThis->ahciPort[i].szSerialNumber));
+
+ char szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szFirmwareRevision, sizeof(szFirmwareRevision));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szFirmwareRevision, pThis->ahciPort[i].szFirmwareRevision))
+ LogRel(("AHCI: Port %u config mismatch: Firmware revision - saved='%s' config='%s'\n",
+ i, szFirmwareRevision, pThis->ahciPort[i].szFirmwareRevision));
+
+ char szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szModelNumber, sizeof(szModelNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szModelNumber, pThis->ahciPort[i].szModelNumber))
+ LogRel(("AHCI: Port %u config mismatch: Model number - saved='%s' config='%s'\n",
+ i, szModelNumber, pThis->ahciPort[i].szModelNumber));
+ }
+
+ static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszIdeEmuPortNames); i++)
+ {
+ uint32_t iPort;
+ rc = CFGMR3QueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i);
+ AssertRCReturn(rc, rc);
+
+ uint32_t iPortSaved;
+ rc = SSMR3GetU32(pSSM, &iPortSaved);
+ AssertRCReturn(rc, rc);
+
+ if (iPortSaved != iPort)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("IDE %s config mismatch: saved=%u config=%u"),
+ s_apszIdeEmuPortNames[i], iPortSaved, iPort);
+ }
+ }
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ /* Restore data. */
+
+ /* The main device structure. */
+ SSMR3GetU32(pSSM, &pThis->regHbaCap);
+ SSMR3GetU32(pSSM, &pThis->regHbaCtrl);
+ SSMR3GetU32(pSSM, &pThis->regHbaIs);
+ SSMR3GetU32(pSSM, &pThis->regHbaPi);
+ SSMR3GetU32(pSSM, &pThis->regHbaVs);
+ SSMR3GetU32(pSSM, &pThis->regHbaCccCtl);
+ SSMR3GetU32(pSSM, &pThis->regHbaCccPorts);
+ SSMR3GetU8(pSSM, &pThis->uCccPortNr);
+ SSMR3GetU64(pSSM, &pThis->uCccTimeout);
+ SSMR3GetU32(pSSM, &pThis->uCccNr);
+ SSMR3GetU32(pSSM, &pThis->uCccCurrentNr);
+
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->u32PortsInterrupted);
+ SSMR3GetBool(pSSM, &pThis->fReset);
+ SSMR3GetBool(pSSM, &pThis->f64BitAddr);
+ SSMR3GetBool(pSSM, &pThis->fR0Enabled);
+ SSMR3GetBool(pSSM, &pThis->fGCEnabled);
+ if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES)
+ SSMR3GetBool(pSSM, &pThis->fLegacyPortResetMethod);
+
+ /* Now every port. */
+ for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ PAHCIPort pAhciPort = &pThis->ahciPort[i];
+
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regCLB);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regCLBU);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regFB);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regFBU);
+ SSMR3GetGCPhys(pSSM, (RTGCPHYS *)&pThis->ahciPort[i].GCPhysAddrClb);
+ SSMR3GetGCPhys(pSSM, (RTGCPHYS *)&pThis->ahciPort[i].GCPhysAddrFb);
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].regIS);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regIE);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regCMD);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regTFD);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regSIG);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regSSTS);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regSCTL);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].regSERR);
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].regSACT);
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].regCI);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].PCHSGeometry.cCylinders);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].PCHSGeometry.cHeads);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].PCHSGeometry.cSectors);
+ SSMR3GetU64(pSSM, &pThis->ahciPort[i].cTotalSectors);
+ SSMR3GetU32(pSSM, &pThis->ahciPort[i].cMultSectors);
+ SSMR3GetU8(pSSM, &pThis->ahciPort[i].uATATransferMode);
+ SSMR3GetBool(pSSM, &pThis->ahciPort[i].fResetDevice);
+
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_VBOX_30)
+ SSMR3Skip(pSSM, AHCI_NR_COMMAND_SLOTS * sizeof(uint8_t)); /* no active data here */
+
+ if (uVersion < AHCI_SAVED_STATE_VERSION_IDE_EMULATION)
+ {
+ /* The old positions in the FIFO, not required. */
+ SSMR3Skip(pSSM, 2*sizeof(uint8_t));
+ }
+ SSMR3GetBool(pSSM, &pThis->ahciPort[i].fPoweredOn);
+ SSMR3GetBool(pSSM, &pThis->ahciPort[i].fSpunUp);
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].u32TasksFinished);
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].u32QueuedTasksFinished);
+
+ if (uVersion >= AHCI_SAVED_STATE_VERSION_IDE_EMULATION)
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->ahciPort[i].u32CurrentCommandSlot);
+
+ if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_ATAPI)
+ {
+ SSMR3GetBool(pSSM, &pThis->ahciPort[i].fATAPI);
+ SSMR3GetMem(pSSM, pThis->ahciPort[i].abATAPISense, sizeof(pThis->ahciPort[i].abATAPISense));
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_PRE_ATAPI_REMOVE)
+ {
+ SSMR3Skip(pSSM, 1); /* cNotifiedMediaChange. */
+ SSMR3Skip(pSSM, 4); /* MediaEventStatus */
+ }
+ }
+ else if (pThis->ahciPort[i].fATAPI)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: atapi - saved=false config=true"));
+
+ /* Check if we have tasks pending. */
+ uint32_t fTasksOutstanding = pAhciPort->regCI & ~pAhciPort->u32TasksFinished;
+ uint32_t fQueuedTasksOutstanding = pAhciPort->regSACT & ~pAhciPort->u32QueuedTasksFinished;
+
+ pAhciPort->u32TasksNew = fTasksOutstanding | fQueuedTasksOutstanding;
+
+ if (pAhciPort->u32TasksNew)
+ {
+ /*
+ * There are tasks pending. The VM was saved after a task failed
+ * because of non-fatal error. Set the redo flag.
+ */
+ pAhciPort->fRedo = true;
+ }
+ }
+
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_IDE_EMULATION)
+ {
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ rc = ahciR3LoadLegacyEmulationState(pSSM);
+ if(RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- device PDM interface -=-=-=-=- */
+
+static DECLCALLBACK(void) ahciR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ uint32_t i;
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ pAhci->pDevInsRC += offDelta;
+ pAhci->pHbaCccTimerRC = TMTimerRCPtr(pAhci->pHbaCccTimerR3);
+ pAhci->pNotifierQueueRC = PDMQueueRCPtr(pAhci->pNotifierQueueR3);
+
+ /* Relocate every port. */
+ for (i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+ {
+ PAHCIPort pAhciPort = &pAhci->ahciPort[i];
+ pAhciPort->pAhciRC += offDelta;
+ pAhciPort->pDevInsRC += offDelta;
+ }
+}
+
+/**
+ * Configure the attached device for a port.
+ *
+ * Used by ahciR3Construct and ahciR3Attach.
+ *
+ * @returns VBox status code
+ * @param pDevIns The device instance data.
+ * @param pAhciPort The port for which the device is to be configured.
+ */
+static int ahciR3ConfigureLUN(PPDMDEVINS pDevIns, PAHCIPort pAhciPort)
+{
+ /* Query the media interface. */
+ pAhciPort->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pAhciPort->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pAhciPort->pDrvMedia),
+ ("AHCI configuration error: LUN#%d misses the basic media interface!\n", pAhciPort->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pAhciPort->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pAhciPort->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pAhciPort->pDrvMediaEx),
+ ("AHCI configuration error: LUN#%d misses the extended media interface!\n", pAhciPort->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Validate type.
+ */
+ PDMMEDIATYPE enmType = pAhciPort->pDrvMedia->pfnGetType(pAhciPort->pDrvMedia);
+ AssertMsgReturn(enmType == PDMMEDIATYPE_HARD_DISK || enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD,
+ ("AHCI configuration error: LUN#%d isn't a disk or cd/dvd. enmType=%u\n", pAhciPort->iLUN, enmType),
+ VERR_PDM_UNSUPPORTED_BLOCK_TYPE);
+
+ int rc = pAhciPort->pDrvMediaEx->pfnIoReqAllocSizeSet(pAhciPort->pDrvMediaEx, sizeof(AHCIREQ));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI configuration error: LUN#%u: Failed to set I/O request size!"),
+ pAhciPort->iLUN);
+
+ uint32_t fFeatures = 0;
+ rc = pAhciPort->pDrvMediaEx->pfnQueryFeatures(pAhciPort->pDrvMediaEx, &fFeatures);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI configuration error: LUN#%u: Failed to query features of device"),
+ pAhciPort->iLUN);
+
+ if (fFeatures & PDMIMEDIAEX_FEATURE_F_DISCARD)
+ pAhciPort->fTrimEnabled = true;
+
+ pAhciPort->fATAPI = (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD)
+ && RT_BOOL(fFeatures & PDMIMEDIAEX_FEATURE_F_RAWSCSICMD);
+ if (pAhciPort->fATAPI)
+ {
+ pAhciPort->PCHSGeometry.cCylinders = 0;
+ pAhciPort->PCHSGeometry.cHeads = 0;
+ pAhciPort->PCHSGeometry.cSectors = 0;
+ LogRel(("AHCI: LUN#%d: CD/DVD\n", pAhciPort->iLUN));
+ }
+ else
+ {
+ pAhciPort->cbSector = pAhciPort->pDrvMedia->pfnGetSectorSize(pAhciPort->pDrvMedia);
+ pAhciPort->cTotalSectors = pAhciPort->pDrvMedia->pfnGetSize(pAhciPort->pDrvMedia) / pAhciPort->cbSector;
+ rc = pAhciPort->pDrvMedia->pfnBiosGetPCHSGeometry(pAhciPort->pDrvMedia, &pAhciPort->PCHSGeometry);
+ if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ {
+ pAhciPort->PCHSGeometry.cCylinders = 0;
+ pAhciPort->PCHSGeometry.cHeads = 16; /*??*/
+ pAhciPort->PCHSGeometry.cSectors = 63; /*??*/
+ }
+ else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+ {
+ pAhciPort->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+ rc = VINF_SUCCESS;
+ }
+ AssertRC(rc);
+
+ if ( pAhciPort->PCHSGeometry.cCylinders == 0
+ || pAhciPort->PCHSGeometry.cHeads == 0
+ || pAhciPort->PCHSGeometry.cSectors == 0)
+ {
+ uint64_t cCylinders = pAhciPort->cTotalSectors / (16 * 63);
+ pAhciPort->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+ pAhciPort->PCHSGeometry.cHeads = 16;
+ pAhciPort->PCHSGeometry.cSectors = 63;
+ /* Set the disk geometry information. Ignore errors. */
+ pAhciPort->pDrvMedia->pfnBiosSetPCHSGeometry(pAhciPort->pDrvMedia, &pAhciPort->PCHSGeometry);
+ rc = VINF_SUCCESS;
+ }
+ LogRel(("AHCI: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n",
+ pAhciPort->iLUN, pAhciPort->PCHSGeometry.cCylinders,
+ pAhciPort->PCHSGeometry.cHeads, pAhciPort->PCHSGeometry.cSectors,
+ pAhciPort->cTotalSectors));
+ if (pAhciPort->fTrimEnabled)
+ LogRel(("AHCI: LUN#%d: Enabled TRIM support\n", pAhciPort->iLUN));
+ }
+ return rc;
+}
+
+/**
+ * Callback employed by ahciR3Suspend and ahciR3PowerOff.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ahciR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns)
+{
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for ahciR3Suspend and ahciR3PowerOff.
+ */
+static void ahciR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncSuspendOrPowerOffDone);
+ else
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->ahciPort); i++)
+ {
+ PAHCIPort pThisPort = &pThis->ahciPort[i];
+ if (pThisPort->pDrvMediaEx)
+ pThisPort->pDrvMediaEx->pfnNotifySuspend(pThisPort->pDrvMediaEx);
+ }
+}
+
+/**
+ * Suspend notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ahciR3Suspend(PPDMDEVINS pDevIns)
+{
+ Log(("ahciR3Suspend\n"));
+ ahciR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * Resume notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ahciR3Resume(PPDMDEVINS pDevIns)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ /*
+ * Check if one of the ports has pending tasks.
+ * Queue a notification item again in this case.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+ {
+ PAHCIPort pAhciPort = &pAhci->ahciPort[i];
+
+ if (pAhciPort->u32TasksRedo)
+ {
+ PDEVPORTNOTIFIERQUEUEITEM pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(pAhci->CTX_SUFF(pNotifierQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+
+ pAhciPort->u32TasksNew |= pAhciPort->u32TasksRedo;
+ pAhciPort->u32TasksRedo = 0;
+
+ Assert(pAhciPort->fRedo);
+ pAhciPort->fRedo = false;
+
+ pItem->iPort = pAhci->ahciPort[i].iLUN;
+ PDMQueueInsert(pAhci->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+ }
+
+ Log(("%s:\n", __FUNCTION__));
+}
+
+/**
+ * Initializes the VPD data of a attached device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pAhciPort The attached device.
+ * @param pszName Name of the port to get the CFGM node.
+ */
+static int ahciR3VpdInit(PPDMDEVINS pDevIns, PAHCIPort pAhciPort, const char *pszName)
+{
+
+ /* Generate a default serial number. */
+ char szSerial[AHCI_SERIAL_NUMBER_LENGTH+1];
+ RTUUID Uuid;
+
+ int rc = VINF_SUCCESS;
+ if (pAhciPort->pDrvMedia)
+ rc = pAhciPort->pDrvMedia->pfnGetUuid(pAhciPort->pDrvMedia, &Uuid);
+ else
+ RTUuidClear(&Uuid);
+
+ if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+ {
+ /* Generate a predictable serial for drives which don't have a UUID. */
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%x-1a2b3c4d",
+ pAhciPort->iLUN);
+ }
+ else
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+ /* Get user config if present using defaults otherwise. */
+ PCFGMNODE pCfgNode = CFGMR3GetChild(pDevIns->pCfg, pszName);
+ rc = CFGMR3QueryStringDef(pCfgNode, "SerialNumber", pAhciPort->szSerialNumber, sizeof(pAhciPort->szSerialNumber),
+ szSerial);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"SerialNumber\" is longer than 20 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"SerialNumber\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "FirmwareRevision", pAhciPort->szFirmwareRevision, sizeof(pAhciPort->szFirmwareRevision),
+ "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"FirmwareRevision\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ModelNumber", pAhciPort->szModelNumber, sizeof(pAhciPort->szModelNumber),
+ pAhciPort->fATAPI ? "VBOX CD-ROM" : "VBOX HARDDISK");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"ModelNumber\" is longer than 40 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"ModelNumber\" as string"));
+ }
+
+ rc = CFGMR3QueryU8Def(pCfgNode, "LogicalSectorsPerPhysical", &pAhciPort->cLogSectorsPerPhysicalExp, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"LogicalSectorsPerPhysical\" as integer"));
+ if (pAhciPort->cLogSectorsPerPhysicalExp >= 16)
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: \"LogicalSectorsPerPhysical\" must be between 0 and 15"));
+
+ /* There are three other identification strings for CD drives used for INQUIRY */
+ if (pAhciPort->fATAPI)
+ {
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIVendorId", pAhciPort->szInquiryVendorId, sizeof(pAhciPort->szInquiryVendorId),
+ "VBOX");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"ATAPIVendorId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"ATAPIVendorId\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIProductId", pAhciPort->szInquiryProductId, sizeof(pAhciPort->szInquiryProductId),
+ "CD-ROM");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"ATAPIProductId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"ATAPIProductId\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIRevision", pAhciPort->szInquiryRevision, sizeof(pAhciPort->szInquiryRevision),
+ "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("AHCI configuration error: \"ATAPIRevision\" is longer than 4 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read \"ATAPIRevision\" as string"));
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Detach notification.
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) ahciR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPort pAhciPort = &pAhci->ahciPort[iLUN];
+ int rc = VINF_SUCCESS;
+
+ Log(("%s:\n", __FUNCTION__));
+
+ AssertMsg(iLUN < pAhci->cPortsImpl, ("iLUN=%u", iLUN));
+ AssertMsgReturnVoid( pAhciPort->fHotpluggable
+ || (fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG),
+ ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN));
+
+
+ if (pAhciPort->pAsyncIOThread)
+ {
+ int rcThread;
+ /* Destroy the thread. */
+ rc = PDMR3ThreadDestroy(pAhciPort->pAsyncIOThread, &rcThread);
+ if (RT_FAILURE(rc) || RT_FAILURE(rcThread))
+ AssertMsgFailed(("%s Failed to destroy async IO thread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread));
+
+ pAhciPort->pAsyncIOThread = NULL;
+ pAhciPort->fWrkThreadSleeping = true;
+ }
+
+ if (!(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG))
+ {
+ /*
+ * Inform the guest about the removed device.
+ */
+ pAhciPort->regSSTS = 0;
+ pAhciPort->regSIG = 0;
+ /*
+ * Clear CR bit too to prevent submission of new commands when CI is written
+ * (AHCI Spec 1.2: 7.4 Interaction of the Command List and Port Change Status).
+ */
+ ASMAtomicAndU32(&pAhciPort->regCMD, ~(AHCI_PORT_CMD_CPS | AHCI_PORT_CMD_CR));
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS);
+ ASMAtomicOrU32(&pAhciPort->regSERR, AHCI_PORT_SERR_X | AHCI_PORT_SERR_N);
+ if ( (pAhciPort->regIE & AHCI_PORT_IE_CPDE)
+ || (pAhciPort->regIE & AHCI_PORT_IE_PCE)
+ || (pAhciPort->regIE & AHCI_PORT_IE_PRCE))
+ ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ }
+
+ /*
+ * Zero some important members.
+ */
+ pAhciPort->pDrvBase = NULL;
+ pAhciPort->pDrvMedia = NULL;
+ pAhciPort->pDrvMediaEx = NULL;
+}
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for one port.
+ * The VM is suspended at this point.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int) ahciR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPort pAhciPort = &pThis->ahciPort[iLUN];
+ int rc;
+
+ Log(("%s:\n", __FUNCTION__));
+
+ /* the usual paranoia */
+ AssertMsg(iLUN < pThis->cPortsImpl, ("iLUN=%u", iLUN));
+ AssertRelease(!pAhciPort->pDrvBase);
+ AssertRelease(!pAhciPort->pDrvMedia);
+ AssertRelease(!pAhciPort->pDrvMediaEx);
+ Assert(pAhciPort->iLUN == iLUN);
+
+ AssertMsgReturn( pAhciPort->fHotpluggable
+ || (fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG),
+ ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ rc = ahciR3ConfigureLUN(pDevIns, pAhciPort);
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pAhciPort->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pAhciPort->pDrvBase = NULL;
+ pAhciPort->pDrvMedia = NULL;
+ }
+ else
+ {
+ char szName[24];
+ RTStrPrintf(szName, sizeof(szName), "Port%d", iLUN);
+
+ rc = SUPSemEventCreate(pThis->pSupDrvSession, &pAhciPort->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to create SUP event semaphore"));
+
+ /* Create the async IO thread. */
+ rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
+ RTTHREADTYPE_IO, szName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Init vendor product data.
+ */
+ if (RT_SUCCESS(rc))
+ rc = ahciR3VpdInit(pDevIns, pAhciPort, szName);
+
+ /* Inform the guest about the added device in case of hotplugging. */
+ if ( RT_SUCCESS(rc)
+ && !(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG))
+ {
+ AssertMsgReturn(pAhciPort->fHotpluggable,
+ ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN),
+ VERR_NOT_SUPPORTED);
+
+ /*
+ * Initialize registers
+ */
+ ASMAtomicOrU32(&pAhciPort->regCMD, AHCI_PORT_CMD_CPS);
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS);
+ ASMAtomicOrU32(&pAhciPort->regSERR, AHCI_PORT_SERR_X | AHCI_PORT_SERR_N);
+
+ if (pAhciPort->fATAPI)
+ pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI;
+ else
+ pAhciPort->regSIG = AHCI_PORT_SIG_DISK;
+ pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */
+ (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
+ (0x03 << 0); /* Device detected and communication established. */
+
+ if ( (pAhciPort->regIE & AHCI_PORT_IE_CPDE)
+ || (pAhciPort->regIE & AHCI_PORT_IE_PCE)
+ || (pAhciPort->regIE & AHCI_PORT_IE_PRCE))
+ ahciHbaSetInterrupt(pAhciPort->CTX_SUFF(pAhci), pAhciPort->iLUN, VERR_IGNORED);
+ }
+
+ }
+
+ return rc;
+}
+
+/**
+ * Common reset worker.
+ *
+ * @param pDevIns The device instance data.
+ */
+static int ahciR3ResetCommon(PPDMDEVINS pDevIns)
+{
+ PAHCI pAhci = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ ahciHBAReset(pAhci);
+
+ /* Hardware reset for the ports. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pAhci->ahciPort); i++)
+ ahciPortHwReset(&pAhci->ahciPort[i]);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback employed by ahciR3Reset.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ahciR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ ahciR3ResetCommon(pDevIns);
+ return true;
+}
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ahciR3Reset(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ ahciR3ResetCommon(pDevIns);
+ }
+}
+
+/**
+ * Poweroff notification.
+ *
+ * @param pDevIns Pointer to the device instance
+ */
+static DECLCALLBACK(void) ahciR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("achiR3PowerOff\n"));
+ ahciR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) ahciR3Destruct(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ int rc = VINF_SUCCESS;
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ /*
+ * At this point the async I/O thread is suspended and will not enter
+ * this module again. So, no coordination is needed here and PDM
+ * will take care of terminating and cleaning up the thread.
+ */
+ if (PDMCritSectIsInitialized(&pThis->lock))
+ {
+ TMR3TimerDestroy(pThis->CTX_SUFF(pHbaCccTimer));
+ pThis->CTX_SUFF(pHbaCccTimer) = NULL;
+
+ Log(("%s: Destruct every port\n", __FUNCTION__));
+ for (unsigned iActPort = 0; iActPort < pThis->cPortsImpl; iActPort++)
+ {
+ PAHCIPort pAhciPort = &pThis->ahciPort[iActPort];
+
+ if (pAhciPort->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ SUPSemEventClose(pThis->pSupDrvSession, pAhciPort->hEvtProcess);
+ pAhciPort->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+ }
+
+ PDMR3CritSectDelete(&pThis->lock);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ahciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PAHCI pThis = PDMINS_2_DATA(pDevIns, PAHCI);
+ PPDMIBASE pBase;
+ int rc = VINF_SUCCESS;
+ unsigned i = 0;
+ bool fGCEnabled = false;
+ bool fR0Enabled = false;
+ uint32_t cbTotalBufferSize = 0;
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "GCEnabled\0"
+ "R0Enabled\0"
+ "PrimaryMaster\0"
+ "PrimarySlave\0"
+ "SecondaryMaster\0"
+ "SecondarySlave\0"
+ "PortCount\0"
+ "Bootable\0"
+ "CmdSlotsAvail\0"
+ "TigerHack\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("AHCI configuration error: unknown option specified"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &fGCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read GCEnabled as boolean"));
+ Log(("%s: fGCEnabled=%d\n", __FUNCTION__, fGCEnabled));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read R0Enabled as boolean"));
+ Log(("%s: fR0Enabled=%d\n", __FUNCTION__, fR0Enabled));
+
+ rc = CFGMR3QueryU32Def(pCfg, "PortCount", &pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read PortCount as integer"));
+ Log(("%s: cPortsImpl=%u\n", __FUNCTION__, pThis->cPortsImpl));
+ if (pThis->cPortsImpl > AHCI_MAX_NR_PORTS_IMPL)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AHCI configuration error: PortCount=%u should not exceed %u"),
+ pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL);
+ if (pThis->cPortsImpl < 1)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AHCI configuration error: PortCount=%u should be at least 1"),
+ pThis->cPortsImpl);
+
+ rc = CFGMR3QueryBoolDef(pCfg, "Bootable", &pThis->fBootable, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read Bootable as boolean"));
+
+ rc = CFGMR3QueryU32Def(pCfg, "CmdSlotsAvail", &pThis->cCmdSlotsAvail, AHCI_NR_COMMAND_SLOTS);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read CmdSlotsAvail as integer"));
+ Log(("%s: cCmdSlotsAvail=%u\n", __FUNCTION__, pThis->cCmdSlotsAvail));
+ if (pThis->cCmdSlotsAvail > AHCI_NR_COMMAND_SLOTS)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AHCI configuration error: CmdSlotsAvail=%u should not exceed %u"),
+ pThis->cPortsImpl, AHCI_NR_COMMAND_SLOTS);
+ if (pThis->cCmdSlotsAvail < 1)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AHCI configuration error: CmdSlotsAvail=%u should be at least 1"),
+ pThis->cCmdSlotsAvail);
+ bool fTigerHack;
+ rc = CFGMR3QueryBoolDef(pCfg, "TigerHack", &fTigerHack, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read TigerHack as boolean"));
+
+ /*
+ * Initialize the instance data (everything touched by the destructor need
+ * to be initialized here!).
+ */
+ pThis->fR0Enabled = fR0Enabled;
+ pThis->fGCEnabled = fGCEnabled;
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->pSupDrvSession = PDMDevHlpGetSupDrvSession(pDevIns);
+
+ PCIDevSetVendorId (&pThis->dev, 0x8086); /* Intel */
+ PCIDevSetDeviceId (&pThis->dev, 0x2829); /* ICH-8M */
+ PCIDevSetCommand (&pThis->dev, 0x0000);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PCIDevSetStatus (&pThis->dev, VBOX_PCI_STATUS_CAP_LIST);
+ PCIDevSetCapabilityList(&pThis->dev, 0x80);
+#else
+ PCIDevSetCapabilityList(&pThis->dev, 0x70);
+#endif
+ PCIDevSetRevisionId (&pThis->dev, 0x02);
+ PCIDevSetClassProg (&pThis->dev, 0x01);
+ PCIDevSetClassSub (&pThis->dev, 0x06);
+ PCIDevSetClassBase (&pThis->dev, 0x01);
+ PCIDevSetBaseAddress (&pThis->dev, 5, false, false, false, 0x00000000);
+
+ PCIDevSetInterruptLine(&pThis->dev, 0x00);
+ PCIDevSetInterruptPin (&pThis->dev, 0x01);
+
+ pThis->dev.abConfig[0x70] = VBOX_PCI_CAP_ID_PM; /* Capability ID: PCI Power Management Interface */
+ pThis->dev.abConfig[0x71] = 0xa8; /* next */
+ pThis->dev.abConfig[0x72] = 0x03; /* version ? */
+
+ pThis->dev.abConfig[0x90] = 0x40; /* AHCI mode. */
+ pThis->dev.abConfig[0x92] = 0x3f;
+ pThis->dev.abConfig[0x94] = 0x80;
+ pThis->dev.abConfig[0x95] = 0x01;
+ pThis->dev.abConfig[0x97] = 0x78;
+
+ pThis->dev.abConfig[0xa8] = 0x12; /* SATACR capability */
+ pThis->dev.abConfig[0xa9] = 0x00; /* next */
+ PCIDevSetWord(&pThis->dev, 0xaa, 0x0010); /* Revision */
+ PCIDevSetDWord(&pThis->dev, 0xac, 0x00000028); /* SATA Capability Register 1 */
+
+ pThis->cThreadsActive = 0;
+
+ /* Initialize port members. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ PAHCIPort pAhciPort = &pThis->ahciPort[i];
+ pAhciPort->pDevInsR3 = pDevIns;
+ pAhciPort->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pAhciPort->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pAhciPort->iLUN = i;
+ pAhciPort->pAhciR3 = pThis;
+ pAhciPort->pAhciR0 = PDMINS_2_DATA_R0PTR(pDevIns);
+ pAhciPort->pAhciRC = PDMINS_2_DATA_RCPTR(pDevIns);
+ pAhciPort->Led.u32Magic = PDMLED_MAGIC;
+ pAhciPort->pDrvBase = NULL;
+ pAhciPort->pAsyncIOThread = NULL;
+ pAhciPort->hEvtProcess = NIL_SUPSEMEVENT;
+ pAhciPort->fHotpluggable = true;
+ }
+
+ /*
+ * Init locks, using explicit locking where necessary.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->lock, RT_SRC_POS, "AHCI#%u", iInstance);
+ if (RT_FAILURE(rc))
+ {
+ Log(("%s: Failed to create critical section.\n", __FUNCTION__));
+ return rc;
+ }
+
+ /*
+ * Register the PCI device, it's I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister (pDevIns, &pThis->dev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x80;
+ MsiReg.iMsiNextOffset = 0x70;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ PCIDevSetCapabilityList(&pThis->dev, 0x70);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ /*
+ * Solaris 10 U5 fails to map the AHCI register space when the sets (0..5) for the legacy
+ * IDE registers are not available.
+ * We set up "fake" entries in the PCI configuration register.
+ * That means they are available but read and writes from/to them have no effect.
+ * No guest should access them anyway because the controller is marked as AHCI in the Programming interface
+ * and we don't have an option to change to IDE emulation (real hardware provides an option in the BIOS
+ * to switch to it which also changes device Id and other things in the PCI configuration space).
+ */
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 8, PCI_ADDRESS_SPACE_IO, ahciR3LegacyFakeIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI I/O region"));
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 1, PCI_ADDRESS_SPACE_IO, ahciR3LegacyFakeIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI I/O region"));
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 2, 8, PCI_ADDRESS_SPACE_IO, ahciR3LegacyFakeIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI I/O region"));
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 3, 1, PCI_ADDRESS_SPACE_IO, ahciR3LegacyFakeIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI I/O region"));
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 4, 0x10, PCI_ADDRESS_SPACE_IO, ahciR3IdxDataIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI I/O region for BMDMA"));
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 5, 4352, PCI_ADDRESS_SPACE_MEM, ahciR3MMIOMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI cannot register PCI memory region for registers"));
+
+ /* Create the timer for command completion coalescing feature. */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ahciCccTimer, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT, "AHCI CCC Timer", &pThis->pHbaCccTimerR3);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("pfnTMTimerCreate -> %Rrc\n", rc));
+ return rc;
+ }
+ pThis->pHbaCccTimerR0 = TMTimerR0Ptr(pThis->pHbaCccTimerR3);
+ pThis->pHbaCccTimerRC = TMTimerRCPtr(pThis->pHbaCccTimerR3);
+
+ /* Status LUN. */
+ pThis->IBase.pfnQueryInterface = ahciR3Status_QueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = ahciR3Status_QueryStatusLed;
+
+ /*
+ * Create the notification queue.
+ *
+ * We need 2 items for every port because of SMP races.
+ */
+ rc = PDMDevHlpQueueCreate(pDevIns, sizeof(DEVPORTNOTIFIERQUEUEITEM), AHCI_MAX_NR_PORTS_IMPL * 2, 0,
+ ahciNotifyQueueConsumer, true, "AHCI-Xmit", &pThis->pNotifierQueueR3);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->pNotifierQueueR0 = PDMQueueR0Ptr(pThis->pNotifierQueueR3);
+ pThis->pNotifierQueueRC = PDMQueueRCPtr(pThis->pNotifierQueueR3);
+
+ /* Initialize static members on every port. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ ahciPortHwReset(&pThis->ahciPort[i]);
+
+ /* Attach drivers to every available port. */
+ for (i = 0; i < pThis->cPortsImpl; i++)
+ {
+ char *pszName;
+ if (RTStrAPrintf(&pszName, "Port%u", i) <= 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ PAHCIPort pAhciPort = &pThis->ahciPort[i];
+ /*
+ * Init interfaces.
+ */
+ pAhciPort->IBase.pfnQueryInterface = ahciR3PortQueryInterface;
+ pAhciPort->IMediaExPort.pfnIoReqCompleteNotify = ahciR3IoReqCompleteNotify;
+ pAhciPort->IMediaExPort.pfnIoReqCopyFromBuf = ahciR3IoReqCopyFromBuf;
+ pAhciPort->IMediaExPort.pfnIoReqCopyToBuf = ahciR3IoReqCopyToBuf;
+ pAhciPort->IMediaExPort.pfnIoReqQueryBuf = ahciR3IoReqQueryBuf;
+ pAhciPort->IMediaExPort.pfnIoReqQueryDiscardRanges = ahciR3IoReqQueryDiscardRanges;
+ pAhciPort->IMediaExPort.pfnIoReqStateChanged = ahciR3IoReqStateChanged;
+ pAhciPort->IMediaExPort.pfnMediumEjected = ahciR3MediumEjected;
+ pAhciPort->IPort.pfnQueryDeviceLocation = ahciR3PortQueryDeviceLocation;
+ pAhciPort->IPort.pfnQueryScsiInqStrings = ahciR3PortQueryScsiInqStrings;
+ pAhciPort->fWrkThreadSleeping = true;
+
+ /* Query per port configuration options if available. */
+ PCFGMNODE pCfgPort = CFGMR3GetChild(pDevIns->pCfg, pszName);
+ if (pCfgPort)
+ {
+ rc = CFGMR3QueryBoolDef(pCfgPort, "Hotpluggable", &pAhciPort->fHotpluggable, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AHCI configuration error: failed to read Hotpluggable as boolean"));
+ }
+
+ /*
+ * Attach the block driver
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, pszName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ahciR3ConfigureLUN(pDevIns, pAhciPort);
+ if (RT_FAILURE(rc))
+ {
+ Log(("%s: Failed to configure the %s.\n", __FUNCTION__, pszName));
+ return rc;
+ }
+
+ /* Mark that a device is present on that port */
+ if (i < 6)
+ pThis->dev.abConfig[0x93] |= (1 << i);
+
+ /*
+ * Init vendor product data.
+ */
+ rc = ahciR3VpdInit(pDevIns, pAhciPort, pszName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = SUPSemEventCreate(pThis->pSupDrvSession, &pAhciPort->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to create SUP event semaphore"));
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop,
+ ahciAsyncIOLoopWakeUp, 0, RTTHREADTYPE_IO, pszName);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to create worker thread %s"), pszName);
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pAhciPort->pDrvBase = NULL;
+ rc = VINF_SUCCESS;
+ LogRel(("AHCI: %s: No driver attached\n", pszName));
+ }
+ else
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to attach drive to %s"), pszName);
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThis->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot attach to status driver"));
+ }
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, AHCI_SAVED_STATE_VERSION, sizeof(*pThis) + cbTotalBufferSize, NULL,
+ NULL, ahciR3LiveExec, NULL,
+ ahciR3SavePrep, ahciR3SaveExec, NULL,
+ ahciR3LoadPrep, ahciR3LoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register the info item.
+ */
+ char szTmp[128];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%s%d", pDevIns->pReg->szName, pDevIns->iInstance);
+ PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "AHCI info", ahciR3Info);
+
+ return ahciR3ResetCommon(pDevIns);
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceAHCI =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "ahci",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "Intel AHCI controller.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION |
+ PDM_DEVREG_FLAGS_FIRST_RESET_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(AHCI),
+ /* pfnConstruct */
+ ahciR3Construct,
+ /* pfnDestruct */
+ ahciR3Destruct,
+ /* pfnRelocate */
+ ahciR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ahciR3Reset,
+ /* pfnSuspend */
+ ahciR3Suspend,
+ /* pfnResume */
+ ahciR3Resume,
+ /* pfnAttach */
+ ahciR3Attach,
+ /* pfnDetach */
+ ahciR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ ahciR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Storage/DevATA.cpp b/src/VBox/Devices/Storage/DevATA.cpp
new file mode 100644
index 00000000..ed7849df
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevATA.cpp
@@ -0,0 +1,7959 @@
+/* $Id: DevATA.cpp $ */
+/** @file
+ * VBox storage devices: ATA/ATAPI controller device (disk and cdrom).
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Temporary instrumentation for tracking down potential virtual disk
+ * write performance issues. */
+#undef VBOX_INSTRUMENT_DMA_WRITES
+
+/** @name The SSM saved state versions.
+ * @{
+ */
+/** The current saved state version. */
+#define ATA_SAVED_STATE_VERSION 20
+/** The saved state version used by VirtualBox 3.0.
+ * This lacks the config part and has the type at the and. */
+#define ATA_SAVED_STATE_VERSION_VBOX_30 19
+#define ATA_SAVED_STATE_VERSION_WITH_BOOL_TYPE 18
+#define ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 16
+#define ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 17
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_IDE
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/mem.h>
+# include <iprt/mp.h>
+# include <iprt/semaphore.h>
+# include <iprt/thread.h>
+# include <iprt/time.h>
+# include <iprt/uuid.h>
+#endif /* IN_RING3 */
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pgm.h>
+
+#include <VBox/sup.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+#include <VBox/ata.h>
+
+#include "ATAPIPassthrough.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/* MediaEventStatus */
+#define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */
+#define ATA_EVENT_STATUS_MEDIA_NEW 1 /**< new medium inserted */
+#define ATA_EVENT_STATUS_MEDIA_REMOVED 2 /**< medium removed */
+#define ATA_EVENT_STATUS_MEDIA_CHANGED 3 /**< medium was removed + new medium was inserted */
+#define ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED 4 /**< medium eject requested (eject button pressed) */
+
+/* Media track type */
+#define ATA_MEDIA_TYPE_UNKNOWN 0 /**< unknown CD type */
+#define ATA_MEDIA_NO_DISC 0x70 /**< Door closed, no medium */
+
+/** @defgroup grp_piix3atabmdma PIIX3 ATA Bus Master DMA
+ * @{
+ */
+
+/** @name BM_STATUS
+ * @{
+ */
+/** Currently performing a DMA operation. */
+#define BM_STATUS_DMAING 0x01
+/** An error occurred during the DMA operation. */
+#define BM_STATUS_ERROR 0x02
+/** The DMA unit has raised the IDE interrupt line. */
+#define BM_STATUS_INT 0x04
+/** User-defined bit 0, commonly used to signal that drive 0 supports DMA. */
+#define BM_STATUS_D0DMA 0x20
+/** User-defined bit 1, commonly used to signal that drive 1 supports DMA. */
+#define BM_STATUS_D1DMA 0x40
+/** @} */
+
+/** @name BM_CMD
+ * @{
+ */
+/** Start the DMA operation. */
+#define BM_CMD_START 0x01
+/** Data transfer direction: from device to memory if set. */
+#define BM_CMD_WRITE 0x08
+/** @} */
+
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @defgroup grp_piix3atabmdma PIIX3 ATA Bus Master DMA
+ * @{
+ */
+/** PIIX3 Bus Master DMA unit state. */
+typedef struct BMDMAState
+{
+ /** Command register. */
+ uint8_t u8Cmd;
+ /** Status register. */
+ uint8_t u8Status;
+ /** Address of the MMIO region in the guest's memory space. */
+ RTGCPHYS32 GCPhysAddr;
+} BMDMAState;
+
+/** PIIX3 Bus Master DMA descriptor entry. */
+typedef struct BMDMADesc
+{
+ /** Address of the DMA source/target buffer. */
+ RTGCPHYS32 GCPhysBuffer;
+ /** Size of the DMA source/target buffer. */
+ uint32_t cbBuffer;
+} BMDMADesc;
+/** @} */
+
+
+/**
+ * The state of an ATA device.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIBLOCKPORT
+ * @implements PDMIMOUNTNOTIFY
+ */
+typedef struct ATADevState
+{
+ /** Flag indicating whether the current command uses LBA48 mode. */
+ bool fLBA48;
+ /** Flag indicating whether this drive implements the ATAPI command set. */
+ bool fATAPI;
+ /** Set if this interface has asserted the IRQ. */
+ bool fIrqPending;
+ /** Currently configured number of sectors in a multi-sector transfer. */
+ uint8_t cMultSectors;
+ /** PCHS disk geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Total number of sectors on this disk. */
+ uint64_t cTotalSectors;
+ /** Sector size of the medium. */
+ uint32_t cbSector;
+ /** Number of sectors to transfer per IRQ. */
+ uint32_t cSectorsPerIRQ;
+
+ /** ATA/ATAPI register 1: feature (write-only). */
+ uint8_t uATARegFeature;
+ /** ATA/ATAPI register 1: feature, high order byte. */
+ uint8_t uATARegFeatureHOB;
+ /** ATA/ATAPI register 1: error (read-only). */
+ uint8_t uATARegError;
+ /** ATA/ATAPI register 2: sector count (read/write). */
+ uint8_t uATARegNSector;
+ /** ATA/ATAPI register 2: sector count, high order byte. */
+ uint8_t uATARegNSectorHOB;
+ /** ATA/ATAPI register 3: sector (read/write). */
+ uint8_t uATARegSector;
+ /** ATA/ATAPI register 3: sector, high order byte. */
+ uint8_t uATARegSectorHOB;
+ /** ATA/ATAPI register 4: cylinder low (read/write). */
+ uint8_t uATARegLCyl;
+ /** ATA/ATAPI register 4: cylinder low, high order byte. */
+ uint8_t uATARegLCylHOB;
+ /** ATA/ATAPI register 5: cylinder high (read/write). */
+ uint8_t uATARegHCyl;
+ /** ATA/ATAPI register 5: cylinder high, high order byte. */
+ uint8_t uATARegHCylHOB;
+ /** ATA/ATAPI register 6: select drive/head (read/write). */
+ uint8_t uATARegSelect;
+ /** ATA/ATAPI register 7: status (read-only). */
+ uint8_t uATARegStatus;
+ /** ATA/ATAPI register 7: command (write-only). */
+ uint8_t uATARegCommand;
+ /** ATA/ATAPI drive control register (write-only). */
+ uint8_t uATARegDevCtl;
+
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** Current transfer direction. */
+ uint8_t uTxDir;
+ /** Index of callback for begin transfer. */
+ uint8_t iBeginTransfer;
+ /** Index of callback for source/sink of data. */
+ uint8_t iSourceSink;
+ /** Flag indicating whether the current command transfers data in DMA mode. */
+ bool fDMA;
+ /** Set to indicate that ATAPI transfer semantics must be used. */
+ bool fATAPITransfer;
+
+ /** Total ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbTotalTransfer;
+ /** Elementary ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbElementaryTransfer;
+ /** Maximum ATAPI elementary transfer size, PIO only. */
+ uint32_t cbPIOTransferLimit;
+ /** ATAPI passthrough transfer size, shared PIO/DMA */
+ uint32_t cbAtapiPassthroughTransfer;
+ /** Current read/write buffer position, shared PIO/DMA. */
+ uint32_t iIOBufferCur;
+ /** First element beyond end of valid buffer content, shared PIO/DMA. */
+ uint32_t iIOBufferEnd;
+ /** Align the following fields correctly. */
+ uint32_t Alignment0;
+
+ /** ATA/ATAPI current PIO read/write transfer position. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataStart;
+ /** ATA/ATAPI current PIO read/write transfer end. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataEnd;
+
+ /** ATAPI current LBA position. */
+ uint32_t iATAPILBA;
+ /** ATAPI current sector size. */
+ uint32_t cbATAPISector;
+ /** ATAPI current command. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** HACK: Countdown till we report a newly unmounted drive as mounted. */
+ uint8_t cNotifiedMediaChange;
+ /** The same for GET_EVENT_STATUS for mechanism */
+ volatile uint32_t MediaEventStatus;
+
+ /** Media type if known. */
+ volatile uint32_t MediaTrackType;
+
+ /** The status LED state for this drive. */
+ PDMLED Led;
+
+ /** Size of I/O buffer. */
+ uint32_t cbIOBuffer;
+ /** Pointer to the I/O buffer. */
+ R3PTRTYPE(uint8_t *) pbIOBufferR3;
+ /** Pointer to the I/O buffer. */
+ R0PTRTYPE(uint8_t *) pbIOBufferR0;
+ /** Pointer to the I/O buffer. */
+ RCPTRTYPE(uint8_t *) pbIOBufferRC;
+
+ /*
+ * No data that is part of the saved state after this point!!!!!
+ */
+
+ /** Counter for number of busy status seen in R3 in a row. */
+ uint8_t cBusyStatusHackR3;
+ /** Counter for number of busy status seen in GC/R0 in a row. */
+ uint8_t cBusyStatusHackRZ;
+ /** Defines the R3 yield rate by a mask (power of 2 minus one).
+ * Lower is more agressive. */
+ uint8_t cBusyStatusHackR3Rate;
+ /** Defines the R0/RC yield rate by a mask (power of 2 minus one).
+ * Lower is more agressive. */
+ uint8_t cBusyStatusHackRZRate;
+
+ /** Release statistics: number of ATA DMA commands. */
+ STAMCOUNTER StatATADMA;
+ /** Release statistics: number of ATA PIO commands. */
+ STAMCOUNTER StatATAPIO;
+ /** Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIDMA;
+ /** Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIPIO;
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ /** Release statistics: number of DMA sector writes and the time spent. */
+ STAMPROFILEADV StatInstrVDWrites;
+#endif
+ /** Release statistics: Profiling RTThreadYield calls during status polling. */
+ STAMPROFILEADV StatStatusYields;
+
+ /** Statistics: number of read operations and the time spent reading. */
+ STAMPROFILEADV StatReads;
+ /** Statistics: number of bytes read. */
+ STAMCOUNTER StatBytesRead;
+ /** Statistics: number of write operations and the time spent writing. */
+ STAMPROFILEADV StatWrites;
+ /** Statistics: number of bytes written. */
+ STAMCOUNTER StatBytesWritten;
+ /** Statistics: number of flush operations and the time spend flushing. */
+ STAMPROFILE StatFlushes;
+
+ /** Enable passing through commands directly to the ATAPI drive. */
+ bool fATAPIPassthrough;
+ /** Flag whether to overwrite inquiry data in passthrough mode. */
+ bool fOverwriteInquiry;
+ /** Number of errors we've reported to the release log.
+ * This is to prevent flooding caused by something going horribly wrong.
+ * this value against MAX_LOG_REL_ERRORS in places likely to cause floods
+ * like the ones we currently seeing on the linux smoke tests (2006-11-10). */
+ uint32_t cErrors;
+ /** Timestamp of last started command. 0 if no command pending. */
+ uint64_t u64CmdTS;
+
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** The LUN #. */
+ RTUINT iLUN;
+
+ RTUINT Alignment2; /**< Align pDevInsR3 correctly. */
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to controller instance. */
+ R3PTRTYPE(struct ATACONTROLLER *) pControllerR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to controller instance. */
+ R0PTRTYPE(struct ATACONTROLLER *) pControllerR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Pointer to controller instance. */
+ RCPTRTYPE(struct ATACONTROLLER *) pControllerRC;
+
+ /** The serial number to use for IDENTIFY DEVICE commands. */
+ char szSerialNumber[ATA_SERIAL_NUMBER_LENGTH+1];
+ /** The firmware revision to use for IDENTIFY DEVICE commands. */
+ char szFirmwareRevision[ATA_FIRMWARE_REVISION_LENGTH+1];
+ /** The model number to use for IDENTIFY DEVICE commands. */
+ char szModelNumber[ATA_MODEL_NUMBER_LENGTH+1];
+ /** The vendor identification string for SCSI INQUIRY commands. */
+ char szInquiryVendorId[SCSI_INQUIRY_VENDOR_ID_LENGTH+1];
+ /** The product identification string for SCSI INQUIRY commands. */
+ char szInquiryProductId[SCSI_INQUIRY_PRODUCT_ID_LENGTH+1];
+ /** The revision string for SCSI INQUIRY commands. */
+ char szInquiryRevision[SCSI_INQUIRY_REVISION_LENGTH+1];
+ /** The current tracklist of the loaded medium if passthrough is used. */
+ R3PTRTYPE(PTRACKLIST) pTrackList;
+
+ uint8_t abAlignment4[HC_ARCH_BITS == 64 ? 7 : 3];
+} ATADevState;
+AssertCompileMemberAlignment(ATADevState, cTotalSectors, 8);
+AssertCompileMemberAlignment(ATADevState, StatATADMA, 8);
+AssertCompileMemberAlignment(ATADevState, u64CmdTS, 8);
+AssertCompileMemberAlignment(ATADevState, pDevInsR3, 8);
+AssertCompileMemberAlignment(ATADevState, szSerialNumber, 8);
+AssertCompileSizeAlignment(ATADevState, 8);
+
+
+/**
+ * Transfer request forwarded to the async I/O thread.
+ */
+typedef struct ATATransferRequest
+{
+ /** The interface index the request is for. */
+ uint8_t iIf;
+ /** The index of the begin transfer callback to call. */
+ uint8_t iBeginTransfer;
+ /** The index of the source sink callback to call for doing the transfer. */
+ uint8_t iSourceSink;
+ /** How many bytes to transfer. */
+ uint32_t cbTotalTransfer;
+ /** Transfer direction. */
+ uint8_t uTxDir;
+} ATATransferRequest;
+
+
+/**
+ * Abort request forwarded to the async I/O thread.
+ */
+typedef struct ATAAbortRequest
+{
+ /** The interface index the request is for. */
+ uint8_t iIf;
+ /** Flag whether to reset the drive. */
+ bool fResetDrive;
+} ATAAbortRequest;
+
+
+/**
+ * Request type indicator.
+ */
+typedef enum
+{
+ /** Begin a new transfer. */
+ ATA_AIO_NEW = 0,
+ /** Continue a DMA transfer. */
+ ATA_AIO_DMA,
+ /** Continue a PIO transfer. */
+ ATA_AIO_PIO,
+ /** Reset the drives on current controller, stop all transfer activity. */
+ ATA_AIO_RESET_ASSERTED,
+ /** Reset the drives on current controller, resume operation. */
+ ATA_AIO_RESET_CLEARED,
+ /** Abort the current transfer of a particular drive. */
+ ATA_AIO_ABORT
+} ATAAIO;
+
+
+/**
+ * Combining structure for an ATA request to the async I/O thread
+ * started with the request type insicator.
+ */
+typedef struct ATARequest
+{
+ /** Request type. */
+ ATAAIO ReqType;
+ /** Request type dependent data. */
+ union
+ {
+ /** Transfer request specific data. */
+ ATATransferRequest t;
+ /** Abort request specific data. */
+ ATAAbortRequest a;
+ } u;
+} ATARequest;
+
+
+/**
+ * The state of an ATA controller containing to devices (master and slave).
+ */
+typedef struct ATACONTROLLER
+{
+ /** The base of the first I/O Port range. */
+ RTIOPORT IOPortBase1;
+ /** The base of the second I/O Port range. (0 if none) */
+ RTIOPORT IOPortBase2;
+ /** The assigned IRQ. */
+ RTUINT irq;
+ /** Access critical section */
+ PDMCRITSECT lock;
+
+ /** Selected drive. */
+ uint8_t iSelectedIf;
+ /** The interface on which to handle async I/O. */
+ uint8_t iAIOIf;
+ /** The state of the async I/O thread. */
+ uint8_t uAsyncIOState;
+ /** Flag indicating whether the next transfer is part of the current command. */
+ bool fChainedTransfer;
+ /** Set when the reset processing is currently active on this controller. */
+ bool fReset;
+ /** Flag whether the current transfer needs to be redone. */
+ bool fRedo;
+ /** Flag whether the redo suspend has been finished. */
+ bool fRedoIdle;
+ /** Flag whether the DMA operation to be redone is the final transfer. */
+ bool fRedoDMALastDesc;
+ /** The BusMaster DMA state. */
+ BMDMAState BmDma;
+ /** Pointer to first DMA descriptor. */
+ RTGCPHYS32 GCPhysFirstDMADesc;
+ /** Pointer to last DMA descriptor. */
+ RTGCPHYS32 GCPhysLastDMADesc;
+ /** Pointer to current DMA buffer (for redo operations). */
+ RTGCPHYS32 GCPhysRedoDMABuffer;
+ /** Size of current DMA buffer (for redo operations). */
+ uint32_t cbRedoDMABuffer;
+
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADevState aIfs[2];
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to device instance. */
+ PPDMDEVINSRC pDevInsRC;
+
+ /** Set when the destroying the device instance and the thread must exit. */
+ uint32_t volatile fShutdown;
+ /** The async I/O thread handle. NIL_RTTHREAD if no thread. */
+ RTTHREAD AsyncIOThread;
+ /** The event semaphore the thread is waiting on for requests. */
+ SUPSEMEVENT hAsyncIOSem;
+ /** The support driver session handle. */
+ PSUPDRVSESSION pSupDrvSession;
+ /** The request queue for the AIO thread. One element is always unused. */
+ ATARequest aAsyncIORequests[4];
+ /** The position at which to insert a new request for the AIO thread. */
+ volatile uint8_t AsyncIOReqHead;
+ /** The position at which to get a new request for the AIO thread. */
+ volatile uint8_t AsyncIOReqTail;
+ /** Whether to call PDMDevHlpAsyncNotificationCompleted when idle. */
+ bool volatile fSignalIdle;
+ uint8_t Alignment3[1]; /**< Explicit padding of the 1 byte gap. */
+ /** Magic delay before triggering interrupts in DMA mode. */
+ uint32_t DelayIRQMillies;
+ /** The event semaphore the thread is waiting on during suspended I/O. */
+ RTSEMEVENT SuspendIOSem;
+ /** The lock protecting the request queue. */
+ PDMCRITSECT AsyncIORequestLock;
+
+ /** Timestamp we started the reset. */
+ uint64_t u64ResetTime;
+
+ /* Statistics */
+ STAMCOUNTER StatAsyncOps;
+ uint64_t StatAsyncMinWait;
+ uint64_t StatAsyncMaxWait;
+ STAMCOUNTER StatAsyncTimeUS;
+ STAMPROFILEADV StatAsyncTime;
+ STAMPROFILE StatLockWait;
+} ATACONTROLLER, *PATACONTROLLER;
+AssertCompileMemberAlignment(ATACONTROLLER, lock, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, aIfs, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, u64ResetTime, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, StatAsyncOps, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, AsyncIORequestLock, 8);
+AssertCompileSizeAlignment(ATACONTROLLER, 8);
+
+typedef enum CHIPSET
+{
+ /** PIIX3 chipset, must be 0 for saved state compatibility */
+ CHIPSET_PIIX3 = 0,
+ /** PIIX4 chipset, must be 1 for saved state compatibility */
+ CHIPSET_PIIX4 = 1,
+ /** ICH6 chipset */
+ CHIPSET_ICH6 = 2
+} CHIPSET;
+
+/**
+ * The state of the ATA PCI device.
+ *
+ * @extends PDMPCIDEV
+ * @implements PDMILEDPORTS
+ */
+typedef struct PCIATAState
+{
+ PDMPCIDEV dev;
+ /** The controllers. */
+ ATACONTROLLER aCts[2];
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevIns;
+ /** Status LUN: Base interface. */
+ PDMIBASE IBase;
+ /** Status LUN: Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Status LUN: Media Notify. */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+ /** Flag whether RC is enabled. */
+ bool fRCEnabled;
+ /** Flag whether R0 is enabled. */
+ bool fR0Enabled;
+ /** Flag indicating chipset being emulated. */
+ uint8_t u8Type;
+ bool Alignment0[HC_ARCH_BITS == 64 ? 5 : 1 ]; /**< Align the struct size. */
+} PCIATAState;
+
+#define ATACONTROLLER_IDX(pController) ( (pController) - PDMINS_2_DATA(CONTROLLER_2_DEVINS(pController), PCIATAState *)->aCts )
+
+#define ATADEVSTATE_2_CONTROLLER(pIf) ( (pIf)->CTX_SUFF(pController) )
+#define ATADEVSTATE_2_DEVINS(pIf) ( (pIf)->CTX_SUFF(pDevIns) )
+#define CONTROLLER_2_DEVINS(pController) ( (pController)->CTX_SUFF(pDevIns) )
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+
+PDMBOTHCBDECL(int) ataIOPortWrite1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortRead1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortWriteStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint8_t const *pbSrc,
+ uint32_t *pcTransfers, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortReadStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint8_t *pbDst,
+ uint32_t *pcTransfers, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortWrite1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortRead1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *u32, unsigned cb);
+PDMBOTHCBDECL(int) ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+RT_C_DECLS_END
+
+
+
+#ifdef IN_RING3
+DECLINLINE(void) ataSetStatusValue(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus = stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+#endif /* IN_RING3 */
+
+
+DECLINLINE(void) ataSetStatus(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus |= stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+
+DECLINLINE(void) ataUnsetStatus(ATADevState *s, uint8_t stat)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus &= ~stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+#if defined(IN_RING3) || defined(IN_RING0)
+
+# ifdef IN_RING3
+typedef void (*PBeginTransferFunc)(ATADevState *);
+typedef bool (*PSourceSinkFunc)(ATADevState *);
+
+static void ataR3ReadWriteSectorsBT(ATADevState *);
+static void ataR3PacketBT(ATADevState *);
+static void atapiR3CmdBT(ATADevState *);
+static void atapiR3PassthroughCmdBT(ATADevState *);
+
+static bool ataR3IdentifySS(ATADevState *);
+static bool ataR3FlushSS(ATADevState *);
+static bool ataR3ReadSectorsSS(ATADevState *);
+static bool ataR3WriteSectorsSS(ATADevState *);
+static bool ataR3ExecuteDeviceDiagnosticSS(ATADevState *);
+static bool ataR3TrimSS(ATADevState *);
+static bool ataR3PacketSS(ATADevState *);
+static bool ataR3InitDevParmSS(ATADevState *);
+static bool ataR3RecalibrateSS(ATADevState *);
+static bool atapiR3GetConfigurationSS(ATADevState *);
+static bool atapiR3GetEventStatusNotificationSS(ATADevState *);
+static bool atapiR3IdentifySS(ATADevState *);
+static bool atapiR3InquirySS(ATADevState *);
+static bool atapiR3MechanismStatusSS(ATADevState *);
+static bool atapiR3ModeSenseErrorRecoverySS(ATADevState *);
+static bool atapiR3ModeSenseCDStatusSS(ATADevState *);
+static bool atapiR3ReadSS(ATADevState *);
+static bool atapiR3ReadCapacitySS(ATADevState *);
+static bool atapiR3ReadDiscInformationSS(ATADevState *);
+static bool atapiR3ReadTOCNormalSS(ATADevState *);
+static bool atapiR3ReadTOCMultiSS(ATADevState *);
+static bool atapiR3ReadTOCRawSS(ATADevState *);
+static bool atapiR3ReadTrackInformationSS(ATADevState *);
+static bool atapiR3RequestSenseSS(ATADevState *);
+static bool atapiR3PassthroughSS(ATADevState *);
+static bool atapiR3ReadDVDStructureSS(ATADevState *);
+# endif /* IN_RING3 */
+
+/**
+ * Begin of transfer function indexes for g_apfnBeginTransFuncs.
+ */
+typedef enum ATAFNBT
+{
+ ATAFN_BT_NULL = 0,
+ ATAFN_BT_READ_WRITE_SECTORS,
+ ATAFN_BT_PACKET,
+ ATAFN_BT_ATAPI_CMD,
+ ATAFN_BT_ATAPI_PASSTHROUGH_CMD,
+ ATAFN_BT_MAX
+} ATAFNBT;
+
+# ifdef IN_RING3
+/**
+ * Array of end transfer functions, the index is ATAFNET.
+ * Make sure ATAFNET and this array match!
+ */
+static const PBeginTransferFunc g_apfnBeginTransFuncs[ATAFN_BT_MAX] =
+{
+ NULL,
+ ataR3ReadWriteSectorsBT,
+ ataR3PacketBT,
+ atapiR3CmdBT,
+ atapiR3PassthroughCmdBT,
+};
+# endif /* IN_RING3 */
+
+/**
+ * Source/sink function indexes for g_apfnSourceSinkFuncs.
+ */
+typedef enum ATAFNSS
+{
+ ATAFN_SS_NULL = 0,
+ ATAFN_SS_IDENTIFY,
+ ATAFN_SS_FLUSH,
+ ATAFN_SS_READ_SECTORS,
+ ATAFN_SS_WRITE_SECTORS,
+ ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC,
+ ATAFN_SS_TRIM,
+ ATAFN_SS_PACKET,
+ ATAFN_SS_INITIALIZE_DEVICE_PARAMETERS,
+ ATAFN_SS_RECALIBRATE,
+ ATAFN_SS_ATAPI_GET_CONFIGURATION,
+ ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION,
+ ATAFN_SS_ATAPI_IDENTIFY,
+ ATAFN_SS_ATAPI_INQUIRY,
+ ATAFN_SS_ATAPI_MECHANISM_STATUS,
+ ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
+ ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
+ ATAFN_SS_ATAPI_READ,
+ ATAFN_SS_ATAPI_READ_CAPACITY,
+ ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
+ ATAFN_SS_ATAPI_READ_TOC_NORMAL,
+ ATAFN_SS_ATAPI_READ_TOC_MULTI,
+ ATAFN_SS_ATAPI_READ_TOC_RAW,
+ ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
+ ATAFN_SS_ATAPI_REQUEST_SENSE,
+ ATAFN_SS_ATAPI_PASSTHROUGH,
+ ATAFN_SS_ATAPI_READ_DVD_STRUCTURE,
+ ATAFN_SS_MAX
+} ATAFNSS;
+
+# ifdef IN_RING3
+/**
+ * Array of source/sink functions, the index is ATAFNSS.
+ * Make sure ATAFNSS and this array match!
+ */
+static const PSourceSinkFunc g_apfnSourceSinkFuncs[ATAFN_SS_MAX] =
+{
+ NULL,
+ ataR3IdentifySS,
+ ataR3FlushSS,
+ ataR3ReadSectorsSS,
+ ataR3WriteSectorsSS,
+ ataR3ExecuteDeviceDiagnosticSS,
+ ataR3TrimSS,
+ ataR3PacketSS,
+ ataR3InitDevParmSS,
+ ataR3RecalibrateSS,
+ atapiR3GetConfigurationSS,
+ atapiR3GetEventStatusNotificationSS,
+ atapiR3IdentifySS,
+ atapiR3InquirySS,
+ atapiR3MechanismStatusSS,
+ atapiR3ModeSenseErrorRecoverySS,
+ atapiR3ModeSenseCDStatusSS,
+ atapiR3ReadSS,
+ atapiR3ReadCapacitySS,
+ atapiR3ReadDiscInformationSS,
+ atapiR3ReadTOCNormalSS,
+ atapiR3ReadTOCMultiSS,
+ atapiR3ReadTOCRawSS,
+ atapiR3ReadTrackInformationSS,
+ atapiR3RequestSenseSS,
+ atapiR3PassthroughSS,
+ atapiR3ReadDVDStructureSS
+};
+# endif /* IN_RING3 */
+
+
+static const ATARequest g_ataDMARequest = { ATA_AIO_DMA, { { 0, 0, 0, 0, 0 } } };
+static const ATARequest g_ataPIORequest = { ATA_AIO_PIO, { { 0, 0, 0, 0, 0 } } };
+# ifdef IN_RING3
+static const ATARequest g_ataResetARequest = { ATA_AIO_RESET_ASSERTED, { { 0, 0, 0, 0, 0 } } };
+static const ATARequest g_ataResetCRequest = { ATA_AIO_RESET_CLEARED, { { 0, 0, 0, 0, 0 } } };
+# endif
+
+# ifdef IN_RING3
+static void ataR3AsyncIOClearRequests(PATACONTROLLER pCtl)
+{
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ pCtl->AsyncIOReqHead = 0;
+ pCtl->AsyncIOReqTail = 0;
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+# endif /* IN_RING3 */
+
+static void ataHCAsyncIOPutRequest(PATACONTROLLER pCtl, const ATARequest *pReq)
+{
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ Assert((pCtl->AsyncIOReqHead + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
+ memcpy(&pCtl->aAsyncIORequests[pCtl->AsyncIOReqHead], pReq, sizeof(*pReq));
+ pCtl->AsyncIOReqHead++;
+ pCtl->AsyncIOReqHead %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+
+ rc = PDMHCCritSectScheduleExitEvent(&pCtl->lock, pCtl->hAsyncIOSem);
+ if (RT_FAILURE(rc))
+ {
+ rc = SUPSemEventSignal(pCtl->pSupDrvSession, pCtl->hAsyncIOSem);
+ AssertRC(rc);
+ }
+}
+
+# ifdef IN_RING3
+
+static const ATARequest *ataR3AsyncIOGetCurrentRequest(PATACONTROLLER pCtl)
+{
+ const ATARequest *pReq;
+
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
+ pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
+ else
+ pReq = NULL;
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+ return pReq;
+}
+
+
+/**
+ * Remove the request with the given type, as it's finished. The request
+ * is not removed blindly, as this could mean a RESET request that is not
+ * yet processed (but has cleared the request queue) is lost.
+ *
+ * @param pCtl Controller for which to remove the request.
+ * @param ReqType Type of the request to remove.
+ */
+static void ataR3AsyncIORemoveCurrentRequest(PATACONTROLLER pCtl, ATAAIO ReqType)
+{
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
+ {
+ pCtl->AsyncIOReqTail++;
+ pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+ }
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+
+/**
+ * Dump the request queue for a particular controller. First dump the queue
+ * contents, then the already processed entries, as long as they haven't been
+ * overwritten.
+ *
+ * @param pCtl Controller for which to dump the queue.
+ */
+static void ataR3AsyncIODumpRequests(PATACONTROLLER pCtl)
+{
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ LogRel(("PIIX3 ATA: Ctl#%d: request queue dump (topmost is current):\n", ATACONTROLLER_IDX(pCtl)));
+ uint8_t curr = pCtl->AsyncIOReqTail;
+ do
+ {
+ if (curr == pCtl->AsyncIOReqHead)
+ LogRel(("PIIX3 ATA: Ctl#%d: processed requests (topmost is oldest):\n", ATACONTROLLER_IDX(pCtl)));
+ switch (pCtl->aAsyncIORequests[curr].ReqType)
+ {
+ case ATA_AIO_NEW:
+ LogRel(("new transfer request, iIf=%d iBeginTransfer=%d iSourceSink=%d cbTotalTransfer=%d uTxDir=%d\n",
+ pCtl->aAsyncIORequests[curr].u.t.iIf, pCtl->aAsyncIORequests[curr].u.t.iBeginTransfer,
+ pCtl->aAsyncIORequests[curr].u.t.iSourceSink, pCtl->aAsyncIORequests[curr].u.t.cbTotalTransfer,
+ pCtl->aAsyncIORequests[curr].u.t.uTxDir));
+ break;
+ case ATA_AIO_DMA:
+ LogRel(("dma transfer continuation\n"));
+ break;
+ case ATA_AIO_PIO:
+ LogRel(("pio transfer continuation\n"));
+ break;
+ case ATA_AIO_RESET_ASSERTED:
+ LogRel(("reset asserted request\n"));
+ break;
+ case ATA_AIO_RESET_CLEARED:
+ LogRel(("reset cleared request\n"));
+ break;
+ case ATA_AIO_ABORT:
+ LogRel(("abort request, iIf=%d fResetDrive=%d\n", pCtl->aAsyncIORequests[curr].u.a.iIf,
+ pCtl->aAsyncIORequests[curr].u.a.fResetDrive));
+ break;
+ default:
+ LogRel(("unknown request %d\n", pCtl->aAsyncIORequests[curr].ReqType));
+ }
+ curr = (curr + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+ } while (curr != pCtl->AsyncIOReqTail);
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+
+/**
+ * Checks whether the request queue for a particular controller is empty
+ * or whether a particular controller is idle.
+ *
+ * @param pCtl Controller for which to check the queue.
+ * @param fStrict If set then the controller is checked to be idle.
+ */
+static bool ataR3AsyncIOIsIdle(PATACONTROLLER pCtl, bool fStrict)
+{
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ bool fIdle = pCtl->fRedoIdle;
+ if (!fIdle)
+ fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
+ if (fStrict)
+ fIdle &= (pCtl->uAsyncIOState == ATA_AIO_NEW);
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+ return fIdle;
+}
+
+
+/**
+ * Send a transfer request to the async I/O thread.
+ *
+ * @param s Pointer to the ATA device state data.
+ * @param cbTotalTransfer Data transfer size.
+ * @param uTxDir Data transfer direction.
+ * @param iBeginTransfer Index of BeginTransfer callback.
+ * @param iSourceSink Index of SourceSink callback.
+ * @param fChainedTransfer Whether this is a transfer that is part of the previous command/transfer.
+ */
+static void ataR3StartTransfer(ATADevState *s, uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer,
+ ATAFNSS iSourceSink, bool fChainedTransfer)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ ATARequest Req;
+
+ Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed new request as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ /* If the controller is already doing something else right now, ignore
+ * the command that is being submitted. Some broken guests issue commands
+ * twice (e.g. the Linux kernel that comes with Acronis True Image 8). */
+ if (!fChainedTransfer && !ataR3AsyncIOIsIdle(pCtl, true /*fStrict*/))
+ {
+ Log(("%s: Ctl#%d: ignored command %#04x, controller state %d\n",
+ __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->uATARegCommand, pCtl->uAsyncIOState));
+ LogRel(("PIIX3 IDE: guest issued command %#04x while controller busy\n", s->uATARegCommand));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_NEW;
+ if (fChainedTransfer)
+ Req.u.t.iIf = pCtl->iAIOIf;
+ else
+ Req.u.t.iIf = pCtl->iSelectedIf;
+ Req.u.t.cbTotalTransfer = cbTotalTransfer;
+ Req.u.t.uTxDir = uTxDir;
+ Req.u.t.iBeginTransfer = iBeginTransfer;
+ Req.u.t.iSourceSink = iSourceSink;
+ ataSetStatusValue(s, ATA_STAT_BUSY);
+ pCtl->fChainedTransfer = fChainedTransfer;
+
+ /*
+ * Kick the worker thread into action.
+ */
+ Log2(("%s: Ctl#%d: message to async I/O thread, new request\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &Req);
+}
+
+
+/**
+ * Send an abort command request to the async I/O thread.
+ *
+ * @param s Pointer to the ATA device state data.
+ * @param fResetDrive Whether to reset the drive or just abort a command.
+ */
+static void ataR3AbortCurrentCommand(ATADevState *s, bool fResetDrive)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ ATARequest Req;
+
+ Assert(PDMCritSectIsOwner(&pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed aborting command as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_ABORT;
+ Req.u.a.iIf = pCtl->iSelectedIf;
+ Req.u.a.fResetDrive = fResetDrive;
+ ataSetStatus(s, ATA_STAT_BUSY);
+ Log2(("%s: Ctl#%d: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->iLUN));
+ ataHCAsyncIOPutRequest(pCtl, &Req);
+}
+# endif /* IN_RING3 */
+
+static void ataHCSetIRQ(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the interrupt
+ * line is asserted. It monitors the line for a rising edge. */
+ if (!s->fIrqPending)
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ /* Only actually set the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+ {
+ /** @todo experiment with adaptive IRQ delivery: for reads it is
+ * better to wait for IRQ delivery, as it reduces latency. */
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 1);
+ }
+ }
+ s->fIrqPending = true;
+}
+
+#endif /* IN_RING0 || IN_RING3 */
+
+static void ataUnsetIRQ(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d deasserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* Only actually unset the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf])
+ {
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 0);
+ }
+ }
+ s->fIrqPending = false;
+}
+
+#if defined(IN_RING0) || defined(IN_RING3)
+
+static void ataHCPIOTransferStart(ATADevState *s, uint32_t start, uint32_t size)
+{
+ Log2(("%s: LUN#%d start %d size %d\n", __FUNCTION__, s->iLUN, start, size));
+ s->iIOBufferPIODataStart = start;
+ s->iIOBufferPIODataEnd = start + size;
+ ataSetStatus(s, ATA_STAT_DRQ | ATA_STAT_SEEK);
+ ataUnsetStatus(s, ATA_STAT_BUSY);
+}
+
+
+static void ataHCPIOTransferStop(ATADevState *s)
+{
+ Log2(("%s: LUN#%d\n", __FUNCTION__, s->iLUN));
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataHCSetIRQ(s);
+ s->fATAPITransfer = false;
+ }
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static void ataHCPIOTransferLimitATAPI(ATADevState *s)
+{
+ uint32_t cbLimit, cbTransfer;
+
+ cbLimit = s->cbPIOTransferLimit;
+ /* Use maximum transfer size if the guest requested 0. Avoids a hang. */
+ if (cbLimit == 0)
+ cbLimit = 0xfffe;
+ Log2(("%s: byte count limit=%d\n", __FUNCTION__, cbLimit));
+ if (cbLimit == 0xffff)
+ cbLimit--;
+ cbTransfer = RT_MIN(s->cbTotalTransfer, s->iIOBufferEnd - s->iIOBufferCur);
+ if (cbTransfer > cbLimit)
+ {
+ /* Byte count limit for clipping must be even in this case */
+ if (cbLimit & 1)
+ cbLimit--;
+ cbTransfer = cbLimit;
+ }
+ s->uATARegLCyl = cbTransfer;
+ s->uATARegHCyl = cbTransfer >> 8;
+ s->cbElementaryTransfer = cbTransfer;
+}
+
+# ifdef IN_RING3
+
+/**
+ * Enters the lock protecting the controller data against concurrent access.
+ *
+ * @returns nothing.
+ * @param pCtl The controller to lock.
+ */
+DECLINLINE(void) ataR3LockEnter(PATACONTROLLER pCtl)
+{
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+}
+
+/**
+ * Leaves the lock protecting the controller against concurrent data access.
+ *
+ * @returns nothing.
+ * @param pCtl The controller to unlock.
+ */
+DECLINLINE(void) ataR3LockLeave(PATACONTROLLER pCtl)
+{
+ PDMCritSectLeave(&pCtl->lock);
+}
+
+static uint32_t ataR3GetNSectors(ATADevState *s)
+{
+ /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+ if (s->fLBA48)
+ {
+ if (!s->uATARegNSector && !s->uATARegNSectorHOB)
+ return 65536;
+ else
+ return s->uATARegNSectorHOB << 8 | s->uATARegNSector;
+ }
+ else
+ {
+ if (!s->uATARegNSector)
+ return 256;
+ else
+ return s->uATARegNSector;
+ }
+}
+
+
+static void ataR3PadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+ for (uint32_t i = 0; i < cbSize; i++)
+ {
+ if (*pbSrc)
+ pbDst[i ^ 1] = *pbSrc++;
+ else
+ pbDst[i ^ 1] = ' ';
+ }
+}
+
+
+#if 0 /* unused */
+/**
+ * Compares two MSF values.
+ *
+ * @returns 1 if the first value is greater than the second value.
+ * 0 if both are equal
+ * -1 if the first value is smaller than the second value.
+ */
+DECLINLINE(int) atapiCmpMSF(const uint8_t *pbMSF1, const uint8_t *pbMSF2)
+{
+ int iRes = 0;
+
+ for (unsigned i = 0; i < 3; i++)
+ {
+ if (pbMSF1[i] < pbMSF2[i])
+ {
+ iRes = -1;
+ break;
+ }
+ else if (pbMSF1[i] > pbMSF2[i])
+ {
+ iRes = 1;
+ break;
+ }
+ }
+
+ return iRes;
+}
+#endif /* unused */
+
+static void ataR3CmdOK(ATADevState *s, uint8_t status)
+{
+ s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
+ ataSetStatusValue(s, ATA_STAT_READY | status);
+}
+
+
+static void ataR3CmdError(ATADevState *s, uint8_t uErrorCode)
+{
+ Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
+ Assert(uErrorCode);
+ s->uATARegError = uErrorCode;
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+static uint32_t ataR3Checksum(void* ptr, size_t count)
+{
+ uint8_t u8Sum = 0xa5, *p = (uint8_t*)ptr;
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ {
+ u8Sum += *p++;
+ }
+
+ return (uint8_t)-(int32_t)u8Sum;
+}
+
+static bool ataR3IdentifySS(ATADevState *s)
+{
+ uint16_t *p;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)s->CTX_SUFF(pbIOBuffer);
+ memset(p, 0, 512);
+ p[0] = RT_H2LE_U16(0x0040);
+ p[1] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+ p[3] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+ /* Block size; obsolete, but required for the BIOS. */
+ p[5] = RT_H2LE_U16(s->cbSector);
+ p[6] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+ ataR3PadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+ ataR3PadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataR3PadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+# if ATA_MAX_MULT_SECTORS > 1
+ p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+# endif
+ p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+ p[54] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+ p[55] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+ p[56] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+ p[57] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+ * s->PCHSGeometry.cHeads
+ * s->PCHSGeometry.cSectors);
+ p[58] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
+ * s->PCHSGeometry.cHeads
+ * s->PCHSGeometry.cSectors >> 16);
+ if (s->cMultSectors)
+ p[59] = RT_H2LE_U16(0x100 | s->cMultSectors);
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ {
+ p[60] = RT_H2LE_U16(s->cTotalSectors);
+ p[61] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ }
+ else
+ {
+ /* Report maximum number of sectors possible with LBA28 */
+ p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+ p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+ }
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ if ( s->pDrvMedia->pfnDiscard
+ || s->cbSector != 512
+ || s->pDrvMedia->pfnIsNonRotational(s->pDrvMedia))
+ {
+ p[80] = RT_H2LE_U16(0x1f0); /* support everything up to ATA/ATAPI-8 ACS */
+ p[81] = RT_H2LE_U16(0x28); /* conforms to ATA/ATAPI-8 ACS */
+ }
+ else
+ {
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ }
+ p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 12); /* supports FLUSH CACHE */
+ else
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[86] = RT_H2LE_U16(1 << 12); /* enabled FLUSH CACHE */
+ else
+ p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ if (s->cTotalSectors > (1 << 28) - 1)
+ {
+ p[100] = RT_H2LE_U16(s->cTotalSectors);
+ p[101] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ p[102] = RT_H2LE_U16(s->cTotalSectors >> 32);
+ p[103] = RT_H2LE_U16(s->cTotalSectors >> 48);
+ }
+
+ if (s->cbSector != 512)
+ {
+ uint32_t cSectorSizeInWords = s->cbSector / sizeof(uint16_t);
+ /* Enable reporting of logical sector size. */
+ p[106] |= RT_H2LE_U16(RT_BIT(12) | RT_BIT(14));
+ p[117] = RT_H2LE_U16(cSectorSizeInWords);
+ p[118] = RT_H2LE_U16(cSectorSizeInWords >> 16);
+ }
+
+ if (s->pDrvMedia->pfnDiscard) /** @todo Set bit 14 in word 69 too? (Deterministic read after TRIM). */
+ p[169] = RT_H2LE_U16(1); /* DATA SET MANAGEMENT command supported. */
+ if (s->pDrvMedia->pfnIsNonRotational(s->pDrvMedia))
+ p[217] = RT_H2LE_U16(1); /* Non-rotational medium */
+ uint32_t uCsum = ataR3Checksum(p, 510);
+ p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static bool ataR3FlushSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE);
+ Assert(!s->cbElementaryTransfer);
+
+ ataR3LockLeave(pCtl);
+
+ STAM_PROFILE_START(&s->StatFlushes, f);
+ rc = s->pDrvMedia->pfnFlush(s->pDrvMedia);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&s->StatFlushes, f);
+
+ ataR3LockEnter(pCtl);
+ ataR3CmdOK(s, 0);
+ return false;
+}
+
+static bool atapiR3IdentifySS(ATADevState *s)
+{
+ uint16_t *p;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)s->CTX_SUFF(pbIOBuffer);
+ memset(p, 0, 512);
+ /* Removable CDROM, 3ms response, 12 byte packets */
+ p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 0 << 5 | 0 << 0);
+ ataR3PadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ ataR3PadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataR3PadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+ p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+ p[75] = RT_H2LE_U16(1); /* queue depth 1 */
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+ p[83] = RT_H2LE_U16(1 << 14);
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+ p[86] = RT_H2LE_U16(0);
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ /* According to ATAPI-5 spec:
+ *
+ * The use of this word is optional.
+ * If bits 7:0 of this word contain the signature A5h, bits 15:8
+ * contain the data
+ * structure checksum.
+ * The data structure checksum is the twos complement of the sum of
+ * all bytes in words 0 through 254 and the byte consisting of
+ * bits 7:0 in word 255.
+ * Each byte shall be added with unsigned arithmetic,
+ * and overflow shall be ignored.
+ * The sum of all 512 bytes is zero when the checksum is correct.
+ */
+ uint32_t uCsum = ataR3Checksum(p, 510);
+ p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static void ataR3SetSignature(ATADevState *s)
+{
+ s->uATARegSelect &= 0xf0; /* clear head */
+ /* put signature */
+ s->uATARegNSector = 1;
+ s->uATARegSector = 1;
+ if (s->fATAPI)
+ {
+ s->uATARegLCyl = 0x14;
+ s->uATARegHCyl = 0xeb;
+ }
+ else if (s->pDrvMedia)
+ {
+ s->uATARegLCyl = 0;
+ s->uATARegHCyl = 0;
+ }
+ else
+ {
+ s->uATARegLCyl = 0xff;
+ s->uATARegHCyl = 0xff;
+ }
+}
+
+
+static uint64_t ataR3GetSector(ATADevState *s)
+{
+ uint64_t iLBA;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ iLBA = ((uint64_t)s->uATARegHCylHOB << 40) |
+ ((uint64_t)s->uATARegLCylHOB << 32) |
+ ((uint64_t)s->uATARegSectorHOB << 24) |
+ ((uint64_t)s->uATARegHCyl << 16) |
+ ((uint64_t)s->uATARegLCyl << 8) |
+ s->uATARegSector;
+ }
+ else
+ {
+ /* LBA */
+ iLBA = ((s->uATARegSelect & 0x0f) << 24) | (s->uATARegHCyl << 16) |
+ (s->uATARegLCyl << 8) | s->uATARegSector;
+ }
+ }
+ else
+ {
+ /* CHS */
+ iLBA = ((s->uATARegHCyl << 8) | s->uATARegLCyl) * s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors +
+ (s->uATARegSelect & 0x0f) * s->PCHSGeometry.cSectors +
+ (s->uATARegSector - 1);
+ }
+ return iLBA;
+}
+
+static void ataR3SetSector(ATADevState *s, uint64_t iLBA)
+{
+ uint32_t cyl, r;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ s->uATARegHCylHOB = iLBA >> 40;
+ s->uATARegLCylHOB = iLBA >> 32;
+ s->uATARegSectorHOB = iLBA >> 24;
+ s->uATARegHCyl = iLBA >> 16;
+ s->uATARegLCyl = iLBA >> 8;
+ s->uATARegSector = iLBA;
+ }
+ else
+ {
+ /* LBA */
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | (iLBA >> 24);
+ s->uATARegHCyl = (iLBA >> 16);
+ s->uATARegLCyl = (iLBA >> 8);
+ s->uATARegSector = (iLBA);
+ }
+ }
+ else
+ {
+ /* CHS */
+ cyl = iLBA / (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+ r = iLBA % (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
+ s->uATARegHCyl = cyl >> 8;
+ s->uATARegLCyl = cyl;
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->PCHSGeometry.cSectors) & 0x0f);
+ s->uATARegSector = (r % s->PCHSGeometry.cSectors) + 1;
+ }
+}
+
+
+static void ataR3WarningDiskFull(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: Host disk full\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_DISKFULL",
+ N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
+ AssertRC(rc);
+}
+
+static void ataR3WarningFileTooBig(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: File too big\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_FILETOOBIG",
+ N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
+ AssertRC(rc);
+}
+
+static void ataR3WarningISCSI(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: iSCSI target unavailable\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_ISCSIDOWN",
+ N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+static bool ataR3IsRedoSetWarning(ATADevState *s, int rc)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ Assert(!PDMCritSectIsOwner(&pCtl->lock));
+ if (rc == VERR_DISK_FULL)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningDiskFull(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ pCtl->fRedoIdle = true;
+ /* iSCSI connection abort (first error) or failure to reestablish
+ * connection (second error). Pause VM. On resume we'll retry. */
+ ataR3WarningISCSI(ATADEVSTATE_2_DEVINS(s));
+ return true;
+ }
+ if (rc == VERR_VD_DEK_MISSING)
+ {
+ /* Error message already set. */
+ pCtl->fRedoIdle = true;
+ return true;
+ }
+
+ return false;
+}
+
+
+static int ataR3ReadSectors(ATADevState *s, uint64_t u64Sector, void *pvBuf,
+ uint32_t cSectors, bool *pfRedo)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ ataR3LockLeave(pCtl);
+
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ rc = s->pDrvMedia->pfnRead(s->pDrvMedia, u64Sector * s->cbSector, pvBuf, cSectors * s->cbSector);
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+ Log4(("ataR3ReadSectors: rc=%Rrc cSectors=%#x u64Sector=%llu\n%.*Rhxd\n",
+ rc, cSectors, u64Sector, cSectors * s->cbSector, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cSectors * s->cbSector);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(s, rc);
+
+ ataR3LockEnter(pCtl);
+ return rc;
+}
+
+
+static int ataR3WriteSectors(ATADevState *s, uint64_t u64Sector,
+ const void *pvBuf, uint32_t cSectors, bool *pfRedo)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ ataR3LockLeave(pCtl);
+
+ STAM_PROFILE_ADV_START(&s->StatWrites, w);
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+# ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_START(&s->StatInstrVDWrites, vw);
+# endif
+ rc = s->pDrvMedia->pfnWrite(s->pDrvMedia, u64Sector * s->cbSector, pvBuf, cSectors * s->cbSector);
+# ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_STOP(&s->StatInstrVDWrites, vw);
+# endif
+ s->Led.Actual.s.fWriting = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatWrites, w);
+ Log4(("ataR3WriteSectors: rc=%Rrc cSectors=%#x u64Sector=%llu\n%.*Rhxd\n",
+ rc, cSectors, u64Sector, cSectors * s->cbSector, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cSectors * s->cbSector);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(s, rc);
+
+ ataR3LockEnter(pCtl);
+ return rc;
+}
+
+
+static void ataR3ReadWriteSectorsBT(ATADevState *s)
+{
+ uint32_t cSectors;
+
+ cSectors = s->cbTotalTransfer / s->cbSector;
+ if (cSectors > s->cSectorsPerIRQ)
+ s->cbElementaryTransfer = s->cSectorsPerIRQ * s->cbSector;
+ else
+ s->cbElementaryTransfer = cSectors * s->cbSector;
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ ataR3CmdOK(s, 0);
+}
+
+
+static bool ataR3ReadSectorsSS(ATADevState *s)
+{
+ int rc;
+ uint32_t cSectors;
+ uint64_t iLBA;
+ bool fRedo;
+
+ cSectors = s->cbElementaryTransfer / s->cbSector;
+ Assert(cSectors);
+ iLBA = ataR3GetSector(s);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3ReadSectors(s, iLBA, s->CTX_SUFF(pbIOBuffer), cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ ataR3SetSector(s, iLBA + cSectors);
+ if (s->cbElementaryTransfer == s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk read error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(s, ID_ERR);
+ }
+ return false;
+}
+
+
+static bool ataR3WriteSectorsSS(ATADevState *s)
+{
+ int rc;
+ uint32_t cSectors;
+ uint64_t iLBA;
+ bool fRedo;
+
+ cSectors = s->cbElementaryTransfer / s->cbSector;
+ Assert(cSectors);
+ iLBA = ataR3GetSector(s);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3WriteSectors(s, iLBA, s->CTX_SUFF(pbIOBuffer), cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ ataR3SetSector(s, iLBA + cSectors);
+ if (!s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk write error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(s, ID_ERR);
+ }
+ return false;
+}
+
+
+static void atapiR3CmdOK(ATADevState *s)
+{
+ s->uATARegError = 0;
+ ataSetStatusValue(s, ATA_STAT_READY);
+ s->uATARegNSector = (s->uATARegNSector & ~7)
+ | ((s->uTxDir != PDMMEDIATXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
+ | (!s->cbTotalTransfer ? ATAPI_INT_REASON_CD : 0);
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ s->abATAPISense[0] = 0x70 | (1 << 7);
+ s->abATAPISense[7] = 10;
+}
+
+
+static void atapiR3CmdError(ATADevState *s, const uint8_t *pabATAPISense, size_t cbATAPISense)
+{
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
+ pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
+ s->uATARegError = pabATAPISense[2] << 4;
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ memcpy(s->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(s->abATAPISense)));
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/** @todo deprecated function - doesn't provide enough info. Replace by direct
+ * calls to atapiR3CmdError() with full data. */
+static void atapiR3CmdErrorSimple(ATADevState *s, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ memset(abATAPISense, '\0', sizeof(abATAPISense));
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = uATAPISenseKey & 0x0f;
+ abATAPISense[7] = 10;
+ abATAPISense[12] = uATAPIASC;
+ atapiR3CmdError(s, abATAPISense, sizeof(abATAPISense));
+}
+
+
+static void atapiR3CmdBT(ATADevState *s)
+{
+ s->fATAPITransfer = true;
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->cbAtapiPassthroughTransfer = s->cbTotalTransfer;
+ s->cbPIOTransferLimit = s->uATARegLCyl | (s->uATARegHCyl << 8);
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ atapiR3CmdOK(s);
+}
+
+
+static void atapiR3PassthroughCmdBT(ATADevState *s)
+{
+ atapiR3CmdBT(s);
+}
+
+static bool atapiR3ReadSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc = VINF_SUCCESS;
+ uint32_t cbTransfer, cSectors;
+ uint64_t cbBlockRegion = 0;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ cbTransfer = RT_MIN(s->cbTotalTransfer, s->cbIOBuffer);
+ cSectors = cbTransfer / s->cbATAPISector;
+ Assert(cSectors * s->cbATAPISector <= cbTransfer);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, s->iATAPILBA));
+
+ ataR3LockLeave(pCtl);
+
+ rc = s->pDrvMedia->pfnQueryRegionPropertiesForLba(s->pDrvMedia, s->iATAPILBA, NULL, NULL,
+ &cbBlockRegion, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+
+ /* If the region block size and requested sector matches we can just pass the request through. */
+ if (cbBlockRegion == s->cbATAPISector)
+ rc = s->pDrvMedia->pfnRead(s->pDrvMedia, (uint64_t)s->iATAPILBA * s->cbATAPISector,
+ s->CTX_SUFF(pbIOBuffer), s->cbATAPISector * cSectors);
+ else
+ {
+ if (cbBlockRegion == 2048 && s->cbATAPISector == 2352)
+ {
+ /* Generate the sync bytes. */
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
+ {
+ /* Sync bytes, see 4.2.3.8 CD Main Channel Block Formats */
+ *pbBuf++ = 0x00;
+ memset(pbBuf, 0xff, 10);
+ pbBuf += 10;
+ *pbBuf++ = 0x00;
+ /* MSF */
+ scsiLBA2MSF(pbBuf, i);
+ pbBuf += 3;
+ *pbBuf++ = 0x01; /* mode 1 data */
+ /* data */
+ rc = s->pDrvMedia->pfnRead(s->pDrvMedia, (uint64_t)i * 2048, pbBuf, 2048);
+ if (RT_FAILURE(rc))
+ break;
+ pbBuf += 2048;
+ /**
+ * @todo: maybe compute ECC and parity, layout is:
+ * 2072 4 EDC
+ * 2076 172 P parity symbols
+ * 2248 104 Q parity symbols
+ */
+ memset(pbBuf, 0, 280);
+ pbBuf += 280;
+ }
+ }
+ else if (cbBlockRegion == 2352 && s->cbATAPISector == 2048)
+ {
+ /* Read only the user data portion. */
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
+ {
+ uint8_t abTmp[2352];
+ rc = s->pDrvMedia->pfnRead(s->pDrvMedia, (uint64_t)i * 2352, &abTmp[0], 2352);
+ if (RT_FAILURE(rc))
+ break;
+
+ memcpy(pbBuf, &abTmp[16], 2048);
+ pbBuf += 2048;
+ }
+ }
+ }
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+ }
+
+ ataR3LockEnter(pCtl);
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, s->cbATAPISector * cSectors);
+
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ s->cbElementaryTransfer = cbTransfer;
+ if (cbTransfer >= s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ s->iATAPILBA += cSectors;
+ }
+ else
+ {
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM read error, %d sectors at LBA %d\n", s->iLUN, cSectors, s->iATAPILBA));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
+ }
+ return false;
+}
+
+/**
+ * Sets the given media track type.
+ */
+static uint32_t ataR3MediumTypeSet(ATADevState *s, uint32_t MediaTrackType)
+{
+ return ASMAtomicXchgU32(&s->MediaTrackType, MediaTrackType);
+}
+
+static bool atapiR3PassthroughSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc = VINF_SUCCESS;
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ uint32_t cbTransfer;
+ PSTAMPROFILEADV pProf = NULL;
+
+ cbTransfer = RT_MIN(s->cbAtapiPassthroughTransfer, s->cbIOBuffer);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+
+ /* Simple heuristics: if there is at least one sector of data
+ * to transfer, it's worth updating the LEDs. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ pProf = &s->StatReads;
+ }
+ else
+ {
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ pProf = &s->StatWrites;
+ }
+ }
+
+ ataR3LockLeave(pCtl);
+
+# if defined(LOG_ENABLED)
+ char szBuf[1024];
+
+ memset(szBuf, 0, sizeof(szBuf));
+
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_MODE_SELECT_10:
+ {
+ size_t cbBlkDescLength = scsiBE2H_U16(&s->CTX_SUFF(pbIOBuffer)[6]);
+
+ SCSILogModePage(szBuf, sizeof(szBuf) - 1,
+ s->CTX_SUFF(pbIOBuffer) + 8 + cbBlkDescLength,
+ cbTransfer - 8 - cbBlkDescLength);
+ break;
+ }
+ case SCSI_SEND_CUE_SHEET:
+ {
+ SCSILogCueSheet(szBuf, sizeof(szBuf) - 1,
+ s->CTX_SUFF(pbIOBuffer), cbTransfer);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Log2(("%s\n", szBuf));
+# endif
+
+ if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
+ if ( cbTransfer > SCSI_MAX_BUFFER_SIZE
+ || s->cbElementaryTransfer > s->cbIOBuffer)
+ {
+ /* Linux accepts commands with up to 100KB of data, but expects
+ * us to handle commands with up to 128KB of data. The usual
+ * imbalance of powers. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ uint32_t iATAPILBA, cSectors, cReqSectors, cbCurrTX;
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ uint32_t cSectorsMax; /**< Maximum amount of sectors to read without exceeding the I/O buffer. */
+
+ Assert(s->cbATAPISector);
+ cSectorsMax = cbTransfer / s->cbATAPISector;
+ Assert(cSectorsMax * s->cbATAPISector <= s->cbIOBuffer);
+
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = scsiBE2H_U32(s->aATAPICmd + 2);
+ cSectors = scsiBE2H_U16(s->aATAPICmd + 7);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ iATAPILBA = scsiBE2H_U32(s->aATAPICmd + 2);
+ cSectors = scsiBE2H_U32(s->aATAPICmd + 6);
+ break;
+ case SCSI_READ_CD:
+ iATAPILBA = scsiBE2H_U32(s->aATAPICmd + 2);
+ cSectors = scsiBE2H_U24(s->aATAPICmd + 6);
+ break;
+ case SCSI_READ_CD_MSF:
+ iATAPILBA = scsiMSF2LBA(s->aATAPICmd + 3);
+ cSectors = scsiMSF2LBA(s->aATAPICmd + 6) - iATAPILBA;
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->aATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ ataR3LockEnter(pCtl);
+ return false;
+ }
+ cSectorsMax = RT_MIN(cSectorsMax, cSectors);
+ memcpy(aATAPICmd, s->aATAPICmd, ATAPI_PACKET_SIZE);
+ cReqSectors = 0;
+ for (uint32_t i = cSectorsMax; i > 0; i -= cReqSectors)
+ {
+ if (i * s->cbATAPISector > SCSI_MAX_BUFFER_SIZE)
+ cReqSectors = SCSI_MAX_BUFFER_SIZE / s->cbATAPISector;
+ else
+ cReqSectors = i;
+ cbCurrTX = s->cbATAPISector * cReqSectors;
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(aATAPICmd + 7, cReqSectors);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(aATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(aATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(aATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
+ break;
+ }
+ rc = s->pDrvMedia->pfnSendCmd(s->pDrvMedia, aATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (rc != VINF_SUCCESS)
+ break;
+ iATAPILBA += cReqSectors;
+ pbBuf += s->cbATAPISector * cReqSectors;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Adjust ATAPI command for the next call. */
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(s->aATAPICmd + 7, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(s->aATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(s->aATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(s->aATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(s->aATAPICmd + 6, iATAPILBA + cSectors - cSectorsMax);
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->aATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ return false;
+ }
+ }
+ }
+ else
+ rc = s->pDrvMedia->pfnSendCmd(s->pDrvMedia, s->aATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ s->CTX_SUFF(pbIOBuffer), &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
+
+ ataR3LockEnter(pCtl);
+
+ /* Update the LEDs and the read/write statistics. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ s->Led.Actual.s.fReading = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbTransfer);
+ }
+ else
+ {
+ s->Led.Actual.s.fWriting = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cbTransfer);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Do post processing for certain commands. */
+ switch (s->aATAPICmd[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ if (!s->pTrackList)
+ rc = ATAPIPassthroughTrackListCreateEmpty(&s->pTrackList);
+
+ if (RT_SUCCESS(rc))
+ rc = ATAPIPassthroughTrackListUpdate(s->pTrackList, s->aATAPICmd, s->CTX_SUFF(pbIOBuffer));
+
+ if ( RT_FAILURE(rc)
+ && s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("ATA: Error (%Rrc) while updating the tracklist during %s, burning the disc might fail\n",
+ rc, s->aATAPICmd[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
+ break;
+ }
+ case SCSI_SYNCHRONIZE_CACHE:
+ {
+ if (s->pTrackList)
+ ATAPIPassthroughTrackListClear(s->pTrackList);
+ break;
+ }
+ }
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ /*
+ * Reply with the same amount of data as the real drive
+ * but only if the command wasn't split.
+ */
+ if (s->cbAtapiPassthroughTransfer < s->cbIOBuffer)
+ s->cbTotalTransfer = cbTransfer;
+
+ if ( s->aATAPICmd[0] == SCSI_INQUIRY
+ && s->fOverwriteInquiry)
+ {
+ /* Make sure that the real drive cannot be identified.
+ * Motivation: changing the VM configuration should be as
+ * invisible as possible to the guest. */
+ Log3(("ATAPI PT inquiry data before (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+ scsiPadStr(s->CTX_SUFF(pbIOBuffer) + 8, "VBOX", 8);
+ scsiPadStr(s->CTX_SUFF(pbIOBuffer) + 16, "CD-ROM", 16);
+ scsiPadStr(s->CTX_SUFF(pbIOBuffer) + 32, "1.0", 4);
+ }
+
+ if (cbTransfer)
+ Log3(("ATAPI PT data read (%d):\n%.*Rhxd\n", cbTransfer, cbTransfer, s->CTX_SUFF(pbIOBuffer)));
+ }
+
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ Assert(cbTransfer <= s->cbAtapiPassthroughTransfer);
+ s->cbElementaryTransfer = cbTransfer;
+ s->cbAtapiPassthroughTransfer -= cbTransfer;
+ if (!s->cbAtapiPassthroughTransfer)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ }
+ }
+ else
+ {
+ if (s->cErrors < MAX_LOG_REL_ERRORS)
+ {
+ uint8_t u8Cmd = s->aATAPICmd[0];
+ do
+ {
+ /* don't log superfluous errors */
+ if ( rc == VERR_DEV_IO_ERROR
+ && ( u8Cmd == SCSI_TEST_UNIT_READY
+ || u8Cmd == SCSI_READ_CAPACITY
+ || u8Cmd == SCSI_READ_DVD_STRUCTURE
+ || u8Cmd == SCSI_READ_TOC_PMA_ATIP))
+ break;
+ s->cErrors++;
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
+ s->iLUN, u8Cmd, abATAPISense[2] & 0x0f, abATAPISense[12], abATAPISense[13], rc));
+ } while (0);
+ }
+ atapiR3CmdError(s, abATAPISense, sizeof(abATAPISense));
+ }
+ return false;
+}
+
+/** @todo Revise ASAP. */
+static bool atapiR3ReadDVDStructureSS(ATADevState *s)
+{
+ uint8_t *buf = s->CTX_SUFF(pbIOBuffer);
+ int media = s->aATAPICmd[1];
+ int format = s->aATAPICmd[7];
+
+ uint16_t max_len = scsiBE2H_U16(&s->aATAPICmd[8]);
+
+ memset(buf, 0, max_len);
+
+ switch (format) {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x0e:
+ case 0x0f:
+ case 0x10:
+ case 0x11:
+ case 0x30:
+ case 0x31:
+ case 0xff:
+ if (media == 0)
+ {
+ int uASC = SCSI_ASC_NONE;
+
+ switch (format)
+ {
+ case 0x0: /* Physical format information */
+ {
+ int layer = s->aATAPICmd[6];
+ uint64_t total_sectors;
+
+ if (layer != 0)
+ {
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+ }
+
+ total_sectors = s->cTotalSectors;
+ total_sectors >>= 2;
+ if (total_sectors == 0)
+ {
+ uASC = -SCSI_ASC_MEDIUM_NOT_PRESENT;
+ break;
+ }
+
+ buf[4] = 1; /* DVD-ROM, part version 1 */
+ buf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ buf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ buf[7] = 0; /* default densities */
+
+ /* FIXME: 0x30000 per spec? */
+ scsiH2BE_U32(buf + 8, 0); /* start sector */
+ scsiH2BE_U32(buf + 12, total_sectors - 1); /* end sector */
+ scsiH2BE_U32(buf + 16, total_sectors - 1); /* l0 end sector */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U32(&buf[0], 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ }
+ case 0x01: /* DVD copyright information */
+ buf[4] = 0; /* no copyright data */
+ buf[5] = 0; /* no region restrictions */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 4 + 2);
+
+ /* 4 byte header + 4 byte data */
+ uASC = (4 + 4);
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+
+ case 0x04: /* DVD disc manufacturing information */
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ case 0xff:
+ /*
+ * This lists all the command capabilities above. Add new ones
+ * in order and update the length and buffer return values.
+ */
+
+ buf[4] = 0x00; /* Physical format */
+ buf[5] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 6), 2048 + 4);
+
+ buf[8] = 0x01; /* Copyright info */
+ buf[9] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 10), 4 + 4);
+
+ buf[12] = 0x03; /* BCA info */
+ buf[13] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 14), 188 + 4);
+
+ buf[16] = 0x04; /* Manufacturing info */
+ buf[17] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 18), 2048 + 4);
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 16 + 2);
+
+ /* data written + 4 byte header */
+ uASC = (16 + 4);
+ break;
+ default: /** @todo formats beyond DVD-ROM requires */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ }
+
+ if (uASC < 0)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, -uASC);
+ return false;
+ }
+ break;
+ }
+ /** @todo BD support, fall through for now */
+ RT_FALL_THRU();
+
+ /* Generic disk structures */
+ case 0x80: /** @todo AACS volume identifier */
+ case 0x81: /** @todo AACS media serial number */
+ case 0x82: /** @todo AACS media identifier */
+ case 0x83: /** @todo AACS media key block */
+ case 0x90: /** @todo List of recognized format layers */
+ case 0xc0: /** @todo Write protection status */
+ default:
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+static bool atapiR3ReadSectors(ATADevState *s, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+ Assert(cSectors > 0);
+ s->iATAPILBA = iATAPILBA;
+ s->cbATAPISector = cbSector;
+ ataR3StartTransfer(s, cSectors * cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
+ return false;
+}
+
+
+static bool atapiR3ReadCapacitySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ scsiH2BE_U32(pbBuf, s->cTotalSectors - 1);
+ scsiH2BE_U32(pbBuf + 4, 2048);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ReadDiscInformationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 34);
+ memset(pbBuf, '\0', 34);
+ scsiH2BE_U16(pbBuf, 32);
+ pbBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+ pbBuf[3] = 1; /* number of first track */
+ pbBuf[4] = 1; /* number of sessions (LSB) */
+ pbBuf[5] = 1; /* first track number in last session (LSB) */
+ pbBuf[6] = (uint8_t)s->pDrvMedia->pfnGetRegionCount(s->pDrvMedia); /* last track number in last session (LSB) */
+ pbBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+ pbBuf[8] = 0; /* disc type = CD-ROM */
+ pbBuf[9] = 0; /* number of sessions (MSB) */
+ pbBuf[10] = 0; /* number of sessions (MSB) */
+ pbBuf[11] = 0; /* number of sessions (MSB) */
+ scsiH2BE_U32(pbBuf + 16, 0xffffffff); /* last session lead-in start time is not available */
+ scsiH2BE_U32(pbBuf + 20, 0xffffffff); /* last possible start time for lead-out is not available */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ReadTrackInformationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ uint32_t u32LogAddr = scsiBE2H_U32(&s->aATAPICmd[2]);
+ uint8_t u8LogAddrType = s->aATAPICmd[1] & 0x03;
+
+ int rc = VINF_SUCCESS;
+ uint64_t u64LbaStart = 0;
+ uint32_t uRegion = 0;
+ uint64_t cBlocks = 0;
+ uint64_t cbBlock = 0;
+ uint8_t u8DataMode = 0xf; /* Unknown data mode. */
+ uint8_t u8TrackMode = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+
+ switch (u8LogAddrType)
+ {
+ case 0x00:
+ rc = s->pDrvMedia->pfnQueryRegionPropertiesForLba(s->pDrvMedia, u32LogAddr, &uRegion,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = s->pDrvMedia->pfnQueryRegionProperties(s->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ break;
+ case 0x01:
+ {
+ if (u32LogAddr >= 1)
+ {
+ uRegion = u32LogAddr - 1;
+ rc = s->pDrvMedia->pfnQueryRegionProperties(s->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ }
+ else
+ rc = VERR_NOT_FOUND; /** @todo Return lead-in information. */
+ break;
+ }
+ case 0x02:
+ default:
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ switch (enmDataForm)
+ {
+ case VDREGIONDATAFORM_MODE1_2048:
+ case VDREGIONDATAFORM_MODE1_2352:
+ case VDREGIONDATAFORM_MODE1_0:
+ u8DataMode = 1;
+ break;
+ case VDREGIONDATAFORM_XA_2336:
+ case VDREGIONDATAFORM_XA_2352:
+ case VDREGIONDATAFORM_XA_0:
+ case VDREGIONDATAFORM_MODE2_2336:
+ case VDREGIONDATAFORM_MODE2_2352:
+ case VDREGIONDATAFORM_MODE2_0:
+ u8DataMode = 2;
+ break;
+ default:
+ u8DataMode = 0xf;
+ }
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ u8TrackMode = 0x0;
+ else
+ u8TrackMode = 0x4;
+
+ memset(pbBuf, '\0', 36);
+ scsiH2BE_U16(pbBuf, 34);
+ pbBuf[2] = uRegion + 1; /* track number (LSB) */
+ pbBuf[3] = 1; /* session number (LSB) */
+ pbBuf[5] = (0 << 5) | (0 << 4) | u8TrackMode; /* not damaged, primary copy, data track */
+ pbBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | u8DataMode; /* not reserved track, not blank, not packet writing, not fixed packet */
+ pbBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+ scsiH2BE_U32(pbBuf + 8, (uint32_t)u64LbaStart); /* track start address is 0 */
+ scsiH2BE_U32(pbBuf + 24, (uint32_t)cBlocks); /* track size */
+ pbBuf[32] = 0; /* track number (MSB) */
+ pbBuf[33] = 0; /* session number (MSB) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureListProfiles(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 3*4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x0); /* feature 0: list of profiles supported */
+ pbBuf[2] = (0 << 2) | (1 << 1) | (1 << 0); /* version 0, persistent, current */
+ pbBuf[3] = 8; /* additional bytes for profiles */
+ /* The MMC-3 spec says that DVD-ROM read capability should be reported
+ * before CD-ROM read capability. */
+ scsiH2BE_U16(pbBuf + 4, 0x10); /* profile: read-only DVD */
+ pbBuf[6] = (0 << 0); /* NOT current profile */
+ scsiH2BE_U16(pbBuf + 8, 0x08); /* profile: read only CD */
+ pbBuf[10] = (1 << 0); /* current profile */
+
+ return 3*4; /* Header + 2 profiles entries */
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureCore(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1); /* feature 0001h: Core Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U16(pbBuf + 4, 0x00000002); /* Physical interface ATAPI. */
+ pbBuf[8] = RT_BIT(0); /* DBE */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureMorphing(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x2); /* feature 0002h: Morphing Feature */
+ pbBuf[2] = (0x1 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = RT_BIT(1) | 0x0; /* OCEvent | !ASYNC */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureRemovableMedium(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x3); /* feature 0003h: Removable Medium Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ /* Tray type loading | Load | Eject | !Pvnt Jmpr | !DBML | Lock */
+ pbBuf[4] = (0x2 << 5) | RT_BIT(4) | RT_BIT(3) | (0x0 << 2) | (0x0 << 1) | RT_BIT(0);
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureRandomReadable (ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x10); /* feature 0010h: Random Readable Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U32(pbBuf + 4, 2048); /* Logical block size. */
+ scsiH2BE_U16(pbBuf + 8, 0x10); /* Blocking (0x10 for DVD, CD is not defined). */
+ pbBuf[10] = 0; /* PP not present */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureCDRead(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1e); /* feature 001Eh: CD Read Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+ pbBuf[4] = (0x0 << 7) | (0x0 << 1) | 0x0; /* !DAP | !C2-Flags | !CD-Text. */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeaturePowerManagement(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x100); /* feature 0100h: Power Management Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+
+ return 4;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureTimeout(ATADevState *s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF1(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x105); /* feature 0105h: Timeout Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = 0x0; /* !Group3 */
+
+ return 8;
+}
+
+/**
+ * Callback to fill in the correct data for a feature.
+ *
+ * @returns Number of bytes written into the buffer.
+ * @param s The ATA device state.
+ * @param pbBuf The buffer to fill the data with.
+ * @param cbBuf Size of the buffer.
+ */
+typedef DECLCALLBACK(uint32_t) FNATAPIR3FEATUREFILL(ATADevState *s, uint8_t *pbBuf, size_t cbBuf);
+/** Pointer to a feature fill callback. */
+typedef FNATAPIR3FEATUREFILL *PFNATAPIR3FEATUREFILL;
+
+/**
+ * ATAPI feature descriptor.
+ */
+typedef struct ATAPIR3FEATDESC
+{
+ /** The feature number. */
+ uint16_t u16Feat;
+ /** The callback to fill in the correct data. */
+ PFNATAPIR3FEATUREFILL pfnFeatureFill;
+} ATAPIR3FEATDESC;
+
+/**
+ * Array of known ATAPI feature descriptors.
+ */
+static const ATAPIR3FEATDESC s_aAtapiR3Features[] =
+{
+ { 0x0000, atapiR3GetConfigurationFillFeatureListProfiles},
+ { 0x0001, atapiR3GetConfigurationFillFeatureCore},
+ { 0x0002, atapiR3GetConfigurationFillFeatureMorphing},
+ { 0x0003, atapiR3GetConfigurationFillFeatureRemovableMedium},
+ { 0x0010, atapiR3GetConfigurationFillFeatureRandomReadable},
+ { 0x001e, atapiR3GetConfigurationFillFeatureCDRead},
+ { 0x0100, atapiR3GetConfigurationFillFeaturePowerManagement},
+ { 0x0105, atapiR3GetConfigurationFillFeatureTimeout}
+};
+
+static bool atapiR3GetConfigurationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ uint32_t cbBuf = s->cbIOBuffer;
+ uint32_t cbCopied = 0;
+ uint16_t u16Sfn = scsiBE2H_U16(&s->aATAPICmd[2]);
+ uint8_t u8Rt = s->aATAPICmd[1] & 0x03;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 80);
+ /* Accept valid request types only. */
+ if (u8Rt == 3)
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ memset(pbBuf, '\0', cbBuf);
+ /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+ * way to differentiate them right now is based on the image size). */
+ if (s->cTotalSectors)
+ scsiH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
+ else
+ scsiH2BE_U16(pbBuf + 6, 0x00); /* current profile: none -> no media */
+ cbBuf -= 8;
+ pbBuf += 8;
+
+ if (u8Rt == 0x2)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aAtapiR3Features); i++)
+ {
+ if (s_aAtapiR3Features[i].u16Feat == u16Sfn)
+ {
+ cbCopied = s_aAtapiR3Features[i].pfnFeatureFill(s, pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aAtapiR3Features); i++)
+ {
+ if (s_aAtapiR3Features[i].u16Feat > u16Sfn)
+ {
+ cbCopied = s_aAtapiR3Features[i].pfnFeatureFill(s, pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ }
+ }
+ }
+
+ /* Set data length now - the field is not included in the final length. */
+ scsiH2BE_U32(s->CTX_SUFF(pbIOBuffer), s->cbIOBuffer - cbBuf - 4);
+
+ /* Other profiles we might want to add in the future: 0x40 (BD-ROM) and 0x50 (HDDVD-ROM) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3GetEventStatusNotificationSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+
+ if (!(s->aATAPICmd[1] & 1))
+ {
+ /* no asynchronous operation supported */
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ NewStatus = ATA_EVENT_STATUS_UNCHANGED;
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_NEW:
+ /* mount */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x02; /* new medium */
+ pbBuf[5] = 0x02; /* medium present / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* umount */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x03; /* media removal */
+ pbBuf[5] = 0x00; /* medium absent / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ if (OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED)
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+
+ case ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED: /* currently unused */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x01; /* eject requested (eject button pressed) */
+ pbBuf[5] = 0x02; /* medium present / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+
+ case ATA_EVENT_STATUS_UNCHANGED:
+ default:
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x01; /* operational change request / notification */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x00;
+ pbBuf[5] = 0x00;
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3InquirySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+ pbBuf[0] = 0x05; /* CD-ROM */
+ pbBuf[1] = 0x80; /* removable */
+# if 1/*ndef VBOX*/ /** @todo implement MESN + AENC. (async notification on removal and stuff.) */
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+# else
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x91; /* format 1, MESN=1, AENC=9 ??? */
+# endif
+ pbBuf[4] = 31; /* additional length */
+ pbBuf[5] = 0; /* reserved */
+ pbBuf[6] = 0; /* reserved */
+ pbBuf[7] = 0; /* reserved */
+ scsiPadStr(pbBuf + 8, s->szInquiryVendorId, 8);
+ scsiPadStr(pbBuf + 16, s->szInquiryProductId, 16);
+ scsiPadStr(pbBuf + 32, s->szInquiryRevision, 4);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ModeSenseErrorRecoverySS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 16);
+ scsiH2BE_U16(&pbBuf[0], 16 + 6);
+ pbBuf[2] = (uint8_t)s->MediaTrackType;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x01;
+ pbBuf[9] = 0x06;
+ pbBuf[10] = 0x00; /* Maximum error recovery */
+ pbBuf[11] = 0x05; /* 5 retries */
+ pbBuf[12] = 0x00;
+ pbBuf[13] = 0x00;
+ pbBuf[14] = 0x00;
+ pbBuf[15] = 0x00;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ModeSenseCDStatusSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 40);
+ scsiH2BE_U16(&pbBuf[0], 38);
+ pbBuf[2] = (uint8_t)s->MediaTrackType;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x2a;
+ pbBuf[9] = 30; /* page length */
+ pbBuf[10] = 0x08; /* DVD-ROM read support */
+ pbBuf[11] = 0x00; /* no write support */
+ /* The following claims we support audio play. This is obviously false,
+ * but the Linux generic CDROM support makes many features depend on this
+ * capability. If it's not set, this causes many things to be disabled. */
+ pbBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+ pbBuf[13] = 0x00; /* no subchannel reads supported */
+ pbBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+ if (s->pDrvMount->pfnIsLocked(s->pDrvMount))
+ pbBuf[14] |= 1 << 1; /* report lock state */
+ pbBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+ scsiH2BE_U16(&pbBuf[16], 5632); /* (obsolete) claim 32x speed support */
+ scsiH2BE_U16(&pbBuf[18], 2); /* number of audio volume levels */
+ scsiH2BE_U16(&pbBuf[20], s->cbIOBuffer / _1K); /* buffer size supported in Kbyte */
+ scsiH2BE_U16(&pbBuf[22], 5632); /* (obsolete) current read speed 32x */
+ pbBuf[24] = 0; /* reserved */
+ pbBuf[25] = 0; /* reserved for digital audio (see idx 15) */
+ scsiH2BE_U16(&pbBuf[26], 0); /* (obsolete) maximum write speed */
+ scsiH2BE_U16(&pbBuf[28], 0); /* (obsolete) current write speed */
+ scsiH2BE_U16(&pbBuf[30], 0); /* copy management revision supported 0=no CSS */
+ pbBuf[32] = 0; /* reserved */
+ pbBuf[33] = 0; /* reserved */
+ pbBuf[34] = 0; /* reserved */
+ pbBuf[35] = 1; /* rotation control CAV */
+ scsiH2BE_U16(&pbBuf[36], 0); /* current write speed */
+ scsiH2BE_U16(&pbBuf[38], 0); /* number of write speed performance descriptors */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3RequestSenseSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ memset(pbBuf, '\0', s->cbElementaryTransfer);
+ memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3MechanismStatusSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ scsiH2BE_U16(pbBuf, 0);
+ /* no current LBA */
+ pbBuf[2] = 0;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 1;
+ scsiH2BE_U16(pbBuf + 6, 0);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ReadTOCNormalSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer), *q, iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+ uint32_t cTracks = s->pDrvMedia->pfnGetRegionCount(s->pDrvMedia);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->aATAPICmd[6];
+ if (iStartTrack == 0)
+ iStartTrack = 1;
+
+ if (iStartTrack > cTracks && iStartTrack != 0xaa)
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ q = pbBuf + 2;
+ *q++ = iStartTrack; /* first track number */
+ *q++ = cTracks; /* last track number */
+ for (uint32_t iTrack = iStartTrack; iTrack <= cTracks; iTrack++)
+ {
+ uint64_t uLbaStart = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+
+ int rc = s->pDrvMedia->pfnQueryRegionProperties(s->pDrvMedia, iTrack - 1, &uLbaStart,
+ NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ *q++ = 0; /* reserved */
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ *q++ = 0x10; /* ADR, control */
+ else
+ *q++ = 0x14; /* ADR, control */
+
+ *q++ = (uint8_t)iTrack; /* track number */
+ *q++ = 0; /* reserved */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+
+ /* Query start and length of last track to get the start of the lead out track. */
+ uint64_t uLbaStart = 0;
+ uint64_t cBlocks = 0;
+
+ int rc = s->pDrvMedia->pfnQueryRegionProperties(s->pDrvMedia, cTracks - 1, &uLbaStart,
+ &cBlocks, NULL, NULL);
+ AssertRC(rc);
+
+ uLbaStart += cBlocks;
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ cbSize = q - pbBuf;
+ scsiH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ReadTOCMultiSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
+ bool fMSF;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 12);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ /* multi session: only a single session defined */
+ /** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R)
+ * with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being
+ * able to figure out whether numbers are in BCD or hex. */
+ memset(pbBuf, 0, 12);
+ pbBuf[1] = 0x0a;
+ pbBuf[2] = 0x01;
+ pbBuf[3] = 0x01;
+
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+ int rc = s->pDrvMedia->pfnQueryRegionProperties(s->pDrvMedia, 0, NULL,
+ NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ pbBuf[5] = 0x10; /* ADR, control */
+ else
+ pbBuf[5] = 0x14; /* ADR, control */
+
+ pbBuf[6] = 1; /* first track in last complete session */
+ if (fMSF)
+ {
+ pbBuf[8] = 0; /* reserved */
+ scsiLBA2MSF(&pbBuf[9], 0);
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(pbBuf + 8, 0);
+ }
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static bool atapiR3ReadTOCRawSS(ATADevState *s)
+{
+ uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer), *q, iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->aATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->aATAPICmd[6];
+
+ q = pbBuf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa0; /* first track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* first track */
+ *q++ = 0x00; /* disk type CD-DA or CD data */
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa1; /* last track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* last track */
+ *q++ = 0;
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa2; /* lead-out */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, s->cTotalSectors);
+ q += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(q, s->cTotalSectors);
+ q += 4;
+ }
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0; /* track number */
+ *q++ = 1; /* point */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, 0);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(q, 0);
+ q += 4;
+ }
+
+ cbSize = q - pbBuf;
+ scsiH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(s);
+ return false;
+}
+
+
+static void atapiR3ParseCmdVirtualATAPI(ATADevState *s)
+{
+ const uint8_t *pbPacket;
+ uint8_t *pbBuf;
+ uint32_t cbMax;
+
+ pbPacket = s->aATAPICmd;
+ pbBuf = s->CTX_SUFF(pbIOBuffer);
+ switch (pbPacket[0])
+ {
+ case SCSI_TEST_UNIT_READY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ if (s->cNotifiedMediaChange-- > 2)
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ else
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ }
+ else if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ atapiR3CmdOK(s);
+ else
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ break;
+ case SCSI_MODE_SENSE_6:
+ {
+ uint8_t uPageControl, uPageCode;
+ cbMax = pbPacket[4];
+ uPageControl = pbPacket[2] >> 6;
+ uPageCode = pbPacket[2] & 0x3f;
+ switch (uPageControl)
+ {
+ case SCSI_PAGECONTROL_CURRENT:
+ switch (uPageCode)
+ {
+ case SCSI_MODEPAGE_ERROR_RECOVERY:
+ ataR3StartTransfer(s, RT_MIN(cbMax, 16), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
+ break;
+ case SCSI_MODEPAGE_CD_STATUS:
+ ataR3StartTransfer(s, RT_MIN(cbMax, 40), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
+ break;
+ default:
+ goto error_cmd;
+ }
+ break;
+ case SCSI_PAGECONTROL_CHANGEABLE:
+ goto error_cmd;
+ case SCSI_PAGECONTROL_DEFAULT:
+ goto error_cmd;
+ default:
+ case SCSI_PAGECONTROL_SAVED:
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ break;
+ }
+ case SCSI_MODE_SENSE_10:
+ {
+ uint8_t uPageControl, uPageCode;
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ uPageControl = pbPacket[2] >> 6;
+ uPageCode = pbPacket[2] & 0x3f;
+ switch (uPageControl)
+ {
+ case SCSI_PAGECONTROL_CURRENT:
+ switch (uPageCode)
+ {
+ case SCSI_MODEPAGE_ERROR_RECOVERY:
+ ataR3StartTransfer(s, RT_MIN(cbMax, 16), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
+ break;
+ case SCSI_MODEPAGE_CD_STATUS:
+ ataR3StartTransfer(s, RT_MIN(cbMax, 40), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
+ break;
+ default:
+ goto error_cmd;
+ }
+ break;
+ case SCSI_PAGECONTROL_CHANGEABLE:
+ goto error_cmd;
+ case SCSI_PAGECONTROL_DEFAULT:
+ goto error_cmd;
+ default:
+ case SCSI_PAGECONTROL_SAVED:
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ break;
+ }
+ case SCSI_REQUEST_SENSE:
+ cbMax = pbPacket[4];
+ ataR3StartTransfer(s, RT_MIN(cbMax, 18), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ break;
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ if (pbPacket[4] & 1)
+ s->pDrvMount->pfnLock(s->pDrvMount);
+ else
+ s->pDrvMount->pfnUnlock(s->pDrvMount);
+ atapiR3CmdOK(s);
+ }
+ else
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ case SCSI_READ_10:
+ case SCSI_READ_12:
+ {
+ uint32_t cSectors, iATAPILBA;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if (pbPacket[0] == SCSI_READ_10)
+ cSectors = scsiBE2H_U16(pbPacket + 7);
+ else
+ cSectors = scsiBE2H_U32(pbPacket + 6);
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+
+ if (cSectors == 0)
+ {
+ atapiR3CmdOK(s);
+ break;
+ }
+
+ /* Check that the sector size is valid. */
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+ int rc = s->pDrvMedia->pfnQueryRegionPropertiesForLba(s->pDrvMedia, iATAPILBA,
+ NULL, NULL, NULL, &enmDataForm);
+ if (RT_UNLIKELY( rc == VERR_NOT_FOUND
+ || ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)))
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ else if ( enmDataForm != VDREGIONDATAFORM_MODE1_2048
+ && enmDataForm != VDREGIONDATAFORM_MODE1_2352
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2336
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2352
+ && enmDataForm != VDREGIONDATAFORM_RAW)
+ {
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ RT_ZERO(abATAPISense);
+
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = (SCSI_SENSE_ILLEGAL_REQUEST & 0x0f) | SCSI_SENSE_FLAG_ILI;
+ scsiH2BE_U32(&abATAPISense[3], iATAPILBA);
+ abATAPISense[7] = 10;
+ abATAPISense[12] = SCSI_ASC_ILLEGAL_MODE_FOR_THIS_TRACK;
+ atapiR3CmdError(s, &abATAPISense[0], sizeof(abATAPISense));
+ break;
+ }
+ atapiR3ReadSectors(s, iATAPILBA, cSectors, 2048);
+ break;
+ }
+ case SCSI_READ_CD:
+ {
+ uint32_t cSectors, iATAPILBA;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if ((pbPacket[10] & 0x7) != 0)
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+ if (cSectors == 0)
+ {
+ atapiR3CmdOK(s);
+ break;
+ }
+ if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ /*
+ * If the LBA is in an audio track we are required to ignore pretty much all
+ * of the channel selection values (except 0x00) and map everything to 0x10
+ * which means read user data with a sector size of 2352 bytes.
+ *
+ * (MMC-6 chapter 6.19.2.6)
+ */
+ uint8_t uChnSel = pbPacket[9] & 0xf8;
+ VDREGIONDATAFORM enmDataForm;
+ int rc = s->pDrvMedia->pfnQueryRegionPropertiesForLba(s->pDrvMedia, iATAPILBA,
+ NULL, NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ {
+ if (uChnSel == 0)
+ {
+ /* nothing */
+ atapiR3CmdOK(s);
+ }
+ else
+ atapiR3ReadSectors(s, iATAPILBA, cSectors, 2352);
+ }
+ else
+ {
+ switch (uChnSel)
+ {
+ case 0x00:
+ /* nothing */
+ atapiR3CmdOK(s);
+ break;
+ case 0x10:
+ /* normal read */
+ atapiR3ReadSectors(s, iATAPILBA, cSectors, 2048);
+ break;
+ case 0xf8:
+ /* read all data */
+ atapiR3ReadSectors(s, iATAPILBA, cSectors, 2352);
+ break;
+ default:
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM sector format not supported (%#x)\n", s->iLUN, pbPacket[9] & 0xf8));
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ }
+ break;
+ }
+ case SCSI_SEEK_10:
+ {
+ uint32_t iATAPILBA;
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+ if (iATAPILBA > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on seeking to places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", s->iLUN, (uint64_t)iATAPILBA));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ atapiR3CmdOK(s);
+ ataSetStatus(s, ATA_STAT_SEEK); /* Linux expects this. */
+ break;
+ }
+ case SCSI_START_STOP_UNIT:
+ {
+ int rc = VINF_SUCCESS;
+ switch (pbPacket[4] & 3)
+ {
+ case 0: /* 00 - Stop motor */
+ case 1: /* 01 - Start motor */
+ break;
+ case 2: /* 10 - Eject media */
+ {
+ /* This must be done from EMT. */
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ ataR3LockLeave(pCtl);
+ rc = VMR3ReqPriorityCallWait(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY,
+ (PFNRT)s->pDrvMount->pfnUnmount, 3,
+ s->pDrvMount, false /*=fForce*/, true /*=fEject*/);
+ Assert(RT_SUCCESS(rc) || rc == VERR_PDM_MEDIA_LOCKED || rc == VERR_PDM_MEDIA_NOT_MOUNTED);
+ if (RT_SUCCESS(rc) && pThis->pMediaNotify)
+ {
+ rc = VMR3ReqCallNoWait(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY,
+ (PFNRT)pThis->pMediaNotify->pfnEjected, 2,
+ pThis->pMediaNotify, s->iLUN);
+ AssertRC(rc);
+ }
+
+ ataR3LockEnter(pCtl);
+ break;
+ }
+ case 3: /* 11 - Load media */
+ /** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ atapiR3CmdOK(s);
+ else
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+ break;
+ }
+ case SCSI_MECHANISM_STATUS:
+ {
+ cbMax = scsiBE2H_U16(pbPacket + 8);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MECHANISM_STATUS, true);
+ break;
+ }
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ uint8_t format;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ /* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
+ * but Linux kernel uses offset 9 (topmost 2 bits). Hope that
+ * the other field is clear... */
+ format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
+ switch (format)
+ {
+ case 0:
+ ataR3StartTransfer(s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
+ break;
+ case 1:
+ ataR3StartTransfer(s, RT_MIN(cbMax, 12), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
+ break;
+ case 2:
+ ataR3StartTransfer(s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
+ break;
+ default:
+ error_cmd:
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ break;
+ }
+ case SCSI_READ_CAPACITY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ ataR3StartTransfer(s, 8, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_CAPACITY, true);
+ break;
+ case SCSI_READ_DISC_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 34), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DISC_INFORMATION, true);
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 36), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION, true);
+ break;
+ case SCSI_GET_CONFIGURATION:
+ /* No media change stuff here, it can confuse Linux guests. */
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 80), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_CONFIGURATION, true);
+ break;
+ case SCSI_INQUIRY:
+ cbMax = scsiBE2H_U16(pbPacket + 3);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 36), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_INQUIRY, true);
+ break;
+ case SCSI_READ_DVD_STRUCTURE:
+ {
+ cbMax = scsiBE2H_U16(pbPacket + 8);
+ ataR3StartTransfer(s, RT_MIN(cbMax, 4), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DVD_STRUCTURE, true);
+ break;
+ }
+ default:
+ atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ }
+}
+
+
+/*
+ * Parse ATAPI commands, passing them directly to the CD/DVD drive.
+ */
+static void atapiR3ParseCmdPassthrough(ATADevState *s)
+{
+ const uint8_t *pbPacket = &s->aATAPICmd[0];
+
+ /* Some cases we have to handle here. */
+ if ( pbPacket[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION
+ && ASMAtomicReadU32(&s->MediaEventStatus) != ATA_EVENT_STATUS_UNCHANGED)
+ {
+ uint32_t cbTransfer = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(s, RT_MIN(cbTransfer, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ }
+ else if ( pbPacket[0] == SCSI_REQUEST_SENSE
+ && (s->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
+ ataR3StartTransfer(s, RT_MIN(pbPacket[4], 18), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ else
+ {
+ size_t cbBuf = 0;
+ size_t cbATAPISector = 0;
+ size_t cbTransfer = 0;
+ PDMMEDIATXDIR uTxDir = PDMMEDIATXDIR_NONE;
+ uint8_t u8ScsiSts = SCSI_STATUS_OK;
+
+ if (pbPacket[0] == SCSI_FORMAT_UNIT || pbPacket[0] == SCSI_GET_PERFORMANCE)
+ cbBuf = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+
+ bool fPassthrough = ATAPIPassthroughParseCdb(pbPacket, sizeof(s->aATAPICmd), cbBuf, s->pTrackList,
+ &s->abATAPISense[0], sizeof(s->abATAPISense), &uTxDir, &cbTransfer,
+ &cbATAPISector, &u8ScsiSts);
+ if (fPassthrough)
+ {
+ s->cbATAPISector = (uint32_t)cbATAPISector;
+ Assert(s->cbATAPISector == (uint32_t)cbATAPISector);
+ Assert(cbTransfer == (uint32_t)cbTransfer);
+
+ /*
+ * Send a command to the drive, passing data in/out as required.
+ * Commands which exceed the I/O buffer size are split below
+ * or aborted if splitting is not implemented.
+ */
+ Log2(("ATAPI PT: max size %d\n", cbTransfer));
+ if (cbTransfer == 0)
+ uTxDir = PDMMEDIATXDIR_NONE;
+ ataR3StartTransfer(s, (uint32_t)cbTransfer, uTxDir, ATAFN_BT_ATAPI_PASSTHROUGH_CMD, ATAFN_SS_ATAPI_PASSTHROUGH, true);
+ }
+ else if (u8ScsiSts == SCSI_STATUS_CHECK_CONDITION)
+ {
+ /* Sense data is already set, end the request and notify the guest. */
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, s->abATAPISense[2] & 0x0f, SCSISenseText(s->abATAPISense[2] & 0x0f),
+ s->abATAPISense[12], s->abATAPISense[13], SCSISenseExtText(s->abATAPISense[12], s->abATAPISense[13])));
+ s->uATARegError = s->abATAPISense[2] << 4;
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+ }
+ else if (u8ScsiSts == SCSI_STATUS_OK)
+ atapiR3CmdOK(s);
+ }
+}
+
+
+static void atapiR3ParseCmd(ATADevState *s)
+{
+ const uint8_t *pbPacket;
+
+ pbPacket = s->aATAPICmd;
+# ifdef DEBUG
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0], SCSICmdText(pbPacket[0])));
+# else /* !DEBUG */
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0]));
+# endif /* !DEBUG */
+ Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, s->uATARegLCyl | (s->uATARegHCyl << 8), ATAPI_PACKET_SIZE, pbPacket));
+
+ if (s->fATAPIPassthrough)
+ atapiR3ParseCmdPassthrough(s);
+ else
+ atapiR3ParseCmdVirtualATAPI(s);
+}
+
+
+static bool ataR3PacketSS(ATADevState *s)
+{
+ s->fDMA = !!(s->uATARegFeature & 1);
+ memcpy(s->aATAPICmd, s->CTX_SUFF(pbIOBuffer), ATAPI_PACKET_SIZE);
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ atapiR3ParseCmd(s);
+ return false;
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
+ * from now on, regardless if there was a medium inserted or not.
+ */
+static void ataR3MediumRemoved(ATADevState *s)
+{
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_MEDIA_REMOVED);
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
+ * there was already a medium inserted, don't forget to send the "medium
+ * removed" event first.
+ */
+static void ataR3MediumInserted(ATADevState *s)
+{
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* no change, we will send "medium removed" + "medium inserted" */
+ NewStatus = ATA_EVENT_STATUS_MEDIA_CHANGED;
+ break;
+ default:
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnMountNotify}
+ */
+static DECLCALLBACK(void) ataR3MountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ ATADevState *pIf = RT_FROM_MEMBER(pInterface, ATADevState, IMountNotify);
+ Log(("%s: changing LUN#%d\n", __FUNCTION__, pIf->iLUN));
+
+ /* Ignore the call if we're called while being attached. */
+ if (!pIf->pDrvMedia)
+ return;
+
+ uint32_t cRegions = pIf->pDrvMedia->pfnGetRegionCount(pIf->pDrvMedia);
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ int rc = pIf->pDrvMedia->pfnQueryRegionProperties(pIf->pDrvMedia, i, NULL, &cBlocks,
+ NULL, NULL);
+ AssertRC(rc);
+ pIf->cTotalSectors += cBlocks;
+ }
+
+ LogRel(("PIIX3 ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough unchanged\n", pIf->iLUN, pIf->cTotalSectors));
+
+ /* Report media changed in TEST UNIT and other (probably incorrect) places. */
+ if (pIf->cNotifiedMediaChange < 2)
+ pIf->cNotifiedMediaChange = 1;
+ ataR3MediumInserted(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_TYPE_UNKNOWN);
+}
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnUnmountNotify}
+ */
+static DECLCALLBACK(void) ataR3UnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ ATADevState *pIf = RT_FROM_MEMBER(pInterface, ATADevState, IMountNotify);
+ Log(("%s:\n", __FUNCTION__));
+ pIf->cTotalSectors = 0;
+
+ /*
+ * Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
+ * However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
+ * So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
+ * present and 2 in which it is changed.
+ */
+ pIf->cNotifiedMediaChange = 1;
+ ataR3MediumRemoved(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_NO_DISC);
+}
+
+static void ataR3PacketBT(ATADevState *s)
+{
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->cbAtapiPassthroughTransfer = s->cbTotalTransfer;
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataSetStatusValue(s, ATA_STAT_READY);
+}
+
+
+static void ataR3ResetDevice(ATADevState *s)
+{
+ s->cMultSectors = ATA_MAX_MULT_SECTORS;
+ s->cNotifiedMediaChange = 0;
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
+ ASMAtomicWriteU32(&s->MediaTrackType, ATA_MEDIA_TYPE_UNKNOWN);
+ ataUnsetIRQ(s);
+
+ s->uATARegSelect = 0x20;
+ ataSetStatusValue(s, ATA_STAT_READY);
+ ataR3SetSignature(s);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+ s->fDMA = false;
+ s->fATAPITransfer = false;
+ s->uATATransferMode = ATA_MODE_UDMA | 2; /* PIIX3 supports only up to UDMA2 */
+
+ s->uATARegFeature = 0;
+}
+
+
+static bool ataR3ExecuteDeviceDiagnosticSS(ATADevState *s)
+{
+ ataR3SetSignature(s);
+ if (s->fATAPI)
+ ataSetStatusValue(s, 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_SEEK);
+ s->uATARegError = 0x01;
+ return false;
+}
+
+
+static bool ataR3InitDevParmSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ LogFlowFunc(("\n"));
+ LogRel(("ATA: LUN#%d: INITIALIZE DEVICE PARAMETERS: %u logical sectors, %u heads\n",
+ s->iLUN, s->uATARegNSector, s->uATARegSelect & 0x0f));
+ ataR3LockLeave(pCtl);
+ RTThreadSleep(pCtl->DelayIRQMillies);
+ ataR3LockEnter(pCtl);
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s);
+ return false;
+}
+
+
+static bool ataR3RecalibrateSS(ATADevState *s)
+{
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+
+ LogFlowFunc(("\n"));
+ ataR3LockLeave(pCtl);
+ RTThreadSleep(pCtl->DelayIRQMillies);
+ ataR3LockEnter(pCtl);
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s);
+ return false;
+}
+
+
+static int ataR3TrimSectors(ATADevState *s, uint64_t u64Sector, uint32_t cSectors,
+ bool *pfRedo)
+{
+ RTRANGE TrimRange;
+ PATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
+ int rc;
+
+ ataR3LockLeave(pCtl);
+
+ TrimRange.offStart = u64Sector * s->cbSector;
+ TrimRange.cbRange = cSectors * s->cbSector;
+
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ rc = s->pDrvMedia->pfnDiscard(s->pDrvMedia, &TrimRange, 1);
+ s->Led.Actual.s.fWriting = 0;
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(s, rc);
+
+ ataR3LockEnter(pCtl);
+ return rc;
+}
+
+
+static bool ataR3TrimSS(ATADevState *s)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ uint32_t cRangesMax;
+ uint64_t *pu64Range = (uint64_t *)s->CTX_SUFF(pbIOBuffer);
+ bool fRedo = false;
+
+ cRangesMax = s->cbElementaryTransfer / sizeof(uint64_t);
+ Assert(cRangesMax);
+
+ while (cRangesMax-- > 0)
+ {
+ if (ATA_RANGE_LENGTH_GET(*pu64Range) == 0)
+ break;
+
+ rc = ataR3TrimSectors(s, *pu64Range & ATA_RANGE_LBA_MASK,
+ ATA_RANGE_LENGTH_GET(*pu64Range), &fRedo);
+ if (RT_FAILURE(rc))
+ break;
+
+ pu64Range++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk trim error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, *pu64Range & ATA_RANGE_LBA_MASK, ATA_RANGE_LENGTH_GET(*pu64Range)));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(s, ID_ERR);
+ }
+
+ return false;
+}
+
+
+static void ataR3ParseCmd(ATADevState *s, uint8_t cmd)
+{
+# ifdef DEBUG
+ Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, cmd, ATACmdText(cmd)));
+# else /* !DEBUG */
+ Log(("%s: LUN#%d CMD=%#04x\n", __FUNCTION__, s->iLUN, cmd));
+# endif /* !DEBUG */
+ s->fLBA48 = false;
+ s->fDMA = false;
+ if (cmd == ATA_IDLE_IMMEDIATE)
+ {
+ /* Detect Linux timeout recovery, first tries IDLE IMMEDIATE (which
+ * would overwrite the failing command unfortunately), then RESET. */
+ int32_t uCmdWait = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (s->u64CmdTS)
+ uCmdWait = (uNow - s->u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: LUN#%d: IDLE IMMEDIATE, CmdIf=%#04x (%d usec ago)\n",
+ s->iLUN, s->uATARegCommand, uCmdWait));
+ }
+ s->uATARegCommand = cmd;
+ switch (cmd)
+ {
+ case ATA_IDENTIFY_DEVICE:
+ if (s->pDrvMedia && !s->fATAPI)
+ ataR3StartTransfer(s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
+ else
+ {
+ if (s->fATAPI)
+ ataR3SetSignature(s);
+ ataR3CmdError(s, ABRT_ERR);
+ ataUnsetStatus(s, ATA_STAT_READY);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_RECALIBRATE:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_RECALIBRATE, false);
+ break;
+ case ATA_INITIALIZE_DEVICE_PARAMETERS:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_INITIALIZE_DEVICE_PARAMETERS, false);
+ break;
+ case ATA_SET_MULTIPLE_MODE:
+ if ( s->uATARegNSector != 0
+ && ( s->uATARegNSector > ATA_MAX_MULT_SECTORS
+ || (s->uATARegNSector & (s->uATARegNSector - 1)) != 0))
+ {
+ ataR3CmdError(s, ABRT_ERR);
+ }
+ else
+ {
+ Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
+ s->cMultSectors = s->uATARegNSector;
+ ataR3CmdOK(s, 0);
+ }
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_VERIFY_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_VERIFY_SECTORS:
+ case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+ /* do sector number check ? */
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_SECTORS:
+ case ATA_READ_SECTORS_WITHOUT_RETRIES:
+ if (!s->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_SECTORS:
+ case ATA_WRITE_SECTORS_WITHOUT_RETRIES:
+ if (!s->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_MULTIPLE:
+ if (!s->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_MULTIPLE:
+ if (!s->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_DMA_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_DMA:
+ case ATA_READ_DMA_WITHOUT_RETRIES:
+ if (!s->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->fDMA = true;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_DMA_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_DMA:
+ case ATA_WRITE_DMA_WITHOUT_RETRIES:
+ if (!s->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->fDMA = true;
+ ataR3StartTransfer(s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+ s->fLBA48 = true;
+ ataR3SetSector(s, s->cTotalSectors - 1);
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SEEK: /* Used by the SCO OpenServer. Command is marked as obsolete */
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS:
+ ataR3SetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_CHECK_POWER_MODE:
+ s->uATARegNSector = 0xff; /* drive active or idle */
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SET_FEATURES:
+ Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
+ if (!s->pDrvMedia)
+ goto abort_cmd;
+ switch (s->uATARegFeature)
+ {
+ case 0x02: /* write cache enable */
+ Log2(("%s: write cache enable\n", __FUNCTION__));
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xaa: /* read look-ahead enable */
+ Log2(("%s: read look-ahead enable\n", __FUNCTION__));
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x55: /* read look-ahead disable */
+ Log2(("%s: read look-ahead disable\n", __FUNCTION__));
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xcc: /* reverting to power-on defaults enable */
+ Log2(("%s: revert to power-on defaults enable\n", __FUNCTION__));
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x66: /* reverting to power-on defaults disable */
+ Log2(("%s: revert to power-on defaults disable\n", __FUNCTION__));
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x82: /* write cache disable */
+ Log2(("%s: write cache disable\n", __FUNCTION__));
+ /* As per the ATA/ATAPI-6 specs, a write cache disable
+ * command MUST flush the write buffers to disc. */
+ ataR3StartTransfer(s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case 0x03: { /* set transfer mode */
+ Log2(("%s: transfer mode %#04x\n", __FUNCTION__, s->uATARegNSector));
+ switch (s->uATARegNSector & 0xf8)
+ {
+ case 0x00: /* PIO default */
+ case 0x08: /* PIO mode */
+ break;
+ case ATA_MODE_MDMA: /* MDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_MDMA_MODE_MAX);
+ break;
+ case ATA_MODE_UDMA: /* UDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_UDMA_MODE_MAX);
+ break;
+ default:
+ goto abort_cmd;
+ }
+ ataR3CmdOK(s, ATA_STAT_SEEK);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+ default:
+ goto abort_cmd;
+ }
+ /*
+ * OS/2 workarond:
+ * The OS/2 IDE driver from MCP2 appears to rely on the feature register being
+ * reset here. According to the specification, this is a driver bug as the register
+ * contents are undefined after the call. This means we can just as well reset it.
+ */
+ s->uATARegFeature = 0;
+ break;
+ case ATA_FLUSH_CACHE_EXT:
+ case ATA_FLUSH_CACHE:
+ if (!s->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case ATA_STANDBY_IMMEDIATE:
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_IDLE_IMMEDIATE:
+ LogRel(("PIIX3 ATA: LUN#%d: aborting current command\n", s->iLUN));
+ ataR3AbortCurrentCommand(s, false);
+ break;
+ case ATA_SLEEP:
+ ataR3CmdOK(s, 0);
+ ataHCSetIRQ(s);
+ break;
+ /* ATAPI commands */
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ if (s->fATAPI)
+ ataR3StartTransfer(s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
+ else
+ {
+ ataR3CmdError(s, ABRT_ERR);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
+ ataR3StartTransfer(s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC, false);
+ break;
+ case ATA_DEVICE_RESET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ LogRel(("PIIX3 ATA: LUN#%d: performing device RESET\n", s->iLUN));
+ ataR3AbortCurrentCommand(s, true);
+ break;
+ case ATA_PACKET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ /* overlapping commands not supported */
+ if (s->uATARegFeature & 0x02)
+ goto abort_cmd;
+ ataR3StartTransfer(s, ATAPI_PACKET_SIZE, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
+ break;
+ case ATA_DATA_SET_MANAGEMENT:
+ if (!s->pDrvMedia || !s->pDrvMedia->pfnDiscard)
+ goto abort_cmd;
+ if ( !(s->uATARegFeature & UINT8_C(0x01))
+ || (s->uATARegFeature & ~UINT8_C(0x01)))
+ goto abort_cmd;
+ s->fDMA = true;
+ ataR3StartTransfer(s, (s->uATARegNSectorHOB << 8 | s->uATARegNSector) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_NULL, ATAFN_SS_TRIM, false);
+ break;
+ default:
+ abort_cmd:
+ ataR3CmdError(s, ABRT_ERR);
+ if (s->fATAPI)
+ ataUnsetStatus(s, ATA_STAT_READY);
+ ataHCSetIRQ(s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+}
+
+# endif /* IN_RING3 */
+#endif /* IN_RING0 || IN_RING3 */
+
+/*
+ * Note: There are four distinct cases of port I/O handling depending on
+ * which devices (if any) are attached to an IDE channel:
+ *
+ * 1) No device attached. No response to writes or reads (i.e. reads return
+ * all bits set).
+ *
+ * 2) Both devices attached. Reads and writes are processed normally.
+ *
+ * 3) Device 0 only. If device 0 is selected, normal behavior applies. But
+ * if Device 1 is selected, writes are still directed to Device 0 (except
+ * commands are not executed), reads from control/command registers are
+ * directed to Device 0, but status/alt status reads return 0. If Device 1
+ * is a PACKET device, all reads return 0. See ATAPI-6 clause 9.16.1 and
+ * Table 18 in clause 7.1.
+ *
+ * 4) Device 1 only - non-standard(!). Device 1 can't tell if Device 0 is
+ * present or not and behaves the same. That means if Device 0 is selected,
+ * Device 1 responds to writes (except commands are not executed) but does
+ * not respond to reads. If Device 1 selected, normal behavior applies.
+ * See ATAPI-6 clause 9.16.2 and Table 15 in clause 7.1.
+ */
+
+static int ataIOPortWriteU8(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: LUN#%d write addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN, addr, val));
+ addr &= 7;
+ switch (addr)
+ {
+ case 0:
+ break;
+ case 1: /* feature register */
+ /* NOTE: data is written to the two drives */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegFeatureHOB = pCtl->aIfs[0].uATARegFeature;
+ pCtl->aIfs[1].uATARegFeatureHOB = pCtl->aIfs[1].uATARegFeature;
+ pCtl->aIfs[0].uATARegFeature = val;
+ pCtl->aIfs[1].uATARegFeature = val;
+ break;
+ case 2: /* sector count */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegNSectorHOB = pCtl->aIfs[0].uATARegNSector;
+ pCtl->aIfs[1].uATARegNSectorHOB = pCtl->aIfs[1].uATARegNSector;
+ pCtl->aIfs[0].uATARegNSector = val;
+ pCtl->aIfs[1].uATARegNSector = val;
+ break;
+ case 3: /* sector number */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegSectorHOB = pCtl->aIfs[0].uATARegSector;
+ pCtl->aIfs[1].uATARegSectorHOB = pCtl->aIfs[1].uATARegSector;
+ pCtl->aIfs[0].uATARegSector = val;
+ pCtl->aIfs[1].uATARegSector = val;
+ break;
+ case 4: /* cylinder low */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegLCylHOB = pCtl->aIfs[0].uATARegLCyl;
+ pCtl->aIfs[1].uATARegLCylHOB = pCtl->aIfs[1].uATARegLCyl;
+ pCtl->aIfs[0].uATARegLCyl = val;
+ pCtl->aIfs[1].uATARegLCyl = val;
+ break;
+ case 5: /* cylinder high */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegHCylHOB = pCtl->aIfs[0].uATARegHCyl;
+ pCtl->aIfs[1].uATARegHCylHOB = pCtl->aIfs[1].uATARegHCyl;
+ pCtl->aIfs[0].uATARegHCyl = val;
+ pCtl->aIfs[1].uATARegHCyl = val;
+ break;
+ case 6: /* drive/head */
+ pCtl->aIfs[0].uATARegSelect = (val & ~0x10) | 0xa0;
+ pCtl->aIfs[1].uATARegSelect = (val | 0x10) | 0xa0;
+ if (((val >> 4) & 1) != pCtl->iSelectedIf)
+ {
+ PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+
+ /* select another drive */
+ pCtl->iSelectedIf = (val >> 4) & 1;
+ /* The IRQ line is multiplexed between the two drives, so
+ * update the state when switching to another drive. Only need
+ * to update interrupt line if it is enabled and there is a
+ * state change. */
+ if ( !(pCtl->aIfs[pCtl->iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
+ && ( pCtl->aIfs[pCtl->iSelectedIf].fIrqPending
+ != pCtl->aIfs[pCtl->iSelectedIf ^ 1].fIrqPending))
+ {
+ if (pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+ {
+ Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if
+ * the interrupt line is asserted. It monitors the line
+ * for a rising edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 0);
+ }
+ }
+ }
+ break;
+ default:
+ case 7: /* command */
+ /* ignore commands to non-existent device */
+ if (pCtl->iSelectedIf && !pCtl->aIfs[pCtl->iSelectedIf].pDrvMedia)
+ break;
+#ifndef IN_RING3
+ /* Don't do anything complicated in GC */
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ ataR3ParseCmd(&pCtl->aIfs[pCtl->iSelectedIf], val);
+#endif /* !IN_RING3 */
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int ataIOPortReadU8(PATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint32_t val;
+ bool fHOB;
+
+ /* Check if the guest is reading from a non-existent device. */
+ if (!s->pDrvMedia)
+ {
+ if (pCtl->iSelectedIf) /* Device 1 selected, Device 0 responding for it. */
+ {
+ if (!pCtl->aIfs[0].pDrvMedia) /** @todo this case should never get here! */
+ {
+ Log2(("%s: addr=%#x: no device on channel\n", __FUNCTION__, addr));
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ if (((addr & 7) != 1) && pCtl->aIfs[0].fATAPI) {
+ Log2(("%s: addr=%#x, val=0: LUN#%d not attached/LUN#%d ATAPI\n", __FUNCTION__, addr,
+ s->iLUN, pCtl->aIfs[0].iLUN));
+ *pu32 = 0;
+ return VINF_SUCCESS;
+ }
+ /* Else handle normally. */
+ }
+ else /* Device 0 selected (but not present). */
+ {
+ Log2(("%s: addr=%#x: LUN#%d not attached\n", __FUNCTION__, addr, s->iLUN));
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+ fHOB = !!(s->uATARegDevCtl & (1 << 7));
+ switch (addr & 7)
+ {
+ case 0: /* data register */
+ val = 0xff;
+ break;
+ case 1: /* error register */
+ /* The ATA specification is very terse when it comes to specifying
+ * the precise effects of reading back the error/feature register.
+ * The error register (read-only) shares the register number with
+ * the feature register (write-only), so it seems that it's not
+ * necessary to support the usual HOB readback here. */
+ if (!s->pDrvMedia)
+ val = 0;
+ else
+ val = s->uATARegError;
+ break;
+ case 2: /* sector count */
+ if (fHOB)
+ val = s->uATARegNSectorHOB;
+ else
+ val = s->uATARegNSector;
+ break;
+ case 3: /* sector number */
+ if (fHOB)
+ val = s->uATARegSectorHOB;
+ else
+ val = s->uATARegSector;
+ break;
+ case 4: /* cylinder low */
+ if (fHOB)
+ val = s->uATARegLCylHOB;
+ else
+ val = s->uATARegLCyl;
+ break;
+ case 5: /* cylinder high */
+ if (fHOB)
+ val = s->uATARegHCylHOB;
+ else
+ val = s->uATARegHCyl;
+ break;
+ case 6: /* drive/head */
+ /* This register must always work as long as there is at least
+ * one drive attached to the controller. It is common between
+ * both drives anyway (completely identical content). */
+ if (!pCtl->aIfs[0].pDrvMedia && !pCtl->aIfs[1].pDrvMedia)
+ val = 0;
+ else
+ val = s->uATARegSelect;
+ break;
+ default:
+ case 7: /* primary status */
+ {
+ if (!s->pDrvMedia)
+ val = 0;
+ else
+ val = s->uATARegStatus;
+
+ /* Give the async I/O thread an opportunity to make progress,
+ * don't let it starve by guests polling frequently. EMT has a
+ * lower priority than the async I/O thread, but sometimes the
+ * host OS doesn't care. With some guests we are only allowed to
+ * be busy for about 5 milliseconds in some situations. Note that
+ * this is no guarantee for any other VBox thread getting
+ * scheduled, so this just lowers the CPU load a bit when drives
+ * are busy. It cannot help with timing problems. */
+ if (val & ATA_STAT_BUSY)
+ {
+#ifdef IN_RING3
+ /* @bugref{1960}: Don't yield all the time, unless it's a reset (can be tricky). */
+ bool fYield = (s->cBusyStatusHackR3++ & s->cBusyStatusHackR3Rate) == 0
+ || pCtl->fReset;
+
+ ataR3LockLeave(pCtl);
+
+ /*
+ * The thread might be stuck in an I/O operation due to a high I/O
+ * load on the host (see @bugref{3301}). To perform the reset
+ * successfully we interrupt the operation by sending a signal to
+ * the thread if the thread didn't responded in 10ms.
+ *
+ * This works only on POSIX hosts (Windows has a CancelSynchronousIo
+ * function which does the same but it was introduced with Vista) but
+ * so far this hang was only observed on Linux and Mac OS X.
+ *
+ * This is a workaround and needs to be solved properly.
+ */
+ if (pCtl->fReset)
+ {
+ uint64_t u64ResetTimeStop = RTTimeMilliTS();
+ if (u64ResetTimeStop - pCtl->u64ResetTime >= 10)
+ {
+ LogRel(("PIIX3 ATA LUN#%d: Async I/O thread probably stuck in operation, interrupting\n", s->iLUN));
+ pCtl->u64ResetTime = u64ResetTimeStop;
+# ifndef RT_OS_WINDOWS /* We've got this API on windows, but it doesn't necessarily interrupt I/O. */
+ RTThreadPoke(pCtl->AsyncIOThread);
+# endif
+ Assert(fYield);
+ }
+ }
+
+ if (fYield)
+ {
+ STAM_REL_PROFILE_ADV_START(&s->StatStatusYields, a);
+ RTThreadYield();
+ STAM_REL_PROFILE_ADV_STOP(&s->StatStatusYields, a);
+ }
+ ASMNopPause();
+
+ ataR3LockEnter(pCtl);
+
+ val = s->uATARegStatus;
+#else /* !IN_RING3 */
+ /* Cannot yield CPU in raw-mode and ring-0 context. And switching
+ * to host context for each and every busy status is too costly,
+ * especially on SMP systems where we don't gain much by
+ * yielding the CPU to someone else. */
+ if ((s->cBusyStatusHackRZ++ & s->cBusyStatusHackRZRate) == 1)
+ {
+ s->cBusyStatusHackR3 = 0; /* Forces a yield. */
+ return VINF_IOM_R3_IOPORT_READ;
+ }
+#endif /* !IN_RING3 */
+ }
+ else
+ {
+ s->cBusyStatusHackRZ = 0;
+ s->cBusyStatusHackR3 = 0;
+ }
+ ataUnsetIRQ(s);
+ break;
+ }
+ }
+ Log2(("%s: LUN#%d addr=%#x val=%#04x\n", __FUNCTION__, s->iLUN, addr, val));
+ *pu32 = val;
+ return VINF_SUCCESS;
+}
+
+
+static uint32_t ataStatusRead(PATACONTROLLER pCtl, uint32_t addr)
+{
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+ uint32_t val;
+ RT_NOREF1(addr);
+
+ /// @todo The handler should not be even registered if there
+ // is no device on an IDE channel.
+ if (!pCtl->aIfs[0].pDrvMedia && !pCtl->aIfs[1].pDrvMedia)
+ val = 0xff;
+ else if (pCtl->iSelectedIf == 1 && !s->pDrvMedia)
+ val = 0; /* Device 1 selected, Device 0 responding for it. */
+ else
+ val = s->uATARegStatus;
+ Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static int ataControlWrite(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF1(addr);
+#ifndef IN_RING3
+ if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_RESET)
+ return VINF_IOM_R3_IOPORT_WRITE; /* The RESET stuff is too complicated for RC+R0. */
+#endif /* !IN_RING3 */
+
+ Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
+ /* RESET is common for both drives attached to a controller. */
+ if ( !(pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET)
+ && (val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET low to high */
+ int32_t uCmdWait0 = -1;
+ int32_t uCmdWait1 = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (pCtl->aIfs[0].u64CmdTS)
+ uCmdWait0 = (uNow - pCtl->aIfs[0].u64CmdTS) / 1000;
+ if (pCtl->aIfs[1].u64CmdTS)
+ uCmdWait1 = (uNow - pCtl->aIfs[1].u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: Ctl#%d: RESET, DevSel=%d AIOIf=%d CmdIf0=%#04x (%d usec ago) CmdIf1=%#04x (%d usec ago)\n",
+ ATACONTROLLER_IDX(pCtl), pCtl->iSelectedIf, pCtl->iAIOIf,
+ pCtl->aIfs[0].uATARegCommand, uCmdWait0,
+ pCtl->aIfs[1].uATARegCommand, uCmdWait1));
+ pCtl->fReset = true;
+ /* Everything must be done after the reset flag is set, otherwise
+ * there are unavoidable races with the currently executing request
+ * (which might just finish in the mean time). */
+ pCtl->fChainedTransfer = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ ataR3ResetDevice(&pCtl->aIfs[i]);
+ /* The following cannot be done using ataSetStatusValue() since the
+ * reset flag is already set, which suppresses all status changes. */
+ pCtl->aIfs[i].uATARegStatus = ATA_STAT_BUSY | ATA_STAT_SEEK;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, pCtl->aIfs[i].iLUN, pCtl->aIfs[i].uATARegStatus));
+ pCtl->aIfs[i].uATARegError = 0x01;
+ }
+ pCtl->iSelectedIf = 0;
+ ataR3AsyncIOClearRequests(pCtl);
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetA\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+
+ /* Save the timestamp we started the reset. */
+ pCtl->u64ResetTime = RTTimeMilliTS();
+
+ /* Issue the reset request now. */
+ ataHCAsyncIOPutRequest(pCtl, &g_ataResetARequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+ else if ( (pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET)
+ && !(val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET high to low */
+ Log(("%s: deasserting RESET\n", __FUNCTION__));
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetC\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+ ataHCAsyncIOPutRequest(pCtl, &g_ataResetCRequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+
+ /* Change of interrupt disable flag. Update interrupt line if interrupt
+ * is pending on the current interface. */
+ if ( ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_DISABLE_IRQ)
+ && pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
+ {
+ if (!(val & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the
+ * interrupt line is asserted. It monitors the line for a rising
+ * edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(CONTROLLER_2_DEVINS(pCtl), 0, 1);
+ else
+ PDMDevHlpISASetIrq(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(CONTROLLER_2_DEVINS(pCtl), 0, 0);
+ else
+ PDMDevHlpISASetIrq(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 0);
+ }
+ }
+
+ if (val & ATA_DEVCTL_HOB)
+ Log2(("%s: set HOB\n", __FUNCTION__));
+
+ pCtl->aIfs[0].uATARegDevCtl = val;
+ pCtl->aIfs[1].uATARegDevCtl = val;
+
+ return VINF_SUCCESS;
+}
+
+#if defined(IN_RING0) || defined(IN_RING3)
+
+static void ataHCPIOTransfer(PATACONTROLLER pCtl)
+{
+ ATADevState *s;
+
+ s = &pCtl->aIfs[pCtl->iAIOIf];
+ Log3(("%s: if=%p\n", __FUNCTION__, s));
+
+ if (s->cbTotalTransfer && s->iIOBufferCur > s->iIOBufferEnd)
+ {
+# ifdef IN_RING3
+ LogRel(("PIIX3 ATA: LUN#%d: %s data in the middle of a PIO transfer - VERY SLOW\n",
+ s->iLUN, s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "loading" : "storing"));
+ /* Any guest OS that triggers this case has a pathetic ATA driver.
+ * In a real system it would block the CPU via IORDY, here we do it
+ * very similarly by not continuing with the current instruction
+ * until the transfer to/from the storage medium is completed. */
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ uint8_t status = s->uATARegStatus;
+ ataSetStatusValue(s, ATA_STAT_BUSY);
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ return;
+ ataSetStatusValue(s, status);
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+# else
+ AssertReleaseFailed();
+# endif
+ }
+ if (s->cbTotalTransfer)
+ {
+ if (s->fATAPITransfer)
+ ataHCPIOTransferLimitATAPI(s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ s->cbTotalTransfer, s->cbElementaryTransfer,
+ s->iIOBufferCur, s->iIOBufferEnd));
+ ataHCPIOTransferStart(s, s->iIOBufferCur, s->cbElementaryTransfer);
+ s->cbTotalTransfer -= s->cbElementaryTransfer;
+ s->iIOBufferCur += s->cbElementaryTransfer;
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ }
+ else
+ ataHCPIOTransferStop(s);
+}
+
+
+DECLINLINE(void) ataHCPIOTransferFinish(PATACONTROLLER pCtl, ATADevState *s)
+{
+ /* Do not interfere with RESET processing if the PIO transfer finishes
+ * while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing PIO transfer as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ if ( s->uTxDir == PDMMEDIATXDIR_TO_DEVICE
+ || ( s->iSourceSink != ATAFN_SS_NULL
+ && s->iIOBufferCur >= s->iIOBufferEnd))
+ {
+ /* Need to continue the transfer in the async I/O thread. This is
+ * the case for write operations or generally for not yet finished
+ * transfers (some data might need to be read). */
+ ataUnsetStatus(s, ATA_STAT_READY | ATA_STAT_DRQ);
+ ataSetStatus(s, ATA_STAT_BUSY);
+
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &g_ataPIORequest);
+ }
+ else
+ {
+ /* Either everything finished (though some data might still be pending)
+ * or some data is pending before the next read is due. */
+
+ /* Continue a previously started transfer. */
+ ataUnsetStatus(s, ATA_STAT_DRQ);
+ ataSetStatus(s, ATA_STAT_READY);
+
+ if (s->cbTotalTransfer)
+ {
+ /* There is more to transfer, happens usually for large ATAPI
+ * reads - the protocol limits the chunk size to 65534 bytes. */
+ ataHCPIOTransfer(pCtl);
+ ataHCSetIRQ(s);
+ }
+ else
+ {
+ Log2(("%s: Ctl#%d: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ }
+ }
+}
+
+#endif /* IN_RING0 || IN_RING3 */
+
+/**
+ * Fallback for ataCopyPioData124 that handles unaligned and out of bounds cases.
+ *
+ * @param pIf The device interface to work with.
+ * @param pbDst The destination buffer.
+ * @param pbSrc The source buffer.
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECL_NO_INLINE(static, void) ataCopyPioData124Slow(ATADevState *pIf, uint8_t *pbDst, const uint8_t *pbSrc, uint32_t cbCopy)
+{
+ uint32_t const offStart = pIf->iIOBufferPIODataStart;
+ uint32_t const offNext = offStart + cbCopy;
+
+ if (offStart + cbCopy > pIf->cbIOBuffer)
+ {
+ Log(("%s: cbCopy=%#x offStart=%#x cbIOBuffer=%#x offNext=%#x (iIOBufferPIODataEnd=%#x)\n",
+ __FUNCTION__, cbCopy, offStart, pIf->cbIOBuffer, offNext, pIf->iIOBufferPIODataEnd));
+ if (offStart < pIf->cbIOBuffer)
+ cbCopy = pIf->cbIOBuffer - offStart;
+ else
+ cbCopy = 0;
+ }
+
+ switch (cbCopy)
+ {
+ case 4: pbDst[3] = pbSrc[3]; RT_FALL_THRU();
+ case 3: pbDst[2] = pbSrc[2]; RT_FALL_THRU();
+ case 2: pbDst[1] = pbSrc[1]; RT_FALL_THRU();
+ case 1: pbDst[0] = pbSrc[0]; RT_FALL_THRU();
+ case 0: break;
+ default: AssertFailed(); /* impossible */
+ }
+
+ pIf->iIOBufferPIODataStart = offNext;
+
+}
+
+
+/**
+ * Work for ataDataWrite & ataDataRead that copies data without using memcpy.
+ *
+ * This also updates pIf->iIOBufferPIODataStart.
+ *
+ * The two buffers are either stack (32-bit aligned) or somewhere within
+ * pIf->pbIOBuffer.
+ *
+ * @param pIf The device interface to work with.
+ * @param pbDst The destination buffer.
+ * @param pbSrc The source buffer.
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECLINLINE(void) ataCopyPioData124(ATADevState *pIf, uint8_t *pbDst, const uint8_t *pbSrc, uint32_t cbCopy)
+{
+ /*
+ * Quick bounds checking can be done by checking that the pbIOBuffer offset
+ * (iIOBufferPIODataStart) is aligned at the transfer size (which is ASSUMED
+ * to be 1, 2 or 4). However, since we're paranoid and don't currently
+ * trust iIOBufferPIODataEnd to be within bounds, we current check against the
+ * IO buffer size too.
+ */
+ Assert(cbCopy == 1 || cbCopy == 2 || cbCopy == 4);
+ uint32_t const offStart = pIf->iIOBufferPIODataStart;
+ if (RT_LIKELY( !(offStart & (cbCopy - 1))
+ && offStart + cbCopy <= pIf->cbIOBuffer))
+ {
+ switch (cbCopy)
+ {
+ case 4: *(uint32_t *)pbDst = *(uint32_t const *)pbSrc; break;
+ case 2: *(uint16_t *)pbDst = *(uint16_t const *)pbSrc; break;
+ case 1: *pbDst = *pbSrc; break;
+ }
+ pIf->iIOBufferPIODataStart = offStart + cbCopy;
+ }
+ else
+ ataCopyPioData124Slow(pIf, pbDst, pbSrc, cbCopy);
+}
+
+
+/**
+ * Port I/O Handler for primary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWrite1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ RT_NOREF1(Port);
+
+ Assert(i < 2);
+ Assert(Port == pCtl->IOPortBase1);
+ Assert(cb == 2 || cb == 4); /* Writes to the data port may be 16-bit or 32-bit. */
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ uint8_t *pbDst = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+ uint8_t const *pbSrc = (uint8_t const *)&u32;
+
+#ifdef IN_RC
+ /* Raw-mode: The ataHCPIOTransfer following the last transfer unit
+ requires I/O thread signalling, we must go to ring-3 for that. */
+ if (s->iIOBufferPIODataStart + cb < s->iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, cb);
+ else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+
+#elif defined(IN_RING0)
+ /* Ring-0: We can do I/O thread signalling here, however for paranoid reasons
+ triggered by a special case in ataHCPIOTransferFinish, we take extra care here. */
+ if (s->iIOBufferPIODataStart + cb < s->iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, cb);
+ else if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE) /* paranoia */
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, cb);
+ ataHCPIOTransferFinish(pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n",__FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ }
+
+#else /* IN_RING 3*/
+ ataCopyPioData124(s, pbDst, pbSrc, cb);
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pCtl, s);
+#endif /* IN_RING 3*/
+ }
+ else
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+
+ Log3(("%s: addr=%#x val=%.*Rhxs rc=%d\n", __FUNCTION__, Port, cb, &u32, rc));
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, Port, rc));
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortRead1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ RT_NOREF1(Port);
+
+ Assert(i < 2);
+ Assert(Port == pCtl->IOPortBase1);
+
+ /* Reads from the data register may be 16-bit or 32-bit. Byte accesses are
+ upgraded to word. */
+ Assert(cb == 1 || cb == 2 || cb == 4);
+ uint32_t cbActual = cb != 1 ? cb : 2;
+ *pu32 = 0;
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ uint8_t const *pbSrc = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
+ uint8_t *pbDst = (uint8_t *)pu32;
+
+#ifdef IN_RC
+ /* All but the last transfer unit is simple enough for RC, but
+ * sending a request to the async IO thread is too complicated. */
+ if (s->iIOBufferPIODataStart + cbActual < s->iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, cbActual);
+ else
+ rc = VINF_IOM_R3_IOPORT_READ;
+
+#elif defined(IN_RING0)
+ /* Ring-0: We can do I/O thread signalling here. However there is one
+ case in ataHCPIOTransfer that does a LogRel and would (but not from
+ here) call directly into the driver code. We detect that odd case
+ here cand return to ring-3 to handle it. */
+ if (s->iIOBufferPIODataStart + cbActual < s->iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, cbActual);
+ else if ( s->cbTotalTransfer == 0
+ || s->iSourceSink != ATAFN_SS_NULL
+ || s->iIOBufferCur <= s->iIOBufferEnd)
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, cbActual);
+ ataHCPIOTransferFinish(pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n",__FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_READ;
+ }
+
+#else /* IN_RING3 */
+ ataCopyPioData124(s, pbDst, pbSrc, cbActual);
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pCtl, s);
+#endif /* IN_RING3 */
+
+ /* Just to be on the safe side (caller takes care of this, really). */
+ if (cb == 1)
+ *pu32 &= 0xff;
+ }
+ else
+ {
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+ memset(pu32, 0xff, cb);
+ }
+ Log3(("%s: addr=%#x val=%.*Rhxs rc=%d\n", __FUNCTION__, Port, cb, pu32, rc));
+
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, Port, rc));
+
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortReadStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint8_t *pbDst,
+ uint32_t *pcTransfers, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ RT_NOREF1(Port);
+
+ Assert(i < 2);
+ Assert(Port == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ int rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ if (offStart < s->iIOBufferPIODataEnd)
+ {
+ /*
+ * Figure how much we can copy. Usually it's the same as the request.
+ * The last transfer unit cannot be handled in RC, as it involves
+ * thread communication. In R0 we let the non-string callback handle it,
+ * and ditto for overflows/dummy data.
+ */
+ uint32_t cAvailable = (s->iIOBufferPIODataEnd - offStart) / cb;
+#ifndef IN_RING3
+ if (cAvailable > 0)
+ cAvailable--;
+#endif
+ uint32_t const cRequested = *pcTransfers;
+ if (cAvailable > cRequested)
+ cAvailable = cRequested;
+ uint32_t const cbTransfer = cAvailable * cb;
+ if ( offStart + cbTransfer <= s->cbIOBuffer
+ && cbTransfer > 0)
+ {
+ /*
+ * Do the transfer.
+ */
+ uint8_t const *pbSrc = s->CTX_SUFF(pbIOBuffer) + offStart;
+ memcpy(pbDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x cb=%#x cbTransfer=%#x val=%.*Rhxd\n",
+ __FUNCTION__, Port, cb, cbTransfer, cbTransfer, pbSrc));
+ s->iIOBufferPIODataStart = offStart + cbTransfer;
+
+#ifdef IN_RING3
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pCtl, s);
+#endif
+ *pcTransfers = cRequested - cAvailable;
+ }
+ else
+ Log2(("ataIOPortReadStr1Data: DUMMY/Overflow!\n"));
+ }
+ else
+ {
+ /*
+ * Dummy read (shouldn't happen) return 0xff like the non-string handler.
+ */
+ Log2(("ataIOPortReadStr1Data: DUMMY data (%#x bytes)\n", *pcTransfers * cb));
+ memset(pbDst, 0xff, *pcTransfers * cb);
+ *pcTransfers = 0;
+ }
+
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ }
+ /*
+ * Let the non-string I/O callback handle 1 byte reads.
+ */
+ else
+ {
+ Log2(("ataIOPortReadStr1Data: 1 byte read (%#x transfers)\n", *pcTransfers));
+ AssertFailed();
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWriteStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint8_t const *pbSrc,
+ uint32_t *pcTransfers, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ RT_NOREF1(Port);
+
+ Assert(i < 2);
+ Assert(Port == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ int rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ ATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ if (offStart < s->iIOBufferPIODataEnd)
+ {
+ /*
+ * Figure how much we can copy. Usually it's the same as the request.
+ * The last transfer unit cannot be handled in RC, as it involves
+ * thread communication. In R0 we let the non-string callback handle it,
+ * and ditto for overflows/dummy data.
+ */
+ uint32_t cAvailable = (s->iIOBufferPIODataEnd - offStart) / cb;
+#ifndef IN_RING3
+ if (cAvailable)
+ cAvailable--;
+#endif
+ uint32_t const cRequested = *pcTransfers;
+ if (cAvailable > cRequested)
+ cAvailable = cRequested;
+ uint32_t const cbTransfer = cAvailable * cb;
+ if ( offStart + cbTransfer <= s->cbIOBuffer
+ && cbTransfer)
+ {
+ /*
+ * Do the transfer.
+ */
+ void *pvDst = s->CTX_SUFF(pbIOBuffer) + offStart;
+ memcpy(pvDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, pvDst));
+ s->iIOBufferPIODataStart = offStart + cbTransfer;
+
+#ifdef IN_RING3
+ if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pCtl, s);
+#endif
+ *pcTransfers = cRequested - cAvailable;
+ }
+ else
+ Log2(("ataIOPortWriteStr1Data: DUMMY/Overflow!\n"));
+ }
+ else
+ {
+ Log2(("ataIOPortWriteStr1Data: DUMMY data (%#x bytes)\n", *pcTransfers * cb));
+ *pcTransfers = 0;
+ }
+
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ }
+ /*
+ * Let the non-string I/O callback handle 1 byte reads.
+ */
+ else
+ {
+ Log2(("ataIOPortWriteStr1Data: 1 byte write (%#x transfers)\n", *pcTransfers));
+ AssertFailed();
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+#ifdef IN_RING3
+
+static void ataR3DMATransferStop(ATADevState *s)
+{
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/**
+ * Perform the entire DMA transfer in one go (unless a source/sink operation
+ * has to be redone or a RESET comes in between). Unlike the PIO counterpart
+ * this function cannot handle empty transfers.
+ *
+ * @param pCtl Controller for which to perform the transfer.
+ */
+static void ataR3DMATransfer(PATACONTROLLER pCtl)
+{
+ PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
+ ATADevState *s = &pCtl->aIfs[pCtl->iAIOIf];
+ bool fRedo;
+ RTGCPHYS32 GCPhysDesc;
+ uint32_t cbTotalTransfer, cbElementaryTransfer;
+ uint32_t iIOBufferCur, iIOBufferEnd;
+ PDMMEDIATXDIR uTxDir;
+ bool fLastDesc = false;
+
+ Assert(sizeof(BMDMADesc) == 8);
+
+ fRedo = pCtl->fRedo;
+ if (RT_LIKELY(!fRedo))
+ Assert(s->cbTotalTransfer);
+ uTxDir = (PDMMEDIATXDIR)s->uTxDir;
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = s->cbElementaryTransfer;
+ iIOBufferCur = s->iIOBufferCur;
+ iIOBufferEnd = s->iIOBufferEnd;
+
+ /* The DMA loop is designed to hold the lock only when absolutely
+ * necessary. This avoids long freezes should the guest access the
+ * ATA registers etc. for some reason. */
+ ataR3LockLeave(pCtl);
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ cbTotalTransfer, cbElementaryTransfer,
+ iIOBufferCur, iIOBufferEnd));
+ for (GCPhysDesc = pCtl->GCPhysFirstDMADesc;
+ GCPhysDesc <= pCtl->GCPhysLastDMADesc;
+ GCPhysDesc += sizeof(BMDMADesc))
+ {
+ BMDMADesc DMADesc;
+ RTGCPHYS32 GCPhysBuffer;
+ uint32_t cbBuffer;
+
+ if (RT_UNLIKELY(fRedo))
+ {
+ GCPhysBuffer = pCtl->GCPhysRedoDMABuffer;
+ cbBuffer = pCtl->cbRedoDMABuffer;
+ fLastDesc = pCtl->fRedoDMALastDesc;
+ DMADesc.GCPhysBuffer = DMADesc.cbBuffer = 0; /* Shut up MSC. */
+ }
+ else
+ {
+ PDMDevHlpPhysRead(pDevIns, GCPhysDesc, &DMADesc, sizeof(BMDMADesc));
+ GCPhysBuffer = RT_LE2H_U32(DMADesc.GCPhysBuffer);
+ cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
+ fLastDesc = !!(cbBuffer & 0x80000000);
+ cbBuffer &= 0xfffe;
+ if (cbBuffer == 0)
+ cbBuffer = 0x10000;
+ if (cbBuffer > cbTotalTransfer)
+ cbBuffer = cbTotalTransfer;
+ }
+
+ while (RT_UNLIKELY(fRedo) || (cbBuffer && cbTotalTransfer))
+ {
+ if (RT_LIKELY(!fRedo))
+ {
+ uint32_t cbXfer = RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur);
+ Log2(("%s: DMA desc %#010x: addr=%#010x size=%#010x orig_size=%#010x\n", __FUNCTION__,
+ (int)GCPhysDesc, GCPhysBuffer, cbBuffer, RT_LE2H_U32(DMADesc.cbBuffer) & 0xfffe));
+
+ if (uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ PDMDevHlpPCIPhysWrite(pDevIns, GCPhysBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, cbXfer);
+ else
+ PDMDevHlpPCIPhysRead(pDevIns, GCPhysBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, cbXfer);
+
+ iIOBufferCur += cbXfer;
+ cbTotalTransfer -= cbXfer;
+ cbBuffer -= cbXfer;
+ GCPhysBuffer += cbXfer;
+ }
+ if ( iIOBufferCur == iIOBufferEnd
+ && (uTxDir == PDMMEDIATXDIR_TO_DEVICE || cbTotalTransfer))
+ {
+ if (uTxDir == PDMMEDIATXDIR_FROM_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+
+ ataR3LockEnter(pCtl);
+
+ /* The RESET handler could have cleared the DMA transfer
+ * state (since we didn't hold the lock until just now
+ * the guest can continue in parallel). If so, the state
+ * is already set up so the loop is exited immediately. */
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->cbTotalTransfer = cbTotalTransfer;
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ if (RT_UNLIKELY(fRedo))
+ {
+ pCtl->GCPhysFirstDMADesc = GCPhysDesc;
+ pCtl->GCPhysRedoDMABuffer = GCPhysBuffer;
+ pCtl->cbRedoDMABuffer = cbBuffer;
+ pCtl->fRedoDMALastDesc = fLastDesc;
+ }
+ else
+ {
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = s->cbElementaryTransfer;
+
+ if (uTxDir == PDMMEDIATXDIR_TO_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+ iIOBufferCur = 0;
+ iIOBufferEnd = cbElementaryTransfer;
+ }
+ pCtl->fRedo = fRedo;
+ }
+ else
+ {
+ /* This forces the loop to exit immediately. */
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pCtl);
+ if (RT_UNLIKELY(fRedo))
+ break;
+ }
+ }
+
+ if (RT_UNLIKELY(fRedo))
+ break;
+
+ /* end of transfer */
+ if (!cbTotalTransfer || fLastDesc)
+ break;
+
+ ataR3LockEnter(pCtl);
+
+ if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: ABORT DMA%s\n", ATACONTROLLER_IDX(pCtl), pCtl->fReset ? " due to RESET" : ""));
+ if (!pCtl->fReset)
+ ataR3DMATransferStop(s);
+ /* This forces the loop to exit immediately. */
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pCtl);
+ }
+
+ ataR3LockEnter(pCtl);
+ if (RT_UNLIKELY(fRedo))
+ return;
+
+ if (fLastDesc)
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ s->cbTotalTransfer = cbTotalTransfer;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+}
+
+/**
+ * Signal PDM that we're idle (if we actually are).
+ *
+ * @param pCtl The controller.
+ */
+static void ataR3AsyncSignalIdle(PATACONTROLLER pCtl)
+{
+ /*
+ * Take the lock here and recheck the idle indicator to avoid
+ * unnecessary work and racing ataR3WaitForAsyncIOIsIdle.
+ */
+ int rc = PDMCritSectEnter(&pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ AssertRC(rc);
+
+ if ( pCtl->fSignalIdle
+ && ataR3AsyncIOIsIdle(pCtl, false /*fStrict*/))
+ {
+ PDMDevHlpAsyncNotificationCompleted(pCtl->pDevInsR3);
+ RTThreadUserSignal(pCtl->AsyncIOThread); /* for ataR3Construct/ataR3ResetCommon. */
+ }
+
+ rc = PDMCritSectLeave(&pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+/**
+ * Async I/O thread for an interface.
+ *
+ * Once upon a time this was readable code with several loops and a different
+ * semaphore for each purpose. But then came the "how can one save the state in
+ * the middle of a PIO transfer" question. The solution was to use an ASM,
+ * which is what's there now.
+ */
+static DECLCALLBACK(int) ataR3AsyncIOThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ const ATARequest *pReq;
+ uint64_t u64TS = 0; /* shut up gcc */
+ uint64_t uWait;
+ int rc = VINF_SUCCESS;
+ PATACONTROLLER pCtl = (PATACONTROLLER)pvUser;
+ ATADevState *s;
+
+ pReq = NULL;
+ pCtl->fChainedTransfer = false;
+ while (!pCtl->fShutdown)
+ {
+ /* Keep this thread from doing anything as long as EMT is suspended. */
+ while (pCtl->fRedoIdle)
+ {
+ if (pCtl->fSignalIdle)
+ ataR3AsyncSignalIdle(pCtl);
+ rc = RTSemEventWait(pCtl->SuspendIOSem, RT_INDEFINITE_WAIT);
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || pCtl->fShutdown)
+ break;
+
+ pCtl->fRedoIdle = false;
+ }
+
+ /* Wait for work. */
+ while (pReq == NULL)
+ {
+ if (pCtl->fSignalIdle)
+ ataR3AsyncSignalIdle(pCtl);
+ rc = SUPSemEventWaitNoResume(pCtl->pSupDrvSession, pCtl->hAsyncIOSem, RT_INDEFINITE_WAIT);
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || RT_UNLIKELY(pCtl->fShutdown))
+ break;
+
+ pReq = ataR3AsyncIOGetCurrentRequest(pCtl);
+ }
+
+ if (RT_FAILURE(rc) || pCtl->fShutdown)
+ break;
+
+ if (pReq == NULL)
+ continue;
+
+ ATAAIO ReqType = pReq->ReqType;
+
+ Log2(("%s: Ctl#%d: state=%d, req=%d\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), pCtl->uAsyncIOState, ReqType));
+ if (pCtl->uAsyncIOState != ReqType)
+ {
+ /* The new state is not the state that was expected by the normal
+ * state changes. This is either a RESET/ABORT or there's something
+ * really strange going on. */
+ if ( (pCtl->uAsyncIOState == ATA_AIO_PIO || pCtl->uAsyncIOState == ATA_AIO_DMA)
+ && (ReqType == ATA_AIO_PIO || ReqType == ATA_AIO_DMA))
+ {
+ /* Incorrect sequence of PIO/DMA states. Dump request queue. */
+ ataR3AsyncIODumpRequests(pCtl);
+ }
+ AssertReleaseMsg( ReqType == ATA_AIO_RESET_ASSERTED
+ || ReqType == ATA_AIO_RESET_CLEARED
+ || ReqType == ATA_AIO_ABORT
+ || pCtl->uAsyncIOState == ReqType,
+ ("I/O state inconsistent: state=%d request=%d\n", pCtl->uAsyncIOState, ReqType));
+ }
+
+ /* Do our work. */
+ ataR3LockEnter(pCtl);
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+ u64TS = RTTimeNanoTS();
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_START(&pCtl->StatAsyncTime, a);
+#endif
+ }
+
+ switch (ReqType)
+ {
+ case ATA_AIO_NEW:
+
+ pCtl->iAIOIf = pReq->u.t.iIf;
+ s = &pCtl->aIfs[pCtl->iAIOIf];
+ s->cbTotalTransfer = pReq->u.t.cbTotalTransfer;
+ s->uTxDir = pReq->u.t.uTxDir;
+ s->iBeginTransfer = pReq->u.t.iBeginTransfer;
+ s->iSourceSink = pReq->u.t.iSourceSink;
+ s->iIOBufferEnd = 0;
+ s->u64CmdTS = u64TS;
+
+ if (s->fATAPI)
+ {
+ if (pCtl->fChainedTransfer)
+ {
+ /* Only count the actual transfers, not the PIO
+ * transfer of the ATAPI command bytes. */
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATAPIDMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIPIO);
+ }
+ }
+ else
+ {
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATADMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIO);
+ }
+
+ pCtl->fChainedTransfer = false;
+
+ if (s->iBeginTransfer != ATAFN_BT_NULL)
+ {
+ Log2(("%s: Ctl#%d: calling begin transfer function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ g_apfnBeginTransFuncs[s->iBeginTransfer](s);
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ if (s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->iIOBufferEnd = s->cbTotalTransfer;
+ }
+ s->iIOBufferCur = 0;
+
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo && !pCtl->fReset))
+ {
+ /* Operation failed at the initial transfer, restart
+ * everything from scratch by resending the current
+ * request. Occurs very rarely, not worth optimizing. */
+ LogRel(("%s: Ctl#%d: redo entire operation\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, pReq);
+ break;
+ }
+ }
+ else
+ ataR3CmdOK(s, 0);
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+
+ }
+
+ /* Do not go into the transfer phase if RESET is asserted.
+ * The CritSect is released while waiting for the host OS
+ * to finish the I/O, thus RESET is possible here. Most
+ * important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->fDMA)
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataSetStatus(s, ATA_STAT_DRQ);
+
+ pCtl->uAsyncIOState = ATA_AIO_DMA;
+ /* If BMDMA is already started, do the transfer now. */
+ if (pCtl->BmDma.u8Cmd & BM_CMD_START)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer immediately\n",
+ __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &g_ataDMARequest);
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish DMA transfer. */
+ ataR3DMATransferStop(s);
+ ataHCSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataHCPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ if (s->fATAPITransfer || s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ ataHCSetIRQ(s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pCtl);
+ Assert(!pCtl->fRedo);
+ if (!s->fATAPITransfer)
+ ataHCSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ break;
+
+ case ATA_AIO_DMA:
+ {
+ BMDMAState *bm = &pCtl->BmDma;
+ s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+ ATAFNSS iOriginalSourceSink = (ATAFNSS)s->iSourceSink; /* Used by the hack below, but gets reset by then. */
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ AssertRelease(bm->u8Cmd & BM_CMD_WRITE);
+ else
+ AssertRelease(!(bm->u8Cmd & BM_CMD_WRITE));
+
+ if (RT_LIKELY(!pCtl->fRedo))
+ {
+ /* The specs say that the descriptor table must not cross a
+ * 4K boundary. */
+ pCtl->GCPhysFirstDMADesc = bm->GCPhysAddr;
+ pCtl->GCPhysLastDMADesc = RT_ALIGN_32(bm->GCPhysAddr + 1, _4K) - sizeof(BMDMADesc);
+ }
+ ataR3DMATransfer(pCtl);
+
+ if (RT_UNLIKELY(pCtl->fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo DMA operation\n", ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &g_ataDMARequest);
+ break;
+ }
+
+ /* The infamous delay IRQ hack. */
+ if ( iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
+ && s->cbTotalTransfer == 0
+ && pCtl->DelayIRQMillies)
+ {
+ /* Delay IRQ for writing. Required to get the Win2K
+ * installation work reliably (otherwise it crashes,
+ * usually during component install). So far no better
+ * solution has been found. */
+ Log(("%s: delay IRQ hack\n", __FUNCTION__));
+ ataR3LockLeave(pCtl);
+ RTThreadSleep(pCtl->DelayIRQMillies);
+ ataR3LockEnter(pCtl);
+ }
+
+ ataUnsetStatus(s, ATA_STAT_DRQ);
+ Assert(!pCtl->fChainedTransfer);
+ Assert(s->iSourceSink == ATAFN_SS_NULL);
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: Ctl#%d: interrupt reason %#04x\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), s->uATARegNSector));
+ s->fATAPITransfer = false;
+ }
+ ataHCSetIRQ(s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ break;
+ }
+
+ case ATA_AIO_PIO:
+ s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
+
+ if (s->iSourceSink != ATAFN_SS_NULL)
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo PIO operation\n", ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &g_ataPIORequest);
+ break;
+ }
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ /* Continue a previously started transfer. */
+ ataUnsetStatus(s, ATA_STAT_BUSY);
+ ataSetStatus(s, ATA_STAT_READY);
+ }
+
+ /* It is possible that the drives on this controller get RESET
+ * during the above call to the source/sink function. If that's
+ * the case, don't restart the transfer and don't finish it the
+ * usual way. RESET handling took care of all that already.
+ * Most important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->cbTotalTransfer)
+ {
+ ataHCPIOTransfer(pCtl);
+ ataHCSetIRQ(s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pCtl);
+ if ( !pCtl->fChainedTransfer
+ && !s->fATAPITransfer
+ && s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ ataHCSetIRQ(s);
+ }
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ break;
+
+ case ATA_AIO_RESET_ASSERTED:
+ pCtl->uAsyncIOState = ATA_AIO_RESET_CLEARED;
+ ataHCPIOTransferStop(&pCtl->aIfs[0]);
+ ataHCPIOTransferStop(&pCtl->aIfs[1]);
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ break;
+
+ case ATA_AIO_RESET_CLEARED:
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ pCtl->fReset = false;
+ /* Ensure that half-completed transfers are not redone. A reset
+ * cancels the entire transfer, so continuing is wrong. */
+ pCtl->fRedo = false;
+ pCtl->fRedoDMALastDesc = false;
+ LogRel(("PIIX3 ATA: Ctl#%d: finished processing RESET\n",
+ ATACONTROLLER_IDX(pCtl)));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ if (pCtl->aIfs[i].fATAPI)
+ ataSetStatusValue(&pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(&pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
+ ataR3SetSignature(&pCtl->aIfs[i]);
+ }
+ break;
+
+ case ATA_AIO_ABORT:
+ /* Abort the current command no matter what. There cannot be
+ * any command activity on the other drive otherwise using
+ * one thread per controller wouldn't work at all. */
+ s = &pCtl->aIfs[pReq->u.a.iIf];
+
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ if (pReq->u.a.fResetDrive)
+ {
+ ataR3ResetDevice(s);
+ ataR3ExecuteDeviceDiagnosticSS(s);
+ }
+ else
+ {
+ /* Stop any pending DMA transfer. */
+ s->fDMA = false;
+ ataHCPIOTransferStop(s);
+ ataUnsetStatus(s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
+ ataSetStatus(s, ATA_STAT_READY);
+ ataHCSetIRQ(s);
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
+ }
+
+ ataR3AsyncIORemoveCurrentRequest(pCtl, ReqType);
+ pReq = ataR3AsyncIOGetCurrentRequest(pCtl);
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+# if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_STOP(&pCtl->StatAsyncTime, a);
+# endif
+
+ u64TS = RTTimeNanoTS() - u64TS;
+ uWait = u64TS / 1000;
+ Log(("%s: Ctl#%d: LUN#%d finished I/O transaction in %d microseconds\n",
+ __FUNCTION__, ATACONTROLLER_IDX(pCtl), pCtl->aIfs[pCtl->iAIOIf].iLUN, (uint32_t)(uWait)));
+ /* Mark command as finished. */
+ pCtl->aIfs[pCtl->iAIOIf].u64CmdTS = 0;
+
+ /*
+ * Release logging of command execution times depends on the
+ * command type. ATAPI commands often take longer (due to CD/DVD
+ * spin up time etc.) so the threshold is different.
+ */
+ if (pCtl->aIfs[pCtl->iAIOIf].uATARegCommand != ATA_PACKET)
+ {
+ if (uWait > 8 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 8 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATA command %#04x was %d seconds\n",
+ pCtl->aIfs[pCtl->iAIOIf].uATARegCommand, uWait / (1000 * 1000)));
+ }
+ }
+ else
+ {
+ if (uWait > 20 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 20 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATAPI command %#04x was %d seconds\n",
+ pCtl->aIfs[pCtl->iAIOIf].aATAPICmd[0], uWait / (1000 * 1000)));
+ }
+ }
+
+# if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ if (uWait < pCtl->StatAsyncMinWait || !pCtl->StatAsyncMinWait)
+ pCtl->StatAsyncMinWait = uWait;
+ if (uWait > pCtl->StatAsyncMaxWait)
+ pCtl->StatAsyncMaxWait = uWait;
+
+ STAM_COUNTER_ADD(&pCtl->StatAsyncTimeUS, uWait);
+ STAM_COUNTER_INC(&pCtl->StatAsyncOps);
+# endif /* DEBUG || VBOX_WITH_STATISTICS */
+ }
+
+ ataR3LockLeave(pCtl);
+ }
+
+ /* Signal the ultimate idleness. */
+ RTThreadUserSignal(pCtl->AsyncIOThread);
+ if (pCtl->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pCtl->pDevInsR3);
+
+ /* Cleanup the state. */
+ /* Do not destroy request lock yet, still needed for proper shutdown. */
+ pCtl->fShutdown = false;
+
+ Log2(("%s: Ctl#%d: return %Rrc\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl), rc));
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+static uint32_t ataBMDMACmdReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Cmd;
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+
+static void ataBMDMACmdWriteB(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ if (!(val & BM_CMD_START))
+ {
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+ }
+ else
+ {
+#ifndef IN_RC
+ /* Check whether the guest OS wants to change DMA direction in
+ * mid-flight. Not allowed, according to the PIIX3 specs. */
+ Assert(!(pCtl->BmDma.u8Status & BM_STATUS_DMAING) || !((val ^ pCtl->BmDma.u8Cmd) & 0x04));
+ uint8_t uOldBmDmaStatus = pCtl->BmDma.u8Status;
+ pCtl->BmDma.u8Status |= BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+
+ /* Do not continue DMA transfers while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing DMA transfer as RESET is active\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ return;
+ }
+
+ /* Do not start DMA transfers if there's a PIO transfer going on,
+ * or if there is already a transfer started on this controller. */
+ if ( !pCtl->aIfs[pCtl->iSelectedIf].fDMA
+ || (uOldBmDmaStatus & BM_STATUS_DMAING))
+ return;
+
+ if (pCtl->aIfs[pCtl->iAIOIf].uATARegStatus & ATA_STAT_DRQ)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__, ATACONTROLLER_IDX(pCtl)));
+ ataHCAsyncIOPutRequest(pCtl, &g_ataDMARequest);
+ }
+#else /* !IN_RING3 */
+ AssertMsgFailed(("DMA START handling is too complicated for RC\n"));
+#endif /* IN_RING3 */
+ }
+}
+
+static uint32_t ataBMDMAStatusReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Status;
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAStatusWriteB(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.u8Status = (val & (BM_STATUS_D0DMA | BM_STATUS_D1DMA))
+ | (pCtl->BmDma.u8Status & BM_STATUS_DMAING)
+ | (pCtl->BmDma.u8Status & ~val & (BM_STATUS_ERROR | BM_STATUS_INT));
+}
+
+static uint32_t ataBMDMAAddrReadL(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = (uint32_t)pCtl->BmDma.GCPhysAddr;
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAAddrWriteL(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.GCPhysAddr = val & ~3;
+}
+
+static void ataBMDMAAddrWriteLowWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF1(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.GCPhysAddr = (pCtl->BmDma.GCPhysAddr & 0xFFFF0000) | RT_LOWORD(val & ~3);
+
+}
+
+static void ataBMDMAAddrWriteHighWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ RT_NOREF1(addr);
+ pCtl->BmDma.GCPhysAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.GCPhysAddr);
+}
+
+#define VAL(port, size) ( ((port) & 7) | ((size) << 3) )
+
+/**
+ * Port I/O Handler for bus master DMA IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ switch (VAL(Port, cb))
+ {
+ case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+ case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
+ case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+ case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
+ case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, Port); break;
+ case VAL(0, 4):
+ /* The SCO OpenServer tries to read 4 bytes starting from offset 0. */
+ *pu32 = ataBMDMACmdReadB(pCtl, Port) | (ataBMDMAStatusReadB(pCtl, Port) << 16);
+ break;
+ default:
+ AssertMsgFailed(("%s: Unsupported read from port %x size=%d\n", __FUNCTION__, Port, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ break;
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+/**
+ * Port I/O Handler for bus master DMA OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ switch (VAL(Port, cb))
+ {
+ case VAL(0, 1):
+#ifdef IN_RC
+ if (u32 & BM_CMD_START)
+ {
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ break;
+ }
+#endif
+ ataBMDMACmdWriteB(pCtl, Port, u32);
+ break;
+ case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, Port, u32); break;
+ case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, Port, u32); break;
+ case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, Port, u32); break;
+ case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, Port, u32); break;
+ default: AssertMsgFailed(("%s: Unsupported write to port %x size=%d val=%x\n", __FUNCTION__, Port, cb, u32)); break;
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ return rc;
+}
+
+#undef VAL
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) ataR3BMDMAIORangeMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, cb, enmType, pPciDev);
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc = VINF_SUCCESS;
+ Assert(enmType == PCI_ADDRESS_SPACE_IO);
+ Assert(iRegion == 4);
+ AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));
+
+ /* Register the port range. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int rc2 = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTHCPTR)(uintptr_t)i, ataBMDMAIOPortWrite, ataBMDMAIOPortRead,
+ NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+
+ if (pThis->fRCEnabled)
+ {
+ rc2 = PDMDevHlpIOPortRegisterRC(pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTGCPTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead",
+ NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+ }
+ if (pThis->fR0Enabled)
+ {
+ rc2 = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
+ (RTR0PTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead",
+ NULL, NULL, "ATA Bus Master DMA");
+ AssertRC(rc2);
+ if (rc2 < rc)
+ rc = rc2;
+ }
+ }
+ return rc;
+}
+
+
+/* -=-=-=-=-=- PCIATAState::IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ataR3Status_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PCIATAState *pThis = RT_FROM_MEMBER(pInterface, PCIATAState, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->ILeds);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- PCIATAState::ILeds -=-=-=-=-=- */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ataR3Status_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PCIATAState *pThis = RT_FROM_MEMBER(pInterface, PCIATAState, ILeds);
+ if (iLUN < 4)
+ {
+ switch (iLUN)
+ {
+ case 0: *ppLed = &pThis->aCts[0].aIfs[0].Led; break;
+ case 1: *ppLed = &pThis->aCts[0].aIfs[1].Led; break;
+ case 2: *ppLed = &pThis->aCts[1].aIfs[0].Led; break;
+ case 3: *ppLed = &pThis->aCts[1].aIfs[1].Led; break;
+ }
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/* -=-=-=-=-=- ATADevState::IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ataR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ ATADevState *pIf = RT_FROM_MEMBER(pInterface, ATADevState, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pIf->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pIf->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pIf->IMountNotify);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- ATADevState::IPort -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) ataR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ ATADevState *pIf = RT_FROM_MEMBER(pInterface, ATADevState, IPort);
+ PPDMDEVINS pDevIns = pIf->CTX_SUFF(pDevIns);
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pIf->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/* -=-=-=-=-=- Wrappers -=-=-=-=-=- */
+
+
+/**
+ * Port I/O Handler for primary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWrite1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ Assert(i < 2);
+ Assert(Port != pCtl->IOPortBase1);
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Writes to the other command block ports should be 8-bit only. If they
+ * are not, the high bits are simply discarded. Undocumented, but observed
+ * on a real PIIX4 system.
+ */
+ if (cb > 1)
+ Log(("ataIOPortWrite1: suspect write to port %x val=%x size=%d\n", Port, u32, cb));
+
+ rc = ataIOPortWriteU8(pCtl, Port, u32);
+
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for primary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortRead1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ Assert(i < 2);
+ Assert(Port != pCtl->IOPortBase1);
+
+ int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Reads from the other command block registers should be 8-bit only.
+ * If they are not, the low byte is propagated to the high bits.
+ * Undocumented, but observed on a real PIIX4 system.
+ */
+ rc = ataIOPortReadU8(pCtl, Port, pu32);
+ if (cb > 1)
+ {
+ uint32_t pad;
+
+ /* Replicate the 8-bit result into the upper three bytes. */
+ pad = *pu32 & 0xff;
+ pad = pad | (pad << 8);
+ pad = pad | (pad << 16);
+ *pu32 = pad;
+ Log(("ataIOPortRead1: suspect read from port %x size=%d\n", Port, cb));
+ }
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for secondary port range OUT operations.
+ * @see FNIOMIOPORTOUT for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ Assert(i < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ rc = ataControlWrite(pCtl, Port, u32);
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for secondary port range IN operations.
+ * @see FNIOMIOPORTIN for details.
+ */
+PDMBOTHCBDECL(int) ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t i = (uint32_t)(uintptr_t)pvUser;
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ int rc;
+
+ Assert(i < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ *pu32 = ataStatusRead(pCtl, Port);
+ PDMCritSectLeave(&pCtl->lock);
+ }
+ }
+ else
+ rc = VERR_IOM_IOPORT_UNUSED;
+ return rc;
+}
+
+#ifdef IN_RING3
+
+
+DECLINLINE(void) ataR3RelocBuffer(PPDMDEVINS pDevIns, ATADevState *s)
+{
+ if (s->pbIOBufferR3)
+ s->pbIOBufferRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), s->pbIOBufferR3);
+}
+
+
+/**
+ * Detach notification.
+ *
+ * The DVD drive has been unplugged.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) ataR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PIIX3IDE: Device does not support hotplugging\n")); RT_NOREF(fFlags);
+
+ /*
+ * Locate the controller and stuff.
+ */
+ unsigned iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ PATACONTROLLER pCtl = &pThis->aCts[iController];
+
+ unsigned iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ ATADevState *pIf = &pCtl->aIfs[iInterface];
+
+ /*
+ * Zero some important members.
+ */
+ pIf->pDrvBase = NULL;
+ pIf->pDrvMedia = NULL;
+ pIf->pDrvMount = NULL;
+
+ /*
+ * In case there was a medium inserted.
+ */
+ ataR3MediumRemoved(pIf);
+}
+
+
+/**
+ * Configure a LUN.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pIf The ATA unit state.
+ */
+static int ataR3ConfigLun(PPDMDEVINS pDevIns, ATADevState *pIf)
+{
+ int rc = VINF_SUCCESS;
+ PDMMEDIATYPE enmType;
+
+ /*
+ * Query Block, Bios and Mount interfaces.
+ */
+ pIf->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pIf->pDrvBase, PDMIMEDIA);
+ if (!pIf->pDrvMedia)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ pIf->pDrvMount = PDMIBASE_QUERY_INTERFACE(pIf->pDrvBase, PDMIMOUNT);
+
+ /*
+ * Validate type.
+ */
+ enmType = pIf->pDrvMedia->pfnGetType(pIf->pDrvMedia);
+ if ( enmType != PDMMEDIATYPE_CDROM
+ && enmType != PDMMEDIATYPE_DVD
+ && enmType != PDMMEDIATYPE_HARD_DISK)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd-rom. enmType=%d\n", pIf->iLUN, enmType));
+ return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+ }
+ if ( ( enmType == PDMMEDIATYPE_DVD
+ || enmType == PDMMEDIATYPE_CDROM)
+ && !pIf->pDrvMount)
+ {
+ AssertMsgFailed(("Internal error: cdrom without a mountable interface, WTF???!\n"));
+ return VERR_INTERNAL_ERROR;
+ }
+ pIf->fATAPI = enmType == PDMMEDIATYPE_DVD || enmType == PDMMEDIATYPE_CDROM;
+ pIf->fATAPIPassthrough = pIf->fATAPI ? (pIf->pDrvMedia->pfnSendCmd != NULL) : false;
+
+ /*
+ * Allocate I/O buffer.
+ */
+ if (pIf->fATAPI)
+ pIf->cbSector = 2048; /* Not required for ATAPI, one medium can have multiple sector sizes. */
+ else
+ pIf->cbSector = pIf->pDrvMedia->pfnGetSectorSize(pIf->pDrvMedia);
+
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ if (pIf->cbIOBuffer)
+ {
+ /* Buffer is (probably) already allocated. Validate the fields,
+ * because memory corruption can also overwrite pIf->cbIOBuffer. */
+ if (pIf->fATAPI)
+ AssertRelease(pIf->cbIOBuffer == _128K);
+ else
+ AssertRelease(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * pIf->cbSector);
+ Assert(pIf->pbIOBufferR3);
+ Assert(pIf->pbIOBufferR0 == MMHyperR3ToR0(pVM, pIf->pbIOBufferR3));
+ Assert(pIf->pbIOBufferRC == MMHyperR3ToRC(pVM, pIf->pbIOBufferR3));
+ }
+ else
+ {
+ if (pIf->fATAPI)
+ pIf->cbIOBuffer = _128K;
+ else
+ pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * pIf->cbSector;
+ Assert(!pIf->pbIOBufferR3);
+ rc = MMR3HyperAllocOnceNoRel(pVM, pIf->cbIOBuffer, 0, MM_TAG_PDM_DEVICE_USER, (void **)&pIf->pbIOBufferR3);
+ if (RT_FAILURE(rc))
+ return VERR_NO_MEMORY;
+ pIf->pbIOBufferR0 = MMHyperR3ToR0(pVM, pIf->pbIOBufferR3);
+ pIf->pbIOBufferRC = MMHyperR3ToRC(pVM, pIf->pbIOBufferR3);
+ }
+
+ /*
+ * Init geometry (only for non-CD/DVD media).
+ */
+ uint32_t cRegions = pIf->pDrvMedia->pfnGetRegionCount(pIf->pDrvMedia);
+ pIf->cTotalSectors = 0;
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ rc = pIf->pDrvMedia->pfnQueryRegionProperties(pIf->pDrvMedia, i, NULL, &cBlocks,
+ NULL, NULL);
+ AssertRC(rc);
+ pIf->cTotalSectors += cBlocks;
+ }
+
+ if (pIf->fATAPI)
+ {
+ pIf->PCHSGeometry.cCylinders = 0; /* dummy */
+ pIf->PCHSGeometry.cHeads = 0; /* dummy */
+ pIf->PCHSGeometry.cSectors = 0; /* dummy */
+ LogRel(("PIIX3 ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n",
+ pIf->iLUN, pIf->cTotalSectors, (pIf->fATAPIPassthrough ? "enabled" : "disabled")));
+ }
+ else
+ {
+ rc = pIf->pDrvMedia->pfnBiosGetPCHSGeometry(pIf->pDrvMedia, &pIf->PCHSGeometry);
+ if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ {
+ pIf->PCHSGeometry.cCylinders = 0;
+ pIf->PCHSGeometry.cHeads = 16; /*??*/
+ pIf->PCHSGeometry.cSectors = 63; /*??*/
+ }
+ else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+ {
+ pIf->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+ rc = VINF_SUCCESS;
+ }
+ AssertRC(rc);
+
+ if ( pIf->PCHSGeometry.cCylinders == 0
+ || pIf->PCHSGeometry.cHeads == 0
+ || pIf->PCHSGeometry.cSectors == 0
+ )
+ {
+ uint64_t cCylinders = pIf->cTotalSectors / (16 * 63);
+ pIf->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+ pIf->PCHSGeometry.cHeads = 16;
+ pIf->PCHSGeometry.cSectors = 63;
+ /* Set the disk geometry information. Ignore errors. */
+ pIf->pDrvMedia->pfnBiosSetPCHSGeometry(pIf->pDrvMedia, &pIf->PCHSGeometry);
+ rc = VINF_SUCCESS;
+ }
+ LogRel(("PIIX3 ATA: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n",
+ pIf->iLUN, pIf->PCHSGeometry.cCylinders, pIf->PCHSGeometry.cHeads, pIf->PCHSGeometry.cSectors,
+ pIf->cTotalSectors));
+
+ if (pIf->pDrvMedia->pfnDiscard)
+ LogRel(("PIIX3 ATA: LUN#%d: TRIM enabled\n", pIf->iLUN));
+ }
+
+ /*
+ * Check if SMP system to adjust the agressiveness of the busy yield hack (@bugref{1960}).
+ *
+ * The hack is an ancient (2006?) one for dealing with UNI CPU systems where EMT
+ * would potentially monopolise the CPU and starve I/O threads. It causes the EMT to
+ * yield it's timeslice if the guest polls the status register during I/O. On modern
+ * multicore and multithreaded systems, yielding EMT too often may have adverse
+ * effects (slow grub) so we aim at avoiding repeating the yield there too often.
+ */
+ RTCPUID cCpus = RTMpGetOnlineCount();
+ if (cCpus <= 1)
+ {
+ pIf->cBusyStatusHackR3Rate = 1;
+ pIf->cBusyStatusHackRZRate = 7;
+ }
+ else if (cCpus <= 2)
+ {
+ pIf->cBusyStatusHackR3Rate = 3;
+ pIf->cBusyStatusHackRZRate = 15;
+ }
+ else if (cCpus <= 4)
+ {
+ pIf->cBusyStatusHackR3Rate = 15;
+ pIf->cBusyStatusHackRZRate = 31;
+ }
+ else
+ {
+ pIf->cBusyStatusHackR3Rate = 127;
+ pIf->cBusyStatusHackRZRate = 127;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for the DVD drive.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int) ataR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PATACONTROLLER pCtl;
+ ATADevState *pIf;
+ int rc;
+ unsigned iController;
+ unsigned iInterface;
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PIIX3IDE: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Locate the controller and stuff.
+ */
+ iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ pCtl = &pThis->aCts[iController];
+
+ iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ pIf = &pCtl->aIfs[iInterface];
+
+ /* the usual paranoia */
+ AssertRelease(!pIf->pDrvBase);
+ AssertRelease(!pIf->pDrvMedia);
+ Assert(ATADEVSTATE_2_CONTROLLER(pIf) == pCtl);
+ Assert(pIf->iLUN == iLUN);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pDevIns, pIf);
+ /*
+ * In case there is a medium inserted.
+ */
+ ataR3MediumInserted(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_TYPE_UNKNOWN);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pIf->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pIf->pDrvBase = NULL;
+ pIf->pDrvMedia = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Resume(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+
+ Log(("%s:\n", __FUNCTION__));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].fRedo && pThis->aCts[i].fRedoIdle)
+ {
+ rc = RTSemEventSignal(pThis->aCts[i].SuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+ return;
+}
+
+
+/**
+ * Checks if all (both) the async I/O threads have quiesced.
+ *
+ * @returns true on success.
+ * @returns false when one or more threads is still processing.
+ * @param pDevIns Pointer to the PDM device instance.
+ */
+static bool ataR3AllAsyncIOIsIdle(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ bool fRc = ataR3AsyncIOIsIdle(&pThis->aCts[i], false /*fStrict*/);
+ if (!fRc)
+ {
+ /* Make it signal PDM & itself when its done */
+ PDMCritSectEnter(&pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ ASMAtomicWriteBool(&pThis->aCts[i].fSignalIdle, true);
+ PDMCritSectLeave(&pThis->aCts[i].AsyncIORequestLock);
+
+ fRc = ataR3AsyncIOIsIdle(&pThis->aCts[i], false /*fStrict*/);
+ if (!fRc)
+ {
+#if 0 /** @todo Need to do some time tracking here... */
+ LogRel(("PIIX3 ATA: Ctl#%u is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x\n",
+ i, pThis->aCts[i].iSelectedIf, pThis->aCts[i].iAIOIf,
+ pThis->aCts[i].aIfs[0].uATARegCommand, pThis->aCts[i].aIfs[1].uATARegCommand));
+#endif
+ return false;
+ }
+ }
+ ASMAtomicWriteBool(&pThis->aCts[i].fSignalIdle, false);
+ }
+ return true;
+}
+
+/**
+ * Prepare state save and load operation.
+ *
+ * @returns VBox status code.
+ * @param pDevIns Device instance of the device which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) ataR3SaveLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF1(pSSM);
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ /* sanity - the suspend notification will wait on the async stuff. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ AssertLogRelMsgReturn(ataR3AsyncIOIsIdle(&pThis->aCts[i], false /*fStrict*/),
+ ("i=%u\n", i),
+ VERR_SSM_IDE_ASYNC_TIMEOUT);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNSSMDEVLIVEEXEC
+ */
+static DECLCALLBACK(int) ataR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF1(uPass);
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ SSMR3PutU8(pSSM, pThis->u8Type);
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ SSMR3PutBool(pSSM, true); /* For controller enabled / disabled. */
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].pDrvBase != NULL);
+ SSMR3PutStrZ(pSSM, pThis->aCts[i].aIfs[j].szSerialNumber);
+ SSMR3PutStrZ(pSSM, pThis->aCts[i].aIfs[j].szFirmwareRevision);
+ SSMR3PutStrZ(pSSM, pThis->aCts[i].aIfs[j].szModelNumber);
+ }
+ }
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @copydoc FNSSMDEVSAVEEXEC
+ */
+static DECLCALLBACK(int) ataR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ ataR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ SSMR3PutU8(pSSM, pThis->aCts[i].iSelectedIf);
+ SSMR3PutU8(pSSM, pThis->aCts[i].iAIOIf);
+ SSMR3PutU8(pSSM, pThis->aCts[i].uAsyncIOState);
+ SSMR3PutBool(pSSM, pThis->aCts[i].fChainedTransfer);
+ SSMR3PutBool(pSSM, pThis->aCts[i].fReset);
+ SSMR3PutBool(pSSM, pThis->aCts[i].fRedo);
+ SSMR3PutBool(pSSM, pThis->aCts[i].fRedoIdle);
+ SSMR3PutBool(pSSM, pThis->aCts[i].fRedoDMALastDesc);
+ SSMR3PutMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ SSMR3PutGCPhys32(pSSM, pThis->aCts[i].GCPhysFirstDMADesc);
+ SSMR3PutGCPhys32(pSSM, pThis->aCts[i].GCPhysLastDMADesc);
+ SSMR3PutGCPhys32(pSSM, pThis->aCts[i].GCPhysRedoDMABuffer);
+ SSMR3PutU32(pSSM, pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].fLBA48);
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPI);
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].fIrqPending);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].cMultSectors);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].PCHSGeometry.cCylinders);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].PCHSGeometry.cHeads);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].PCHSGeometry.cSectors);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ SSMR3PutU64(pSSM, pThis->aCts[i].aIfs[j].cTotalSectors);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeature);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegError);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSector);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSector);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCyl);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCyl);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSelect);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegStatus);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegCommand);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uATATransferMode);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].uTxDir);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].iBeginTransfer);
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].iSourceSink);
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].fDMA);
+ SSMR3PutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPITransfer);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferCur);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].iATAPILBA);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].cbATAPISector);
+ SSMR3PutMem(pSSM, &pThis->aCts[i].aIfs[j].aATAPICmd, sizeof(pThis->aCts[i].aIfs[j].aATAPICmd));
+ SSMR3PutMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ SSMR3PutU8(pSSM, pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].MediaEventStatus);
+ SSMR3PutMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ SSMR3PutU32(pSSM, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ SSMR3PutMem(pSSM, pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer), pThis->aCts[i].aIfs[j].cbIOBuffer);
+ else
+ Assert(pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+ }
+ }
+
+ return SSMR3PutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * Converts the LUN number into a message string.
+ */
+static const char *ataR3StringifyLun(unsigned iLun)
+{
+ switch (iLun)
+ {
+ case 0: return "primary master";
+ case 1: return "primary slave";
+ case 2: return "secondary master";
+ case 3: return "secondary slave";
+ default: AssertFailedReturn("unknown lun");
+ }
+}
+
+/**
+ * FNSSMDEVLOADEXEC
+ */
+static DECLCALLBACK(int) ataR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+ uint32_t u32;
+
+ if ( uVersion != ATA_SAVED_STATE_VERSION
+ && uVersion != ATA_SAVED_STATE_VERSION_VBOX_30
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS
+ && uVersion != ATA_SAVED_STATE_VERSION_WITH_BOOL_TYPE)
+ {
+ AssertMsgFailed(("uVersion=%d\n", uVersion));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ /*
+ * Verify the configuration.
+ */
+ if (uVersion > ATA_SAVED_STATE_VERSION_VBOX_30)
+ {
+ uint8_t u8Type;
+ rc = SSMR3GetU8(pSSM, &u8Type);
+ AssertRCReturn(rc, rc);
+ if (u8Type != pThis->u8Type)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: u8Type - saved=%u config=%u"), u8Type, pThis->u8Type);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ bool fEnabled;
+ rc = SSMR3GetBool(pSSM, &fEnabled);
+ AssertRCReturn(rc, rc);
+ if (!fEnabled)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Ctr#%u onfig mismatch: fEnabled != true"), i);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADevState const *pIf = &pThis->aCts[i].aIfs[j];
+
+ bool fInUse;
+ rc = SSMR3GetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pIf->pDrvBase != NULL))
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS,
+ N_("The %s VM is missing a %s device. Please make sure the source and target VMs have compatible storage configurations"),
+ fInUse ? "target" : "source", ataR3StringifyLun(pIf->iLUN) );
+
+ char szSerialNumber[ATA_SERIAL_NUMBER_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szSerialNumber, sizeof(szSerialNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szSerialNumber, pIf->szSerialNumber))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Serial number - saved='%s' config='%s'\n",
+ pIf->iLUN, szSerialNumber, pIf->szSerialNumber));
+
+ char szFirmwareRevision[ATA_FIRMWARE_REVISION_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szFirmwareRevision, sizeof(szFirmwareRevision));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szFirmwareRevision, pIf->szFirmwareRevision))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Firmware revision - saved='%s' config='%s'\n",
+ pIf->iLUN, szFirmwareRevision, pIf->szFirmwareRevision));
+
+ char szModelNumber[ATA_MODEL_NUMBER_LENGTH+1];
+ rc = SSMR3GetStrZ(pSSM, szModelNumber, sizeof(szModelNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szModelNumber, pIf->szModelNumber))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Model number - saved='%s' config='%s'\n",
+ pIf->iLUN, szModelNumber, pIf->szModelNumber));
+ }
+ }
+ }
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ /*
+ * Restore valid parts of the PCIATAState structure
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ /* integrity check */
+ if (!ataR3AsyncIOIsIdle(&pThis->aCts[i], false))
+ {
+ AssertMsgFailed(("Async I/O for controller %d is active\n", i));
+ return VERR_INTERNAL_ERROR_4;
+ }
+
+ SSMR3GetU8(pSSM, &pThis->aCts[i].iSelectedIf);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].iAIOIf);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].uAsyncIOState);
+ SSMR3GetBool(pSSM, &pThis->aCts[i].fChainedTransfer);
+ SSMR3GetBool(pSSM, (bool *)&pThis->aCts[i].fReset);
+ SSMR3GetBool(pSSM, (bool *)&pThis->aCts[i].fRedo);
+ SSMR3GetBool(pSSM, (bool *)&pThis->aCts[i].fRedoIdle);
+ SSMR3GetBool(pSSM, (bool *)&pThis->aCts[i].fRedoDMALastDesc);
+ SSMR3GetMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ SSMR3GetGCPhys32(pSSM, &pThis->aCts[i].GCPhysFirstDMADesc);
+ SSMR3GetGCPhys32(pSSM, &pThis->aCts[i].GCPhysLastDMADesc);
+ SSMR3GetGCPhys32(pSSM, &pThis->aCts[i].GCPhysRedoDMABuffer);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ SSMR3GetBool(pSSM, &pThis->aCts[i].aIfs[j].fLBA48);
+ SSMR3GetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPI);
+ SSMR3GetBool(pSSM, &pThis->aCts[i].aIfs[j].fIrqPending);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].cMultSectors);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].PCHSGeometry.cCylinders);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].PCHSGeometry.cHeads);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].PCHSGeometry.cSectors);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ SSMR3GetU64(pSSM, &pThis->aCts[i].aIfs[j].cTotalSectors);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeature);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegError);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSector);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSector);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCyl);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCyl);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSelect);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegStatus);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegCommand);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uATATransferMode);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].uTxDir);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].iBeginTransfer);
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].iSourceSink);
+ SSMR3GetBool(pSSM, &pThis->aCts[i].aIfs[j].fDMA);
+ SSMR3GetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPITransfer);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ /* NB: cbPIOTransferLimit could be saved/restored but it's sufficient
+ * to re-calculate it here, with a tiny risk that it could be
+ * unnecessarily low for the current transfer only. Could be changed
+ * when changing the saved state in the future.
+ */
+ pThis->aCts[i].aIfs[j].cbPIOTransferLimit = (pThis->aCts[i].aIfs[j].uATARegHCyl << 8) | pThis->aCts[i].aIfs[j].uATARegLCyl;
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferCur);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].iATAPILBA);
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].cbATAPISector);
+ SSMR3GetMem(pSSM, &pThis->aCts[i].aIfs[j].aATAPICmd, sizeof(pThis->aCts[i].aIfs[j].aATAPICmd));
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ {
+ SSMR3GetMem(pSSM, pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ }
+ else
+ {
+ uint8_t uATAPISenseKey, uATAPIASC;
+ memset(pThis->aCts[i].aIfs[j].abATAPISense, '\0', sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ pThis->aCts[i].aIfs[j].abATAPISense[0] = 0x70 | (1 << 7);
+ pThis->aCts[i].aIfs[j].abATAPISense[7] = 10;
+ SSMR3GetU8(pSSM, &uATAPISenseKey);
+ SSMR3GetU8(pSSM, &uATAPIASC);
+ pThis->aCts[i].aIfs[j].abATAPISense[2] = uATAPISenseKey & 0x0f;
+ pThis->aCts[i].aIfs[j].abATAPISense[12] = uATAPIASC;
+ }
+ /** @todo triple-check this hack after passthrough is working */
+ SSMR3GetU8(pSSM, &pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ SSMR3GetU32(pSSM, (uint32_t*)&pThis->aCts[i].aIfs[j].MediaEventStatus);
+ else
+ pThis->aCts[i].aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
+ SSMR3GetMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ SSMR3GetU32(pSSM, &pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ {
+ if (pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer))
+ SSMR3GetMem(pSSM, pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer), pThis->aCts[i].aIfs[j].cbIOBuffer);
+ else
+ {
+ LogRel(("ATA: No buffer for %d/%d\n", i, j));
+ if (SSMR3HandleGetAfter(pSSM) != SSMAFTER_DEBUG_IT)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("No buffer for %d/%d"), i, j);
+
+ /* skip the buffer if we're loading for the debugger / animator. */
+ uint8_t u8Ignored;
+ size_t cbLeft = pThis->aCts[i].aIfs[j].cbIOBuffer;
+ while (cbLeft-- > 0)
+ SSMR3GetU8(pSSM, &u8Ignored);
+ }
+ }
+ else
+ Assert(pThis->aCts[i].aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
+ }
+ }
+ if (uVersion <= ATA_SAVED_STATE_VERSION_VBOX_30)
+ SSMR3GetU8(pSSM, &pThis->u8Type);
+
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32 != ~0U)
+ {
+ AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+ rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Callback employed by ataSuspend and ataR3PowerOff.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ataR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns)
+{
+ return ataR3AllAsyncIOIsIdle(pDevIns);
+}
+
+
+/**
+ * Common worker for ataSuspend and ataR3PowerOff.
+ */
+static void ataR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ if (!ataR3AllAsyncIOIsIdle(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ataR3IsAsyncSuspendOrPowerOffDone);
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ ataR3SuspendOrPowerOff(pDevIns);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Suspend(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ ataR3SuspendOrPowerOff(pDevIns);
+}
+
+
+/**
+ * Callback employed by ataR3Reset.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ataR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ if (!ataR3AllAsyncIOIsIdle(pDevIns))
+ return false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ PDMCritSectEnter(&pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ ataR3ResetDevice(&pThis->aCts[i].aIfs[j]);
+ PDMCritSectLeave(&pThis->aCts[i].lock);
+ }
+ return true;
+}
+
+
+/**
+ * Common reset worker for ataR3Reset and ataR3Construct.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ * @param fConstruct Indicates who is calling.
+ */
+static int ataR3ResetCommon(PPDMDEVINS pDevIns, bool fConstruct)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ PDMCritSectEnter(&pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+
+ pThis->aCts[i].iSelectedIf = 0;
+ pThis->aCts[i].iAIOIf = 0;
+ pThis->aCts[i].BmDma.u8Cmd = 0;
+ /* Report that both drives present on the bus are in DMA mode. This
+ * pretends that there is a BIOS that has set it up. Normal reset
+ * default is 0x00. */
+ pThis->aCts[i].BmDma.u8Status = (pThis->aCts[i].aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
+ | (pThis->aCts[i].aIfs[1].pDrvBase != NULL ? BM_STATUS_D1DMA : 0);
+ pThis->aCts[i].BmDma.GCPhysAddr = 0;
+
+ pThis->aCts[i].fReset = true;
+ pThis->aCts[i].fRedo = false;
+ pThis->aCts[i].fRedoIdle = false;
+ ataR3AsyncIOClearRequests(&pThis->aCts[i]);
+ Log2(("%s: Ctl#%d: message to async I/O thread, reset controller\n", __FUNCTION__, i));
+ ataHCAsyncIOPutRequest(&pThis->aCts[i], &g_ataResetARequest);
+ ataHCAsyncIOPutRequest(&pThis->aCts[i], &g_ataResetCRequest);
+
+ PDMCritSectLeave(&pThis->aCts[i].lock);
+ }
+
+ int rcRet = VINF_SUCCESS;
+ if (!fConstruct)
+ {
+ /*
+ * Setup asynchronous notification completion if the requests haven't
+ * completed yet.
+ */
+ if (!ataR3IsAsyncResetDone(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ataR3IsAsyncResetDone);
+ }
+ else
+ {
+ /*
+ * Wait for the requests for complete.
+ *
+ * Would be real nice if we could do it all from EMT(0) and not
+ * involve the worker threads, then we could dispense with all the
+ * waiting and semaphore ping-pong here...
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ int rc = PDMCritSectEnter(&pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ AssertRC(rc);
+
+ ASMAtomicWriteBool(&pThis->aCts[i].fSignalIdle, true);
+ rc = RTThreadUserReset(pThis->aCts[i].AsyncIOThread);
+ AssertRC(rc);
+
+ rc = PDMCritSectLeave(&pThis->aCts[i].AsyncIORequestLock);
+ AssertRC(rc);
+
+ if (!ataR3AsyncIOIsIdle(&pThis->aCts[i], false /*fStrict*/))
+ {
+ rc = RTThreadUserWait(pThis->aCts[i].AsyncIOThread, 30*1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ rc = RTThreadUserWait(pThis->aCts[i].AsyncIOThread, 1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ rcRet = rc;
+ }
+ }
+ }
+ ASMAtomicWriteBool(&pThis->aCts[i].fSignalIdle, false);
+ }
+ if (RT_SUCCESS(rcRet))
+ {
+ rcRet = ataR3IsAsyncResetDone(pDevIns) ? VINF_SUCCESS : VERR_INTERNAL_ERROR;
+ AssertRC(rcRet);
+ }
+ }
+ return rcRet;
+}
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Reset(PPDMDEVINS pDevIns)
+{
+ ataR3ResetCommon(pDevIns, false /*fConstruct*/);
+}
+
+/**
+ * @copydoc FNPDMDEVRELOCATE
+ */
+static DECLCALLBACK(void) ataR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[0].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[0].pControllerRC += offDelta;
+ ataR3RelocBuffer(pDevIns, &pThis->aCts[i].aIfs[0]);
+ pThis->aCts[i].aIfs[1].pDevInsRC += offDelta;
+ pThis->aCts[i].aIfs[1].pControllerRC += offDelta;
+ ataR3RelocBuffer(pDevIns, &pThis->aCts[i].aIfs[1]);
+ }
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) ataR3Destruct(PPDMDEVINS pDevIns)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ int rc;
+
+ Log(("ataR3Destruct\n"));
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ /*
+ * Tell the async I/O threads to terminate.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteU32(&pThis->aCts[i].fShutdown, true);
+ rc = SUPSemEventSignal(pThis->aCts[i].pSupDrvSession, pThis->aCts[i].hAsyncIOSem);
+ AssertRC(rc);
+ rc = RTSemEventSignal(pThis->aCts[i].SuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+
+ /*
+ * Wait for the threads to terminate before destroying their resources.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThis->aCts[i].AsyncIOThread, 30000 /* 30 s*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->aCts[i].AsyncIOThread = NIL_RTTHREAD;
+ else
+ LogRel(("PIIX3 ATA Dtor: Ctl#%u is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x rc=%Rrc\n",
+ i, pThis->aCts[i].iSelectedIf, pThis->aCts[i].iAIOIf,
+ pThis->aCts[i].aIfs[0].uATARegCommand, pThis->aCts[i].aIfs[1].uATARegCommand, rc));
+ }
+ }
+
+ /*
+ * Free resources.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (PDMCritSectIsInitialized(&pThis->aCts[i].AsyncIORequestLock))
+ PDMR3CritSectDelete(&pThis->aCts[i].AsyncIORequestLock);
+ if (pThis->aCts[i].hAsyncIOSem != NIL_SUPSEMEVENT)
+ {
+ SUPSemEventClose(pThis->aCts[i].pSupDrvSession, pThis->aCts[i].hAsyncIOSem);
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ }
+ if (pThis->aCts[i].SuspendIOSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->aCts[i].SuspendIOSem);
+ pThis->aCts[i].SuspendIOSem = NIL_RTSEMEVENT;
+ }
+
+ /* try one final time */
+ if (pThis->aCts[i].AsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThis->aCts[i].AsyncIOThread, 1 /*ms*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->aCts[i].AsyncIOThread = NIL_RTTHREAD;
+ LogRel(("PIIX3 ATA Dtor: Ctl#%u actually completed.\n", i));
+ }
+ }
+
+ for (uint32_t iIf = 0; iIf < RT_ELEMENTS(pThis->aCts[i].aIfs); iIf++)
+ {
+ if (pThis->aCts[i].aIfs[iIf].pTrackList)
+ {
+ ATAPIPassthroughTrackListDestroy(pThis->aCts[i].aIfs[iIf].pTrackList);
+ pThis->aCts[i].aIfs[iIf].pTrackList = NULL;
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Convert config value to DEVPCBIOSBOOT.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ * @param pCfg Configuration handle.
+ * @param penmChipset Where to store the chipset type.
+ */
+static int ataR3ControllerFromCfg(PPDMDEVINS pDevIns, PCFGMNODE pCfg, CHIPSET *penmChipset)
+{
+ char szType[20];
+
+ int rc = CFGMR3QueryStringDef(pCfg, "Type", &szType[0], sizeof(szType), "PIIX4");
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("Configuration error: Querying \"Type\" as a string failed"));
+ if (!strcmp(szType, "PIIX3"))
+ *penmChipset = CHIPSET_PIIX3;
+ else if (!strcmp(szType, "PIIX4"))
+ *penmChipset = CHIPSET_PIIX4;
+ else if (!strcmp(szType, "ICH6"))
+ *penmChipset = CHIPSET_ICH6;
+ else
+ {
+ PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("Configuration error: The \"Type\" value \"%s\" is unknown"),
+ szType);
+ rc = VERR_INTERNAL_ERROR;
+ }
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ataR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
+ PPDMIBASE pBase;
+ int rc;
+ bool fRCEnabled;
+ bool fR0Enabled;
+ uint32_t DelayIRQMillies;
+
+ Assert(iInstance == 0);
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Initialize NIL handle values (for the destructor).
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ pThis->aCts[i].SuspendIOSem = NIL_RTSEMEVENT;
+ pThis->aCts[i].AsyncIOThread = NIL_RTTHREAD;
+ }
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg,
+ "GCEnabled\0"
+ "R0Enabled\0"
+ "IRQDelay\0"
+ "Type\0")
+ /** @todo || invalid keys */)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("PIIX3 configuration error: unknown option specified"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &fRCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read GCEnabled as boolean"));
+ Log(("%s: fRCEnabled=%d\n", __FUNCTION__, fRCEnabled));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read R0Enabled as boolean"));
+ Log(("%s: fR0Enabled=%d\n", __FUNCTION__, fR0Enabled));
+
+ rc = CFGMR3QueryU32Def(pCfg, "IRQDelay", &DelayIRQMillies, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read IRQDelay as integer"));
+ Log(("%s: DelayIRQMillies=%d\n", __FUNCTION__, DelayIRQMillies));
+ Assert(DelayIRQMillies < 50);
+
+ CHIPSET enmChipset = CHIPSET_PIIX3;
+ rc = ataR3ControllerFromCfg(pDevIns, pCfg, &enmChipset);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->u8Type = (uint8_t)enmChipset;
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* Status LUN. */
+ pThis->IBase.pfnQueryInterface = ataR3Status_QueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = ataR3Status_QueryStatusLed;
+
+ /* PCI configuration space. */
+ PCIDevSetVendorId(&pThis->dev, 0x8086); /* Intel */
+
+ /*
+ * When adding more IDE chipsets, don't forget to update pci_bios_init_device()
+ * as it explicitly checks for PCI id for IDE controllers.
+ */
+ switch (pThis->u8Type)
+ {
+ case CHIPSET_ICH6:
+ PCIDevSetDeviceId(&pThis->dev, 0x269e); /* ICH6 IDE */
+ /** @todo do we need it? Do we need anything else? */
+ pThis->dev.abConfig[0x48] = 0x00; /* UDMACTL */
+ pThis->dev.abConfig[0x4A] = 0x00; /* UDMATIM */
+ pThis->dev.abConfig[0x4B] = 0x00;
+ {
+ /*
+ * See www.intel.com/Assets/PDF/manual/298600.pdf p. 30
+ * Report
+ * WR_Ping-Pong_EN: must be set
+ * PCR0, PCR1: 80-pin primary cable reporting for both disks
+ * SCR0, SCR1: 80-pin secondary cable reporting for both disks
+ */
+ uint16_t u16Config = (1<<10) | (1<<7) | (1<<6) | (1<<5) | (1<<4) ;
+ pThis->dev.abConfig[0x54] = u16Config & 0xff;
+ pThis->dev.abConfig[0x55] = u16Config >> 8;
+ }
+ break;
+ case CHIPSET_PIIX4:
+ PCIDevSetDeviceId(&pThis->dev, 0x7111); /* PIIX4 IDE */
+ PCIDevSetRevisionId(&pThis->dev, 0x01); /* PIIX4E */
+ pThis->dev.abConfig[0x48] = 0x00; /* UDMACTL */
+ pThis->dev.abConfig[0x4A] = 0x00; /* UDMATIM */
+ pThis->dev.abConfig[0x4B] = 0x00;
+ break;
+ case CHIPSET_PIIX3:
+ PCIDevSetDeviceId(&pThis->dev, 0x7010); /* PIIX3 IDE */
+ break;
+ default:
+ AssertMsgFailed(("Unsupported IDE chipset type: %d\n", pThis->u8Type));
+ }
+
+ /** @todo
+ * This is the job of the BIOS / EFI!
+ *
+ * The same is done in DevPCI.cpp / pci_bios_init_device() but there is no
+ * corresponding function in DevPciIch9.cpp. The EFI has corresponding code
+ * in OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c: NotifyDev() but this
+ * function assumes that the IDE controller is located at PCI 00:01.1 which
+ * is not true if the ICH9 chipset is used.
+ */
+ PCIDevSetWord(&pThis->dev, 0x40, 0x8000); /* enable IDE0 */
+ PCIDevSetWord(&pThis->dev, 0x42, 0x8000); /* enable IDE1 */
+
+ PCIDevSetCommand( &pThis->dev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS | PCI_COMMAND_BUSMASTER);
+ PCIDevSetClassProg( &pThis->dev, 0x8a); /* programming interface = PCI_IDE bus master is supported */
+ PCIDevSetClassSub( &pThis->dev, 0x01); /* class_sub = PCI_IDE */
+ PCIDevSetClassBase( &pThis->dev, 0x01); /* class_base = PCI_mass_storage */
+ PCIDevSetHeaderType(&pThis->dev, 0x00);
+
+ pThis->pDevIns = pDevIns;
+ pThis->fRCEnabled = fRCEnabled;
+ pThis->fR0Enabled = fR0Enabled;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].pDevInsR3 = pDevIns;
+ pThis->aCts[i].pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->aCts[i].pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->aCts[i].DelayIRQMillies = (uint32_t)DelayIRQMillies;
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADevState *pIf = &pThis->aCts[i].aIfs[j];
+
+ pIf->iLUN = i * RT_ELEMENTS(pThis->aCts) + j;
+ pIf->pDevInsR3 = pDevIns;
+ pIf->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pIf->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pIf->pControllerR3 = &pThis->aCts[i];
+ pIf->pControllerR0 = MMHyperR3ToR0(PDMDevHlpGetVM(pDevIns), &pThis->aCts[i]);
+ pIf->pControllerRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), &pThis->aCts[i]);
+ pIf->IBase.pfnQueryInterface = ataR3QueryInterface;
+ pIf->IMountNotify.pfnMountNotify = ataR3MountNotify;
+ pIf->IMountNotify.pfnUnmountNotify = ataR3UnmountNotify;
+ pIf->IPort.pfnQueryDeviceLocation = ataR3QueryDeviceLocation;
+ pIf->Led.u32Magic = PDMLED_MAGIC;
+ }
+ }
+
+ Assert(RT_ELEMENTS(pThis->aCts) == 2);
+ pThis->aCts[0].irq = 14;
+ pThis->aCts[0].IOPortBase1 = 0x1f0;
+ pThis->aCts[0].IOPortBase2 = 0x3f6;
+ pThis->aCts[1].irq = 15;
+ pThis->aCts[1].IOPortBase1 = 0x170;
+ pThis->aCts[1].IOPortBase2 = 0x376;
+
+ /*
+ * Set the default critical section to NOP as we lock on controller level.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the PCI device.
+ */
+ rc = PDMDevHlpPCIRegisterEx(pDevIns, &pThis->dev, PDMPCIDEVREG_CFG_PRIMARY, PDMPCIDEVREG_F_NOT_MANDATORY_NO,
+ 1 /*uPciDevNo*/, 1 /*uPciDevFn*/, "piix3ide");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register PCI device"));
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 4, 0x10, PCI_ADDRESS_SPACE_IO, ataR3BMDMAIORangeMap);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register PCI I/O region for BMDMA"));
+
+ /*
+ * Register the I/O ports.
+ * The ports are all hardcoded and enforced by the PIIX3 host bridge controller.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->aCts[i].IOPortBase1, 1, (RTHCPTR)(uintptr_t)i,
+ ataIOPortWrite1Data, ataIOPortRead1Data,
+ ataIOPortWriteStr1Data, ataIOPortReadStr1Data, "ATA I/O Base 1 - Data");
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->aCts[i].IOPortBase1 + 1, 7, (RTHCPTR)(uintptr_t)i,
+ ataIOPortWrite1Other, ataIOPortRead1Other, NULL, NULL, "ATA I/O Base 1 - Other");
+
+ AssertLogRelRCReturn(rc, rc);
+ if (fRCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, pThis->aCts[i].IOPortBase1, 1, (RTGCPTR)i,
+ "ataIOPortWrite1Data", "ataIOPortRead1Data",
+ "ataIOPortWriteStr1Data", "ataIOPortReadStr1Data", "ATA I/O Base 1 - Data");
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, pThis->aCts[i].IOPortBase1 + 1, 7, (RTGCPTR)i,
+ "ataIOPortWrite1Other", "ataIOPortRead1Other", NULL, NULL, "ATA I/O Base 1 - Other");
+ AssertLogRelRCReturn(rc, rc);
+ }
+
+ if (fR0Enabled)
+ {
+#if 0
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase1, 1, (RTR0PTR)i,
+ "ataIOPortWrite1Data", "ataIOPortRead1Data", NULL, NULL, "ATA I/O Base 1 - Data");
+#else
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase1, 1, (RTR0PTR)i,
+ "ataIOPortWrite1Data", "ataIOPortRead1Data",
+ "ataIOPortWriteStr1Data", "ataIOPortReadStr1Data", "ATA I/O Base 1 - Data");
+#endif
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase1 + 1, 7, (RTR0PTR)i,
+ "ataIOPortWrite1Other", "ataIOPortRead1Other", NULL, NULL, "ATA I/O Base 1 - Other");
+ AssertLogRelRCReturn(rc, rc);
+ }
+
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTHCPTR)(uintptr_t)i,
+ ataIOPortWrite2, ataIOPortRead2, NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers"));
+
+ if (fRCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTGCPTR)i,
+ "ataIOPortWrite2", "ataIOPortRead2", NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers (GC)"));
+ }
+ if (fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->aCts[i].IOPortBase2, 1, (RTR0PTR)i,
+ "ataIOPortWrite2", "ataIOPortRead2", NULL, NULL, "ATA I/O Base 2");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register base2 I/O handlers (R0)"));
+ }
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADevState *pIf = &pThis->aCts[i].aIfs[j];
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATADMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATA DMA transfers.", "/Devices/IDE%d/ATA%d/Unit%d/DMA", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATA PIO transfers.", "/Devices/IDE%d/ATA%d/Unit%d/PIO", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIDMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATAPI DMA transfers.", "/Devices/IDE%d/ATA%d/Unit%d/AtapiDMA", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATAPI PIO transfers.", "/Devices/IDE%d/ATA%d/Unit%d/AtapiPIO", iInstance, i, j);
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatReads, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the read operations.", "/Devices/IDE%d/ATA%d/Unit%d/Reads", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+ "Amount of data read.", "/Devices/IDE%d/ATA%d/Unit%d/ReadBytes", iInstance, i, j);
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatInstrVDWrites,STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the VD DMA write operations.", "/Devices/IDE%d/ATA%d/Unit%d/InstrVDWrites", iInstance, i, j);
+#endif
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatWrites, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the write operations.", "/Devices/IDE%d/ATA%d/Unit%d/Writes", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+ "Amount of data written.", "/Devices/IDE%d/ATA%d/Unit%d/WrittenBytes", iInstance, i, j);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatFlushes, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the flush operations.", "/Devices/IDE%d/ATA%d/Unit%d/Flushes", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatStatusYields, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of status polling yields.", "/Devices/IDE%d/ATA%d/Unit%d/StatusYields", iInstance, i, j);
+ }
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncOps, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "The number of async operations.", "/Devices/IDE%d/ATA%d/Async/Operations", iInstance, i);
+ /** @todo STAMUNIT_MICROSECS */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMinWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Minimum wait in microseconds.", "/Devices/IDE%d/ATA%d/Async/MinWait", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMaxWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Maximum wait in microseconds.", "/Devices/IDE%d/ATA%d/Async/MaxWait", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTimeUS, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Total time spent in microseconds.", "/Devices/IDE%d/ATA%d/Async/TotalTimeUS", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTime, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of async operations.", "/Devices/IDE%d/ATA%d/Async/Time", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatLockWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of locks.", "/Devices/IDE%d/ATA%d/Async/LockWait", iInstance, i);
+#endif /* VBOX_WITH_STATISTICS */
+
+ /* Initialize per-controller critical section. */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aCts[i].lock, RT_SRC_POS, "ATA#%u-Ctl", i);
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Initialize per-controller async I/O request critical section. */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aCts[i].AsyncIORequestLock, RT_SRC_POS, "ATA#%u-Req", i);
+ AssertLogRelRCReturn(rc, rc);
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThis->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot attach to status driver"));
+ }
+
+ /*
+ * Attach the units.
+ */
+ uint32_t cbTotalBuffer = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+
+ /*
+ * Start the worker thread.
+ */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ pCtl->pSupDrvSession = PDMDevHlpGetSupDrvSession(pDevIns);
+ rc = SUPSemEventCreate(pCtl->pSupDrvSession, &pCtl->hAsyncIOSem);
+ AssertLogRelRCReturn(rc, rc);
+ rc = RTSemEventCreate(&pCtl->SuspendIOSem);
+ AssertLogRelRCReturn(rc, rc);
+
+ ataR3AsyncIOClearRequests(pCtl);
+ rc = RTThreadCreateF(&pCtl->AsyncIOThread, ataR3AsyncIOThread, (void *)pCtl, 128*1024 /*cbStack*/,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ATA-%u", i);
+ AssertLogRelRCReturn(rc, rc);
+ Assert( pCtl->AsyncIOThread != NIL_RTTHREAD && pCtl->hAsyncIOSem != NIL_SUPSEMEVENT
+ && pCtl->SuspendIOSem != NIL_RTSEMEVENT && PDMCritSectIsInitialized(&pCtl->AsyncIORequestLock));
+ Log(("%s: controller %d AIO thread id %#x; sem %p susp_sem %p\n", __FUNCTION__, i, pCtl->AsyncIOThread, pCtl->hAsyncIOSem, pCtl->SuspendIOSem));
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+ {
+ static const char *s_apszDescs[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "Primary Master", "Primary Slave" },
+ { "Secondary Master", "Secondary Slave" }
+ };
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ ATADevState *pIf = &pCtl->aIfs[j];
+
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, s_apszDescs[i][j]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pDevIns, pIf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Init vendor product data.
+ */
+ static const char *s_apszCFGMKeys[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "PrimaryMaster", "PrimarySlave" },
+ { "SecondaryMaster", "SecondarySlave" }
+ };
+
+ /* Generate a default serial number. */
+ char szSerial[ATA_SERIAL_NUMBER_LENGTH+1];
+ RTUUID Uuid;
+ if (pIf->pDrvMedia)
+ rc = pIf->pDrvMedia->pfnGetUuid(pIf->pDrvMedia, &Uuid);
+ else
+ RTUuidClear(&Uuid);
+
+ if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+ {
+ /* Generate a predictable serial for drives which don't have a UUID. */
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%x-%04x%04x",
+ pIf->iLUN + pDevIns->iInstance * 32,
+ pThis->aCts[i].IOPortBase1, pThis->aCts[i].IOPortBase2);
+ }
+ else
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+ /* Get user config if present using defaults otherwise. */
+ PCFGMNODE pCfgNode = CFGMR3GetChild(pCfg, s_apszCFGMKeys[i][j]);
+ rc = CFGMR3QueryStringDef(pCfgNode, "SerialNumber", pIf->szSerialNumber, sizeof(pIf->szSerialNumber),
+ szSerial);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"SerialNumber\" is longer than 20 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"SerialNumber\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "FirmwareRevision", pIf->szFirmwareRevision, sizeof(pIf->szFirmwareRevision),
+ "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"FirmwareRevision\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ModelNumber", pIf->szModelNumber, sizeof(pIf->szModelNumber),
+ pIf->fATAPI ? "VBOX CD-ROM" : "VBOX HARDDISK");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ModelNumber\" is longer than 40 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ModelNumber\" as string"));
+ }
+
+ /* There are three other identification strings for CD drives used for INQUIRY */
+ if (pIf->fATAPI)
+ {
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIVendorId", pIf->szInquiryVendorId, sizeof(pIf->szInquiryVendorId),
+ "VBOX");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIVendorId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIVendorId\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIProductId", pIf->szInquiryProductId, sizeof(pIf->szInquiryProductId),
+ "CD-ROM");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIProductId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIProductId\" as string"));
+ }
+
+ rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIRevision", pIf->szInquiryRevision, sizeof(pIf->szInquiryRevision),
+ "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIRevision\" is longer than 4 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIRevision\" as string"));
+ }
+
+ rc = CFGMR3QueryBoolDef(pCfgNode, "OverwriteInquiry", &pIf->fOverwriteInquiry, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"OverwriteInquiry\" as boolean"));
+ }
+ }
+
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pIf->pDrvBase = NULL;
+ pIf->pDrvMedia = NULL;
+ pIf->cbIOBuffer = 0;
+ pIf->pbIOBufferR3 = NULL;
+ pIf->pbIOBufferR0 = NIL_RTR0PTR;
+ pIf->pbIOBufferRC = NIL_RTGCPTR;
+ LogRel(("PIIX3 ATA: LUN#%d: no unit\n", pIf->iLUN));
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* Error already cached by DrvHostBase */
+ return rc;
+ default:
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("PIIX3 cannot attach drive to the %s"),
+ s_apszDescs[i][j]);
+ }
+ }
+ cbTotalBuffer += pIf->cbIOBuffer;
+ }
+ }
+
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, ATA_SAVED_STATE_VERSION, sizeof(*pThis) + cbTotalBuffer, NULL,
+ NULL, ataR3LiveExec, NULL,
+ ataR3SaveLoadPrep, ataR3SaveExec, NULL,
+ ataR3SaveLoadPrep, ataR3LoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register save state handlers"));
+
+ /*
+ * Initialize the device state.
+ */
+ return ataR3ResetCommon(pDevIns, true /*fConstruct*/);
+}
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePIIX3IDE =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "piix3ide",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "Intel PIIX3 ATA controller.\n"
+ " LUN #0 is primary master.\n"
+ " LUN #1 is primary slave.\n"
+ " LUN #2 is secondary master.\n"
+ " LUN #3 is secondary slave.\n"
+ " LUN #999 is the LED/Status connector.",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION |
+ PDM_DEVREG_FLAGS_FIRST_RESET_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(PCIATAState),
+ /* pfnConstruct */
+ ataR3Construct,
+ /* pfnDestruct */
+ ataR3Destruct,
+ /* pfnRelocate */
+ ataR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ataR3Reset,
+ /* pfnSuspend */
+ ataR3Suspend,
+ /* pfnResume */
+ ataR3Resume,
+ /* pfnAttach */
+ ataR3Attach,
+ /* pfnDetach */
+ ataR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ ataR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Storage/DevBusLogic.cpp b/src/VBox/Devices/Storage/DevBusLogic.cpp
new file mode 100644
index 00000000..5e84407b
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevBusLogic.cpp
@@ -0,0 +1,4545 @@
+/* $Id: DevBusLogic.cpp $ */
+/** @file
+ * VBox storage devices - BusLogic SCSI host adapter BT-958.
+ *
+ * Based on the Multi-Master Ultra SCSI Systems Technical Reference Manual.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmcritsect.h>
+#include <VBox/scsi.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/log.h>
+#ifdef IN_RING3
+# include <iprt/alloc.h>
+# include <iprt/memcache.h>
+# include <iprt/param.h>
+# include <iprt/uuid.h>
+#endif
+
+#include "VBoxSCSI.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of attached devices the adapter can handle. */
+#define BUSLOGIC_MAX_DEVICES 16
+
+/** Maximum number of scatter gather elements this device can handle. */
+#define BUSLOGIC_MAX_SCATTER_GATHER_LIST_SIZE 128
+
+/** Size of the command buffer. */
+#define BUSLOGIC_COMMAND_SIZE_MAX 53
+
+/** Size of the reply buffer. */
+#define BUSLOGIC_REPLY_SIZE_MAX 64
+
+/** Custom fixed I/O ports for BIOS controller access.
+ * Note that these should not be in the ISA range (below 400h) to avoid
+ * conflicts with ISA device probing. Addresses in the 300h-340h range should be
+ * especially avoided.
+ */
+#define BUSLOGIC_BIOS_IO_PORT 0x430
+
+/** State saved version. */
+#define BUSLOGIC_SAVED_STATE_MINOR_VERSION 4
+
+/** Saved state version before the suspend on error feature was implemented. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_ERROR_HANDLING 1
+/** Saved state version before 24-bit mailbox support was implemented. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_24BIT_MBOX 2
+/** Saved state version before command buffer size was raised. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_CMDBUF_RESIZE 3
+
+/** Command buffer size in old saved states. */
+#define BUSLOGIC_COMMAND_SIZE_OLD 5
+
+/** The duration of software-initiated reset (in nano seconds).
+ * Not documented, set to 50 ms. */
+#define BUSLOGIC_RESET_DURATION_NS UINT64_C(50000000)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * State of a device attached to the buslogic host adapter.
+ *
+ * @implements PDMIBASE
+ * @implements PDMISCSIPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct BUSLOGICDEVICE
+{
+ /** Pointer to the owning buslogic device instance. - R3 pointer */
+ R3PTRTYPE(struct BUSLOGIC *) pBusLogicR3;
+ /** Pointer to the owning buslogic device instance. - R0 pointer */
+ R0PTRTYPE(struct BUSLOGIC *) pBusLogicR0;
+ /** Pointer to the owning buslogic device instance. - RC pointer */
+ RCPTRTYPE(struct BUSLOGIC *) pBusLogicRC;
+
+ /** Flag whether device is present. */
+ bool fPresent;
+ /** LUN of the device. */
+ RTUINT iLUN;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Our base interface. */
+ PDMIBASE IBase;
+ /** Media port interface. */
+ PDMIMEDIAPORT IMediaPort;
+ /** Extended media port interface. */
+ PDMIMEDIAEXPORT IMediaExPort;
+ /** Led interface. */
+ PDMILEDPORTS ILed;
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's media interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's extended media interface. */
+ R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx;
+ /** The status LED state for this device. */
+ PDMLED Led;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment1;
+#endif
+
+ /** Number of outstanding tasks on the port. */
+ volatile uint32_t cOutstandingRequests;
+
+} BUSLOGICDEVICE, *PBUSLOGICDEVICE;
+
+/**
+ * Commands the BusLogic adapter supports.
+ */
+enum BUSLOGICCOMMAND
+{
+ BUSLOGICCOMMAND_TEST_CMDC_INTERRUPT = 0x00,
+ BUSLOGICCOMMAND_INITIALIZE_MAILBOX = 0x01,
+ BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND = 0x02,
+ BUSLOGICCOMMAND_EXECUTE_BIOS_COMMAND = 0x03,
+ BUSLOGICCOMMAND_INQUIRE_BOARD_ID = 0x04,
+ BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT = 0x05,
+ BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT = 0x06,
+ BUSLOGICCOMMAND_SET_PREEMPT_TIME_ON_BUS = 0x07,
+ BUSLOGICCOMMAND_SET_TIME_OFF_BUS = 0x08,
+ BUSLOGICCOMMAND_SET_BUS_TRANSFER_RATE = 0x09,
+ BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_0_TO_7 = 0x0a,
+ BUSLOGICCOMMAND_INQUIRE_CONFIGURATION = 0x0b,
+ BUSLOGICCOMMAND_ENABLE_TARGET_MODE = 0x0c,
+ BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION = 0x0d,
+ BUSLOGICCOMMAND_WRITE_ADAPTER_LOCAL_RAM = 0x1a,
+ BUSLOGICCOMMAND_READ_ADAPTER_LOCAL_RAM = 0x1b,
+ BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO = 0x1c,
+ BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO = 0x1d,
+ BUSLOGICCOMMAND_ECHO_COMMAND_DATA = 0x1f,
+ BUSLOGICCOMMAND_HOST_ADAPTER_DIAGNOSTIC = 0x20,
+ BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS = 0x21,
+ BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_8_TO_15 = 0x23,
+ BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES = 0x24,
+ BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT = 0x25,
+ BUSLOGICCOMMAND_EXT_BIOS_INFO = 0x28,
+ BUSLOGICCOMMAND_UNLOCK_MAILBOX = 0x29,
+ BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX = 0x81,
+ BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND = 0x83,
+ BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER = 0x84,
+ BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER = 0x85,
+ BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION = 0x86,
+ BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER = 0x8b,
+ BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD = 0x8c,
+ BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION = 0x8d,
+ BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE = 0x8f,
+ BUSLOGICCOMMAND_STORE_HOST_ADAPTER_LOCAL_RAM = 0x90,
+ BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM = 0x91,
+ BUSLOGICCOMMAND_STORE_LOCAL_DATA_IN_EEPROM = 0x92,
+ BUSLOGICCOMMAND_UPLOAD_AUTO_SCSI_CODE = 0x94,
+ BUSLOGICCOMMAND_MODIFY_IO_ADDRESS = 0x95,
+ BUSLOGICCOMMAND_SET_CCB_FORMAT = 0x96,
+ BUSLOGICCOMMAND_WRITE_INQUIRY_BUFFER = 0x9a,
+ BUSLOGICCOMMAND_READ_INQUIRY_BUFFER = 0x9b,
+ BUSLOGICCOMMAND_FLASH_ROM_UPLOAD_DOWNLOAD = 0xa7,
+ BUSLOGICCOMMAND_READ_SCAM_DATA = 0xa8,
+ BUSLOGICCOMMAND_WRITE_SCAM_DATA = 0xa9
+} BUSLOGICCOMMAND;
+
+#pragma pack(1)
+/**
+ * Auto SCSI structure which is located
+ * in host adapter RAM and contains several
+ * configuration parameters.
+ */
+typedef struct AutoSCSIRam
+{
+ uint8_t aInternalSignature[2];
+ uint8_t cbInformation;
+ uint8_t aHostAdaptertype[6];
+ uint8_t uReserved1;
+ bool fFloppyEnabled : 1;
+ bool fFloppySecondary : 1;
+ bool fLevelSensitiveInterrupt : 1;
+ unsigned char uReserved2 : 2;
+ unsigned char uSystemRAMAreForBIOS : 3;
+ unsigned char uDMAChannel : 7;
+ bool fDMAAutoConfiguration : 1;
+ unsigned char uIrqChannel : 7;
+ bool fIrqAutoConfiguration : 1;
+ uint8_t uDMATransferRate;
+ uint8_t uSCSIId;
+ bool fLowByteTerminated : 1;
+ bool fParityCheckingEnabled : 1;
+ bool fHighByteTerminated : 1;
+ bool fNoisyCablingEnvironment : 1;
+ bool fFastSynchronousNeogtiation : 1;
+ bool fBusResetEnabled : 1;
+ bool fReserved3 : 1;
+ bool fActiveNegotiationEnabled : 1;
+ uint8_t uBusOnDelay;
+ uint8_t uBusOffDelay;
+ bool fHostAdapterBIOSEnabled : 1;
+ bool fBIOSRedirectionOfInt19 : 1;
+ bool fExtendedTranslation : 1;
+ bool fMapRemovableAsFixed : 1;
+ bool fReserved4 : 1;
+ bool fBIOSSupportsMoreThan2Drives : 1;
+ bool fBIOSInterruptMode : 1;
+ bool fFlopticalSupport : 1;
+ uint16_t u16DeviceEnabledMask;
+ uint16_t u16WidePermittedMask;
+ uint16_t u16FastPermittedMask;
+ uint16_t u16SynchronousPermittedMask;
+ uint16_t u16DisconnectPermittedMask;
+ uint16_t u16SendStartUnitCommandMask;
+ uint16_t u16IgnoreInBIOSScanMask;
+ unsigned char uPCIInterruptPin : 2;
+ unsigned char uHostAdapterIoPortAddress : 2;
+ bool fStrictRoundRobinMode : 1;
+ bool fVesaBusSpeedGreaterThan33MHz : 1;
+ bool fVesaBurstWrite : 1;
+ bool fVesaBurstRead : 1;
+ uint16_t u16UltraPermittedMask;
+ uint32_t uReserved5;
+ uint8_t uReserved6;
+ uint8_t uAutoSCSIMaximumLUN;
+ bool fReserved7 : 1;
+ bool fSCAMDominant : 1;
+ bool fSCAMenabled : 1;
+ bool fSCAMLevel2 : 1;
+ unsigned char uReserved8 : 4;
+ bool fInt13Extension : 1;
+ bool fReserved9 : 1;
+ bool fCDROMBoot : 1;
+ unsigned char uReserved10 : 5;
+ unsigned char uBootTargetId : 4;
+ unsigned char uBootChannel : 4;
+ bool fForceBusDeviceScanningOrder : 1;
+ unsigned char uReserved11 : 7;
+ uint16_t u16NonTaggedToAlternateLunPermittedMask;
+ uint16_t u16RenegotiateSyncAfterCheckConditionMask;
+ uint8_t aReserved12[10];
+ uint8_t aManufacturingDiagnostic[2];
+ uint16_t u16Checksum;
+} AutoSCSIRam, *PAutoSCSIRam;
+AssertCompileSize(AutoSCSIRam, 64);
+#pragma pack()
+
+/**
+ * The local Ram.
+ */
+typedef union HostAdapterLocalRam
+{
+ /** Byte view. */
+ uint8_t u8View[256];
+ /** Structured view. */
+ struct
+ {
+ /** Offset 0 - 63 is for BIOS. */
+ uint8_t u8Bios[64];
+ /** Auto SCSI structure. */
+ AutoSCSIRam autoSCSIData;
+ } structured;
+} HostAdapterLocalRam, *PHostAdapterLocalRam;
+AssertCompileSize(HostAdapterLocalRam, 256);
+
+
+/** Ugly 24-bit big-endian addressing. */
+typedef struct
+{
+ uint8_t hi;
+ uint8_t mid;
+ uint8_t lo;
+} Addr24, Len24;
+AssertCompileSize(Addr24, 3);
+
+#define ADDR_TO_U32(x) (((x).hi << 16) | ((x).mid << 8) | (x).lo)
+#define LEN_TO_U32 ADDR_TO_U32
+#define U32_TO_ADDR(a, x) do {(a).hi = (x) >> 16; (a).mid = (x) >> 8; (a).lo = (x);} while(0)
+#define U32_TO_LEN U32_TO_ADDR
+
+/** @name Compatible ISA base I/O port addresses. Disabled if zero.
+ * @{ */
+#define NUM_ISA_BASES 8
+#define MAX_ISA_BASE (NUM_ISA_BASES - 1)
+#define ISA_BASE_DISABLED 6
+
+#ifdef IN_RING3
+static uint16_t const g_aISABases[NUM_ISA_BASES] =
+{
+ 0x330, 0x334, 0x230, 0x234, 0x130, 0x134, 0, 0
+};
+#endif
+/** @} */
+
+/**
+ * Emulated device types.
+ */
+enum BL_DEVICE_TYPE
+{
+ DEV_BT_958D = 0, /* BusLogic BT-958D, PCI. */
+ DEV_BT_545C = 1, /* BusLogic BT-545C, ISA. */
+ DEV_AHA_1540B = 2 /* Adaptec AHA-1540B, ISA. */
+};
+
+/** Pointer to a task state structure. */
+typedef struct BUSLOGICREQ *PBUSLOGICREQ;
+
+/**
+ * Main BusLogic device state.
+ *
+ * @extends PDMPCIDEV
+ * @implements PDMILEDPORTS
+ */
+typedef struct BUSLOGIC
+{
+ /** The PCI device structure. */
+ PDMPCIDEV dev;
+ /** Pointer to the device instance - HC ptr */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 ptr */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+
+ /** Whether R0 is enabled. */
+ bool fR0Enabled;
+ /** Whether RC is enabled. */
+ bool fGCEnabled;
+ /** Base address of the I/O ports. */
+ RTIOPORT IOPortBase;
+
+ /** Base address of the memory mapping. */
+ RTGCPHYS MMIOBase;
+
+ /** Status register - Readonly. */
+ volatile uint8_t regStatus;
+ /** Interrupt register - Readonly. */
+ volatile uint8_t regInterrupt;
+ /** Geometry register - Readonly. */
+ volatile uint8_t regGeometry;
+ /** Pending (delayed) interrupt. */
+ uint8_t uPendingIntr;
+
+ /** Local RAM for the fetch hostadapter local RAM request.
+ * I don't know how big the buffer really is but the maximum
+ * seems to be 256 bytes because the offset and count field in the command request
+ * are only one byte big.
+ */
+ HostAdapterLocalRam LocalRam;
+
+ /** Command code the guest issued. */
+ uint8_t uOperationCode;
+ /** Buffer for the command parameters the adapter is currently receiving from the guest.
+ * Size of the largest command which is possible.
+ */
+ uint8_t aCommandBuffer[BUSLOGIC_COMMAND_SIZE_MAX]; /* Size of the biggest request. */
+ /** Current position in the command buffer. */
+ uint8_t iParameter;
+ /** Parameters left until the command is complete. */
+ uint8_t cbCommandParametersLeft;
+
+ /** Whether we are using the RAM or reply buffer. */
+ bool fUseLocalRam;
+ /** Buffer to store reply data from the controller to the guest. */
+ uint8_t aReplyBuffer[BUSLOGIC_REPLY_SIZE_MAX]; /* Size of the biggest reply. */
+ /** Position in the buffer we are reading next. */
+ uint8_t iReply;
+ /** Bytes left until the reply buffer is empty. */
+ uint8_t cbReplyParametersLeft;
+
+ /** Flag whether IRQs are enabled. */
+ bool fIRQEnabled;
+ /** Flag whether 24-bit mailboxes are in use (default is 32-bit). */
+ bool fMbxIs24Bit;
+ /** ISA I/O port base (encoded in FW-compatible format). */
+ uint8_t uISABaseCode;
+ /** ISA IRQ, non-zero if in ISA mode. */
+ uint8_t uIsaIrq;
+
+ /** ISA I/O port base (disabled if zero). */
+ RTIOPORT IOISABase;
+ /** Default ISA I/O port base in FW-compatible format. */
+ uint8_t uDefaultISABaseCode;
+ /** Emulated device type. */
+ uint8_t uDevType;
+
+ /** Signature index for Adaptec models. */
+ uint8_t uAhaSigIdx;
+ uint8_t Alignment[7];
+
+ /** Number of mailboxes the guest set up. */
+ uint32_t cMailbox;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Time when HBA reset was last initiated. */ /**< @todo does this need to be saved? */
+ uint64_t u64ResetTime;
+ /** Physical base address of the outgoing mailboxes. */
+ RTGCPHYS GCPhysAddrMailboxOutgoingBase;
+ /** Current outgoing mailbox position. */
+ uint32_t uMailboxOutgoingPositionCurrent;
+ /** Number of mailboxes ready. */
+ volatile uint32_t cMailboxesReady;
+ /** Whether a notification to R3 was sent. */
+ volatile bool fNotificationSent;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment1;
+#endif
+
+ /** Physical base address of the incoming mailboxes. */
+ RTGCPHYS GCPhysAddrMailboxIncomingBase;
+ /** Current incoming mailbox position. */
+ uint32_t uMailboxIncomingPositionCurrent;
+
+ /** Whether strict round robin is enabled. */
+ bool fStrictRoundRobinMode;
+ /** Whether the extended LUN CCB format is enabled for 32 possible logical units. */
+ bool fExtendedLunCCBFormat;
+
+ /** Queue to send tasks to R3. - HC ptr */
+ R3PTRTYPE(PPDMQUEUE) pNotifierQueueR3;
+ /** Queue to send tasks to R3. - HC ptr */
+ R0PTRTYPE(PPDMQUEUE) pNotifierQueueR0;
+ /** Queue to send tasks to R3. - RC ptr */
+ RCPTRTYPE(PPDMQUEUE) pNotifierQueueRC;
+
+ uint32_t Alignment2;
+
+ /** Critical section protecting access to the interrupt status register. */
+ PDMCRITSECT CritSectIntr;
+
+ /** Device state for BIOS access. */
+ VBOXSCSI VBoxSCSI;
+
+ /** BusLogic device states. */
+ BUSLOGICDEVICE aDeviceStates[BUSLOGIC_MAX_DEVICES];
+
+ /** The base interface.
+ * @todo use PDMDEVINS::IBase */
+ PDMIBASE IBase;
+ /** Status Port - Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Status LUN: Media Notifys. */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment3;
+#endif
+
+ /** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when
+ * a port is entering the idle state. */
+ bool volatile fSignalIdle;
+ /** Flag whether the worker thread is sleeping. */
+ volatile bool fWrkThreadSleeping;
+ /** Flag whether a request from the BIOS is pending which the
+ * worker thread needs to process. */
+ volatile bool fBiosReqPending;
+
+ /** The support driver session handle. */
+ R3R0PTRTYPE(PSUPDRVSESSION) pSupDrvSession;
+ /** Worker thread. */
+ R3PTRTYPE(PPDMTHREAD) pThreadWrk;
+ /** The event semaphore the processing thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+ /** Pointer to the array of addresses to redo. */
+ R3PTRTYPE(PRTGCPHYS) paGCPhysAddrCCBRedo;
+ /** Number of addresses the redo array holds. */
+ uint32_t cReqsRedo;
+
+#ifdef LOG_ENABLED
+ volatile uint32_t cInMailboxesReady;
+#else
+# if HC_ARCH_BITS == 64
+ uint32_t Alignment4;
+# endif
+#endif
+
+} BUSLOGIC, *PBUSLOGIC;
+
+/** Register offsets in the I/O port space. */
+#define BUSLOGIC_REGISTER_CONTROL 0 /**< Writeonly */
+/** Fields for the control register. */
+# define BL_CTRL_RSBUS RT_BIT(4) /* Reset SCSI Bus. */
+# define BL_CTRL_RINT RT_BIT(5) /* Reset Interrupt. */
+# define BL_CTRL_RSOFT RT_BIT(6) /* Soft Reset. */
+# define BL_CTRL_RHARD RT_BIT(7) /* Hard Reset. */
+
+#define BUSLOGIC_REGISTER_STATUS 0 /**< Readonly */
+/** Fields for the status register. */
+# define BL_STAT_CMDINV RT_BIT(0) /* Command Invalid. */
+# define BL_STAT_DIRRDY RT_BIT(2) /* Data In Register Ready. */
+# define BL_STAT_CPRBSY RT_BIT(3) /* Command/Parameter Out Register Busy. */
+# define BL_STAT_HARDY RT_BIT(4) /* Host Adapter Ready. */
+# define BL_STAT_INREQ RT_BIT(5) /* Initialization Required. */
+# define BL_STAT_DFAIL RT_BIT(6) /* Diagnostic Failure. */
+# define BL_STAT_DACT RT_BIT(7) /* Diagnistic Active. */
+
+#define BUSLOGIC_REGISTER_COMMAND 1 /**< Writeonly */
+#define BUSLOGIC_REGISTER_DATAIN 1 /**< Readonly */
+#define BUSLOGIC_REGISTER_INTERRUPT 2 /**< Readonly */
+/** Fields for the interrupt register. */
+# define BL_INTR_IMBL RT_BIT(0) /* Incoming Mailbox Loaded. */
+# define BL_INTR_OMBR RT_BIT(1) /* Outgoing Mailbox Available. */
+# define BL_INTR_CMDC RT_BIT(2) /* Command Complete. */
+# define BL_INTR_RSTS RT_BIT(3) /* SCSI Bus Reset State. */
+# define BL_INTR_INTV RT_BIT(7) /* Interrupt Valid. */
+
+#define BUSLOGIC_REGISTER_GEOMETRY 3 /* Readonly */
+# define BL_GEOM_XLATEN RT_BIT(7) /* Extended geometry translation enabled. */
+
+/** Structure for the INQUIRE_PCI_HOST_ADAPTER_INFORMATION reply. */
+typedef struct ReplyInquirePCIHostAdapterInformation
+{
+ uint8_t IsaIOPort;
+ uint8_t IRQ;
+ unsigned char LowByteTerminated : 1;
+ unsigned char HighByteTerminated : 1;
+ unsigned char uReserved : 2; /* Reserved. */
+ unsigned char JP1 : 1; /* Whatever that means. */
+ unsigned char JP2 : 1; /* Whatever that means. */
+ unsigned char JP3 : 1; /* Whatever that means. */
+ /** Whether the provided info is valid. */
+ unsigned char InformationIsValid: 1;
+ uint8_t uReserved2; /* Reserved. */
+} ReplyInquirePCIHostAdapterInformation, *PReplyInquirePCIHostAdapterInformation;
+AssertCompileSize(ReplyInquirePCIHostAdapterInformation, 4);
+
+/** Structure for the INQUIRE_CONFIGURATION reply. */
+typedef struct ReplyInquireConfiguration
+{
+ unsigned char uReserved1 : 5;
+ bool fDmaChannel5 : 1;
+ bool fDmaChannel6 : 1;
+ bool fDmaChannel7 : 1;
+ bool fIrqChannel9 : 1;
+ bool fIrqChannel10 : 1;
+ bool fIrqChannel11 : 1;
+ bool fIrqChannel12 : 1;
+ unsigned char uReserved2 : 1;
+ bool fIrqChannel14 : 1;
+ bool fIrqChannel15 : 1;
+ unsigned char uReserved3 : 1;
+ unsigned char uHostAdapterId : 4;
+ unsigned char uReserved4 : 4;
+} ReplyInquireConfiguration, *PReplyInquireConfiguration;
+AssertCompileSize(ReplyInquireConfiguration, 3);
+
+/** Structure for the INQUIRE_SETUP_INFORMATION reply. */
+typedef struct ReplyInquireSetupInformationSynchronousValue
+{
+ unsigned char uOffset : 4;
+ unsigned char uTransferPeriod : 3;
+ bool fSynchronous : 1;
+}ReplyInquireSetupInformationSynchronousValue, *PReplyInquireSetupInformationSynchronousValue;
+AssertCompileSize(ReplyInquireSetupInformationSynchronousValue, 1);
+
+typedef struct ReplyInquireSetupInformation
+{
+ bool fSynchronousInitiationEnabled : 1;
+ bool fParityCheckingEnabled : 1;
+ unsigned char uReserved1 : 6;
+ uint8_t uBusTransferRate;
+ uint8_t uPreemptTimeOnBus;
+ uint8_t uTimeOffBus;
+ uint8_t cMailbox;
+ Addr24 MailboxAddress;
+ ReplyInquireSetupInformationSynchronousValue SynchronousValuesId0To7[8];
+ uint8_t uDisconnectPermittedId0To7;
+ uint8_t uSignature;
+ uint8_t uCharacterD;
+ uint8_t uHostBusType;
+ uint8_t uWideTransferPermittedId0To7;
+ uint8_t uWideTransfersActiveId0To7;
+ ReplyInquireSetupInformationSynchronousValue SynchronousValuesId8To15[8];
+ uint8_t uDisconnectPermittedId8To15;
+ uint8_t uReserved2;
+ uint8_t uWideTransferPermittedId8To15;
+ uint8_t uWideTransfersActiveId8To15;
+} ReplyInquireSetupInformation, *PReplyInquireSetupInformation;
+AssertCompileSize(ReplyInquireSetupInformation, 34);
+
+/** Structure for the INQUIRE_EXTENDED_SETUP_INFORMATION. */
+#pragma pack(1)
+typedef struct ReplyInquireExtendedSetupInformation
+{
+ uint8_t uBusType;
+ uint8_t uBiosAddress;
+ uint16_t u16ScatterGatherLimit;
+ uint8_t cMailbox;
+ uint32_t uMailboxAddressBase;
+ unsigned char uReserved1 : 2;
+ bool fFastEISA : 1;
+ unsigned char uReserved2 : 3;
+ bool fLevelSensitiveInterrupt : 1;
+ unsigned char uReserved3 : 1;
+ unsigned char aFirmwareRevision[3];
+ bool fHostWideSCSI : 1;
+ bool fHostDifferentialSCSI : 1;
+ bool fHostSupportsSCAM : 1;
+ bool fHostUltraSCSI : 1;
+ bool fHostSmartTermination : 1;
+ unsigned char uReserved4 : 3;
+} ReplyInquireExtendedSetupInformation, *PReplyInquireExtendedSetupInformation;
+AssertCompileSize(ReplyInquireExtendedSetupInformation, 14);
+#pragma pack()
+
+/** Structure for the INITIALIZE EXTENDED MAILBOX request. */
+#pragma pack(1)
+typedef struct RequestInitializeExtendedMailbox
+{
+ /** Number of mailboxes in guest memory. */
+ uint8_t cMailbox;
+ /** Physical address of the first mailbox. */
+ uint32_t uMailboxBaseAddress;
+} RequestInitializeExtendedMailbox, *PRequestInitializeExtendedMailbox;
+AssertCompileSize(RequestInitializeExtendedMailbox, 5);
+#pragma pack()
+
+/** Structure for the INITIALIZE MAILBOX request. */
+typedef struct
+{
+ /** Number of mailboxes to set up. */
+ uint8_t cMailbox;
+ /** Physical address of the first mailbox. */
+ Addr24 aMailboxBaseAddr;
+} RequestInitMbx, *PRequestInitMbx;
+AssertCompileSize(RequestInitMbx, 4);
+
+/**
+ * Structure of a mailbox in guest memory.
+ * The incoming and outgoing mailbox have the same size
+ * but the incoming one has some more fields defined which
+ * are marked as reserved in the outgoing one.
+ * The last field is also different from the type.
+ * For outgoing mailboxes it is the action and
+ * for incoming ones the completion status code for the task.
+ * We use one structure for both types.
+ */
+typedef struct Mailbox32
+{
+ /** Physical address of the CCB structure in the guest memory. */
+ uint32_t u32PhysAddrCCB;
+ /** Type specific data. */
+ union
+ {
+ /** For outgoing mailboxes. */
+ struct
+ {
+ /** Reserved */
+ uint8_t uReserved[3];
+ /** Action code. */
+ uint8_t uActionCode;
+ } out;
+ /** For incoming mailboxes. */
+ struct
+ {
+ /** The host adapter status after finishing the request. */
+ uint8_t uHostAdapterStatus;
+ /** The status of the device which executed the request after executing it. */
+ uint8_t uTargetDeviceStatus;
+ /** Reserved. */
+ uint8_t uReserved;
+ /** The completion status code of the request. */
+ uint8_t uCompletionCode;
+ } in;
+ } u;
+} Mailbox32, *PMailbox32;
+AssertCompileSize(Mailbox32, 8);
+
+/** Old style 24-bit mailbox entry. */
+typedef struct Mailbox24
+{
+ /** Mailbox command (incoming) or state (outgoing). */
+ uint8_t uCmdState;
+ /** Physical address of the CCB structure in the guest memory. */
+ Addr24 aPhysAddrCCB;
+} Mailbox24, *PMailbox24;
+AssertCompileSize(Mailbox24, 4);
+
+/**
+ * Action codes for outgoing mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_OUTGOING_ACTION
+{
+ BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE = 0x00,
+ BUSLOGIC_MAILBOX_OUTGOING_ACTION_START_COMMAND = 0x01,
+ BUSLOGIC_MAILBOX_OUTGOING_ACTION_ABORT_COMMAND = 0x02
+};
+
+/**
+ * Completion codes for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_COMPLETION
+{
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_FREE = 0x00,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITHOUT_ERROR = 0x01,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED = 0x02,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED_NOT_FOUND = 0x03,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR = 0x04,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_INVALID_CCB = 0x05
+};
+
+/**
+ * Host adapter status for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS
+{
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_CMD_COMPLETED = 0x00,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CMD_COMPLETED = 0x0a,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CMD_COMPLETED_WITH_FLAG = 0x0b,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_DATA_UNDERUN = 0x0c,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_SELECTION_TIMEOUT = 0x11,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_DATA_OVERRUN = 0x12,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_UNEXPECTED_BUS_FREE = 0x13,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_BUS_PHASE_REQUESTED = 0x14,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_OUTGOING_MAILBOX_ACTION_CODE = 0x15,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_OPERATION_CODE = 0x16,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_LINKED_CCB_HAS_INVALID_LUN = 0x17,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_PARAMETER = 0x1a,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_AUTO_REQUEST_SENSE_FAILED = 0x1b,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TAGGED_QUEUING_MESSAGE_REJECTED = 0x1c,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_UNSUPPORTED_MESSAGE_RECEIVED = 0x1d,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_HARDWARE_FAILED = 0x20,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TARGET_FAILED_RESPONSE_TO_ATN = 0x21,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_ASSERTED_RST = 0x22,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_OTHER_DEVICE_ASSERTED_RST = 0x23,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_TARGET_DEVICE_RECONNECTED_IMPROPERLY = 0x24,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_ASSERTED_BUS_DEVICE_RESET = 0x25,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_ABORT_QUEUE_GENERATED = 0x26,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_SOFTWARE_ERROR = 0x27,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_HOST_ADAPTER_HARDWARE_TIMEOUT_ERROR = 0x30,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_PARITY_ERROR_DETECTED = 0x34
+};
+
+/**
+ * Device status codes for incoming mailboxes.
+ */
+enum BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS
+{
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD = 0x00,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_CHECK_CONDITION = 0x02,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_DEVICE_BUSY = 0x08
+};
+
+/**
+ * Opcode types for CCB.
+ */
+enum BUSLOGIC_CCB_OPCODE
+{
+ BUSLOGIC_CCB_OPCODE_INITIATOR_CCB = 0x00,
+ BUSLOGIC_CCB_OPCODE_TARGET_CCB = 0x01,
+ BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_SCATTER_GATHER = 0x02,
+ BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_DATA_LENGTH = 0x03,
+ BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER = 0x04,
+ BUSLOGIC_CCB_OPCODE_BUS_DEVICE_RESET = 0x81
+};
+
+/**
+ * Data transfer direction.
+ */
+enum BUSLOGIC_CCB_DIRECTION
+{
+ BUSLOGIC_CCB_DIRECTION_UNKNOWN = 0x00,
+ BUSLOGIC_CCB_DIRECTION_IN = 0x01,
+ BUSLOGIC_CCB_DIRECTION_OUT = 0x02,
+ BUSLOGIC_CCB_DIRECTION_NO_DATA = 0x03
+};
+
+/**
+ * The command control block for a SCSI request.
+ */
+typedef struct CCB32
+{
+ /** Opcode. */
+ uint8_t uOpcode;
+ /** Reserved */
+ unsigned char uReserved1 : 3;
+ /** Data direction for the request. */
+ unsigned char uDataDirection : 2;
+ /** Whether the request is tag queued. */
+ bool fTagQueued : 1;
+ /** Queue tag mode. */
+ unsigned char uQueueTag : 2;
+ /** Length of the SCSI CDB. */
+ uint8_t cbCDB;
+ /** Sense data length. */
+ uint8_t cbSenseData;
+ /** Data length. */
+ uint32_t cbData;
+ /** Data pointer.
+ * This points to the data region or a scatter gather list based on the opcode.
+ */
+ uint32_t u32PhysAddrData;
+ /** Reserved. */
+ uint8_t uReserved2[2];
+ /** Host adapter status. */
+ uint8_t uHostAdapterStatus;
+ /** Device adapter status. */
+ uint8_t uDeviceStatus;
+ /** The device the request is sent to. */
+ uint8_t uTargetId;
+ /**The LUN in the device. */
+ unsigned char uLogicalUnit : 5;
+ /** Legacy tag. */
+ bool fLegacyTagEnable : 1;
+ /** Legacy queue tag. */
+ unsigned char uLegacyQueueTag : 2;
+ /** The SCSI CDB. (A CDB can be 12 bytes long.) */
+ uint8_t abCDB[12];
+ /** Reserved. */
+ uint8_t uReserved3[6];
+ /** Sense data pointer. */
+ uint32_t u32PhysAddrSenseData;
+} CCB32, *PCCB32;
+AssertCompileSize(CCB32, 40);
+
+
+/**
+ * The 24-bit command control block.
+ */
+typedef struct CCB24
+{
+ /** Opcode. */
+ uint8_t uOpcode;
+ /** The LUN in the device. */
+ unsigned char uLogicalUnit : 3;
+ /** Data direction for the request. */
+ unsigned char uDataDirection : 2;
+ /** The target device ID. */
+ unsigned char uTargetId : 3;
+ /** Length of the SCSI CDB. */
+ uint8_t cbCDB;
+ /** Sense data length. */
+ uint8_t cbSenseData;
+ /** Data length. */
+ Len24 acbData;
+ /** Data pointer.
+ * This points to the data region or a scatter gather list based on the opc
+ */
+ Addr24 aPhysAddrData;
+ /** Pointer to next CCB for linked commands. */
+ Addr24 aPhysAddrLink;
+ /** Command linking identifier. */
+ uint8_t uLinkId;
+ /** Host adapter status. */
+ uint8_t uHostAdapterStatus;
+ /** Device adapter status. */
+ uint8_t uDeviceStatus;
+ /** Two unused bytes. */
+ uint8_t aReserved[2];
+ /** The SCSI CDB. (A CDB can be 12 bytes long.) */
+ uint8_t abCDB[12];
+} CCB24, *PCCB24;
+AssertCompileSize(CCB24, 30);
+
+/**
+ * The common 24-bit/32-bit command control block. The 32-bit CCB is laid out
+ * such that many fields are in the same location as in the older 24-bit CCB.
+ */
+typedef struct CCBC
+{
+ /** Opcode. */
+ uint8_t uOpcode;
+ /** The LUN in the device. */
+ unsigned char uPad1 : 3;
+ /** Data direction for the request. */
+ unsigned char uDataDirection : 2;
+ /** The target device ID. */
+ unsigned char uPad2 : 3;
+ /** Length of the SCSI CDB. */
+ uint8_t cbCDB;
+ /** Sense data length. */
+ uint8_t cbSenseData;
+ uint8_t aPad1[10];
+ /** Host adapter status. */
+ uint8_t uHostAdapterStatus;
+ /** Device adapter status. */
+ uint8_t uDeviceStatus;
+ uint8_t aPad2[2];
+ /** The SCSI CDB (up to 12 bytes). */
+ uint8_t abCDB[12];
+} CCBC, *PCCBC;
+AssertCompileSize(CCBC, 30);
+
+/* Make sure that the 24-bit/32-bit/common CCB offsets match. */
+AssertCompileMemberOffset(CCBC, cbCDB, 2);
+AssertCompileMemberOffset(CCB24, cbCDB, 2);
+AssertCompileMemberOffset(CCB32, cbCDB, 2);
+AssertCompileMemberOffset(CCBC, uHostAdapterStatus, 14);
+AssertCompileMemberOffset(CCB24, uHostAdapterStatus, 14);
+AssertCompileMemberOffset(CCB32, uHostAdapterStatus, 14);
+AssertCompileMemberOffset(CCBC, abCDB, 18);
+AssertCompileMemberOffset(CCB24, abCDB, 18);
+AssertCompileMemberOffset(CCB32, abCDB, 18);
+
+/** A union of all CCB types (24-bit/32-bit/common). */
+typedef union CCBU
+{
+ CCB32 n; /**< New 32-bit CCB. */
+ CCB24 o; /**< Old 24-bit CCB. */
+ CCBC c; /**< Common CCB subset. */
+} CCBU, *PCCBU;
+
+/** 32-bit scatter-gather list entry. */
+typedef struct SGE32
+{
+ uint32_t cbSegment;
+ uint32_t u32PhysAddrSegmentBase;
+} SGE32, *PSGE32;
+AssertCompileSize(SGE32, 8);
+
+/** 24-bit scatter-gather list entry. */
+typedef struct SGE24
+{
+ Len24 acbSegment;
+ Addr24 aPhysAddrSegmentBase;
+} SGE24, *PSGE24;
+AssertCompileSize(SGE24, 6);
+
+/**
+ * The structure for the "Execute SCSI Command" command.
+ */
+typedef struct ESCMD
+{
+ /** Data length. */
+ uint32_t cbData;
+ /** Data pointer. */
+ uint32_t u32PhysAddrData;
+ /** The device the request is sent to. */
+ uint8_t uTargetId;
+ /** The LUN in the device. */
+ uint8_t uLogicalUnit;
+ /** Reserved */
+ unsigned char uReserved1 : 3;
+ /** Data direction for the request. */
+ unsigned char uDataDirection : 2;
+ /** Reserved */
+ unsigned char uReserved2 : 3;
+ /** Length of the SCSI CDB. */
+ uint8_t cbCDB;
+ /** The SCSI CDB. (A CDB can be 12 bytes long.) */
+ uint8_t abCDB[12];
+} ESCMD, *PESCMD;
+AssertCompileSize(ESCMD, 24);
+
+/**
+ * Task state for a CCB request.
+ */
+typedef struct BUSLOGICREQ
+{
+ /** PDM extended media interface I/O request hande. */
+ PDMMEDIAEXIOREQ hIoReq;
+ /** Device this task is assigned to. */
+ PBUSLOGICDEVICE pTargetDevice;
+ /** The command control block from the guest. */
+ CCBU CCBGuest;
+ /** Guest physical address of th CCB. */
+ RTGCPHYS GCPhysAddrCCB;
+ /** Pointer to the R3 sense buffer. */
+ uint8_t *pbSenseBuffer;
+ /** Flag whether this is a request from the BIOS. */
+ bool fBIOS;
+ /** 24-bit request flag (default is 32-bit). */
+ bool fIs24Bit;
+ /** SCSI status code. */
+ uint8_t u8ScsiSts;
+} BUSLOGICREQ;
+
+#ifdef IN_RING3
+/**
+ * Memory buffer callback.
+ *
+ * @returns nothing.
+ * @param pThis The BusLogic controller instance.
+ * @param GCPhys The guest physical address of the memory buffer.
+ * @param pSgBuf The pointer to the host R3 S/G buffer.
+ * @param cbCopy How many bytes to copy between the two buffers.
+ * @param pcbSkip Initially contains the amount of bytes to skip
+ * starting from the guest physical address before
+ * accessing the S/G buffer and start copying data.
+ * On return this contains the remaining amount if
+ * cbCopy < *pcbSkip or 0 otherwise.
+ */
+typedef DECLCALLBACK(void) BUSLOGICR3MEMCOPYCALLBACK(PBUSLOGIC pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf, size_t cbCopy,
+ size_t *pcbSkip);
+/** Pointer to a memory copy buffer callback. */
+typedef BUSLOGICR3MEMCOPYCALLBACK *PBUSLOGICR3MEMCOPYCALLBACK;
+#endif
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+static int buslogicR3RegisterISARange(PBUSLOGIC pBusLogic, uint8_t uBaseCode);
+#endif
+
+
+/**
+ * Assert IRQ line of the BusLogic adapter.
+ *
+ * @returns nothing.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ * @param fSuppressIrq Flag to suppress IRQ generation regardless of fIRQEnabled
+ * @param uIrqType Type of interrupt being generated.
+ */
+static void buslogicSetInterrupt(PBUSLOGIC pBusLogic, bool fSuppressIrq, uint8_t uIrqType)
+{
+ LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+
+ /* The CMDC interrupt has priority over IMBL and OMBR. */
+ if (uIrqType & (BL_INTR_IMBL | BL_INTR_OMBR))
+ {
+ if (!(pBusLogic->regInterrupt & BL_INTR_CMDC))
+ pBusLogic->regInterrupt |= uIrqType; /* Report now. */
+ else
+ pBusLogic->uPendingIntr |= uIrqType; /* Report later. */
+ }
+ else if (uIrqType & BL_INTR_CMDC)
+ {
+ AssertMsg(pBusLogic->regInterrupt == 0 || pBusLogic->regInterrupt == (BL_INTR_INTV | BL_INTR_CMDC),
+ ("regInterrupt=%02X\n", pBusLogic->regInterrupt));
+ pBusLogic->regInterrupt |= uIrqType;
+ }
+ else
+ AssertMsgFailed(("Invalid interrupt state!\n"));
+
+ pBusLogic->regInterrupt |= BL_INTR_INTV;
+ if (pBusLogic->fIRQEnabled && !fSuppressIrq)
+ {
+ if (!pBusLogic->uIsaIrq)
+ PDMDevHlpPCISetIrq(pBusLogic->CTX_SUFF(pDevIns), 0, 1);
+ else
+ PDMDevHlpISASetIrq(pBusLogic->CTX_SUFF(pDevIns), pBusLogic->uIsaIrq, 1);
+ }
+}
+
+/**
+ * Deasserts the interrupt line of the BusLogic adapter.
+ *
+ * @returns nothing.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ */
+static void buslogicClearInterrupt(PBUSLOGIC pBusLogic)
+{
+ LogFlowFunc(("pBusLogic=%#p, clearing %#02x (pending %#02x)\n",
+ pBusLogic, pBusLogic->regInterrupt, pBusLogic->uPendingIntr));
+ pBusLogic->regInterrupt = 0;
+ pBusLogic->regStatus &= ~BL_STAT_CMDINV;
+ if (!pBusLogic->uIsaIrq)
+ PDMDevHlpPCISetIrq(pBusLogic->CTX_SUFF(pDevIns), 0, 0);
+ else
+ PDMDevHlpISASetIrq(pBusLogic->CTX_SUFF(pDevIns), pBusLogic->uIsaIrq, 0);
+ /* If there's another pending interrupt, report it now. */
+ if (pBusLogic->uPendingIntr)
+ {
+ buslogicSetInterrupt(pBusLogic, false, pBusLogic->uPendingIntr);
+ pBusLogic->uPendingIntr = 0;
+ }
+}
+
+#if defined(IN_RING3)
+
+/**
+ * Advances the mailbox pointer to the next slot.
+ *
+ * @returns nothing.
+ * @param pBusLogic The BusLogic controller instance.
+ */
+DECLINLINE(void) buslogicR3OutgoingMailboxAdvance(PBUSLOGIC pBusLogic)
+{
+ pBusLogic->uMailboxOutgoingPositionCurrent = (pBusLogic->uMailboxOutgoingPositionCurrent + 1) % pBusLogic->cMailbox;
+}
+
+/**
+ * Initialize local RAM of host adapter with default values.
+ *
+ * @returns nothing.
+ * @param pBusLogic The BusLogic controller instance.
+ */
+static void buslogicR3InitializeLocalRam(PBUSLOGIC pBusLogic)
+{
+ /*
+ * These values are mostly from what I think is right
+ * looking at the dmesg output from a Linux guest inside
+ * a VMware server VM.
+ *
+ * So they don't have to be right :)
+ */
+ memset(pBusLogic->LocalRam.u8View, 0, sizeof(HostAdapterLocalRam));
+ pBusLogic->LocalRam.structured.autoSCSIData.fLevelSensitiveInterrupt = true;
+ pBusLogic->LocalRam.structured.autoSCSIData.fParityCheckingEnabled = true;
+ pBusLogic->LocalRam.structured.autoSCSIData.fExtendedTranslation = true; /* Same as in geometry register. */
+ pBusLogic->LocalRam.structured.autoSCSIData.u16DeviceEnabledMask = UINT16_MAX; /* All enabled. Maybe mask out non present devices? */
+ pBusLogic->LocalRam.structured.autoSCSIData.u16WidePermittedMask = UINT16_MAX;
+ pBusLogic->LocalRam.structured.autoSCSIData.u16FastPermittedMask = UINT16_MAX;
+ pBusLogic->LocalRam.structured.autoSCSIData.u16SynchronousPermittedMask = UINT16_MAX;
+ pBusLogic->LocalRam.structured.autoSCSIData.u16DisconnectPermittedMask = UINT16_MAX;
+ pBusLogic->LocalRam.structured.autoSCSIData.fStrictRoundRobinMode = pBusLogic->fStrictRoundRobinMode;
+ pBusLogic->LocalRam.structured.autoSCSIData.u16UltraPermittedMask = UINT16_MAX;
+ /** @todo calculate checksum? */
+}
+
+/**
+ * Do a hardware reset of the buslogic adapter.
+ *
+ * @returns VBox status code.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ * @param fResetIO Flag determining whether ISA I/O should be reset.
+ */
+static int buslogicR3HwReset(PBUSLOGIC pBusLogic, bool fResetIO)
+{
+ LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+
+ /* Reset registers to default values. */
+ pBusLogic->regStatus = BL_STAT_HARDY | BL_STAT_INREQ;
+ pBusLogic->regGeometry = BL_GEOM_XLATEN;
+ pBusLogic->uOperationCode = 0xff; /* No command executing. */
+ pBusLogic->iParameter = 0;
+ pBusLogic->cbCommandParametersLeft = 0;
+ pBusLogic->fIRQEnabled = true;
+ pBusLogic->fStrictRoundRobinMode = false;
+ pBusLogic->fExtendedLunCCBFormat = false;
+ pBusLogic->uMailboxOutgoingPositionCurrent = 0;
+ pBusLogic->uMailboxIncomingPositionCurrent = 0;
+ pBusLogic->uAhaSigIdx = 0;
+
+ /* Clear any active/pending interrupts. */
+ pBusLogic->uPendingIntr = 0;
+ buslogicClearInterrupt(pBusLogic);
+
+ /* Guest-initiated HBA reset does not affect ISA port I/O. */
+ if (fResetIO)
+ {
+ buslogicR3RegisterISARange(pBusLogic, pBusLogic->uDefaultISABaseCode);
+ }
+ buslogicR3InitializeLocalRam(pBusLogic);
+ vboxscsiInitialize(&pBusLogic->VBoxSCSI);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Resets the command state machine for the next command and notifies the guest.
+ *
+ * @returns nothing.
+ * @param pBusLogic Pointer to the BusLogic device instance
+ * @param fSuppressIrq Flag to suppress IRQ generation regardless of current state
+ */
+static void buslogicCommandComplete(PBUSLOGIC pBusLogic, bool fSuppressIrq)
+{
+ LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+ Assert(pBusLogic->uOperationCode != BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND);
+
+ pBusLogic->fUseLocalRam = false;
+ pBusLogic->regStatus |= BL_STAT_HARDY;
+ pBusLogic->iReply = 0;
+
+ /* The Enable OMBR command does not set CMDC when successful. */
+ if (pBusLogic->uOperationCode != BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT)
+ {
+ /* Notify that the command is complete. */
+ pBusLogic->regStatus &= ~BL_STAT_DIRRDY;
+ buslogicSetInterrupt(pBusLogic, fSuppressIrq, BL_INTR_CMDC);
+ }
+
+ pBusLogic->uOperationCode = 0xff;
+ pBusLogic->iParameter = 0;
+}
+
+/**
+ * Memory write helper to handle PCI/ISA differences.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the BusLogic device instance
+ * @param GCPhys Guest physical memory address
+ * @param pvBuf Host side buffer address
+ * @param cbWrite Number of bytes to write
+ */
+static void blPhysWrite(PBUSLOGIC pThis, RTGCPHYS GCPhys, const void *pvBuf, size_t cbWrite)
+{
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhys, pvBuf, cbWrite);
+ else
+ PDMDevHlpPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhys, pvBuf, cbWrite);
+}
+
+#if defined(IN_RING3)
+
+/**
+ * Initiates a hard reset which was issued from the guest.
+ *
+ * @returns nothing
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ * @param fHardReset Flag initiating a hard (vs. soft) reset.
+ */
+static void buslogicR3InitiateReset(PBUSLOGIC pBusLogic, bool fHardReset)
+{
+ LogFlowFunc(("pBusLogic=%#p fHardReset=%d\n", pBusLogic, fHardReset));
+
+ buslogicR3HwReset(pBusLogic, false);
+
+ if (fHardReset)
+ {
+ /* Set the diagnostic active bit in the status register and clear the ready state. */
+ pBusLogic->regStatus |= BL_STAT_DACT;
+ pBusLogic->regStatus &= ~BL_STAT_HARDY;
+
+ /* Remember when the guest initiated a reset (after we're done resetting). */
+ pBusLogic->u64ResetTime = PDMDevHlpTMTimeVirtGetNano(pBusLogic->CTX_SUFF(pDevIns));
+ }
+}
+
+
+/**
+ * Send a mailbox with set status codes to the guest.
+ *
+ * @returns nothing.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ * @param GCPhysAddrCCB The physical guest address of the CCB the mailbox is for.
+ * @param pCCBGuest The command control block.
+ * @param uHostAdapterStatus The host adapter status code to set.
+ * @param uDeviceStatus The target device status to set.
+ * @param uMailboxCompletionCode Completion status code to set in the mailbox.
+ */
+static void buslogicR3SendIncomingMailbox(PBUSLOGIC pBusLogic, RTGCPHYS GCPhysAddrCCB,
+ PCCBU pCCBGuest, uint8_t uHostAdapterStatus,
+ uint8_t uDeviceStatus, uint8_t uMailboxCompletionCode)
+{
+ Mailbox32 MbxIn;
+
+ MbxIn.u32PhysAddrCCB = (uint32_t)GCPhysAddrCCB;
+ MbxIn.u.in.uHostAdapterStatus = uHostAdapterStatus;
+ MbxIn.u.in.uTargetDeviceStatus = uDeviceStatus;
+ MbxIn.u.in.uCompletionCode = uMailboxCompletionCode;
+
+ int rc = PDMCritSectEnter(&pBusLogic->CritSectIntr, VINF_SUCCESS);
+ AssertRC(rc);
+
+ RTGCPHYS GCPhysAddrMailboxIncoming = pBusLogic->GCPhysAddrMailboxIncomingBase
+ + ( pBusLogic->uMailboxIncomingPositionCurrent
+ * (pBusLogic->fMbxIs24Bit ? sizeof(Mailbox24) : sizeof(Mailbox32)) );
+
+ if (uMailboxCompletionCode != BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED_NOT_FOUND)
+ {
+ LogFlowFunc(("Completing CCB %RGp hstat=%u, dstat=%u, outgoing mailbox at %RGp\n", GCPhysAddrCCB,
+ uHostAdapterStatus, uDeviceStatus, GCPhysAddrMailboxIncoming));
+
+ /* Update CCB. */
+ pCCBGuest->c.uHostAdapterStatus = uHostAdapterStatus;
+ pCCBGuest->c.uDeviceStatus = uDeviceStatus;
+ /* Rewrite CCB up to the CDB; perhaps more than necessary. */
+ blPhysWrite(pBusLogic, GCPhysAddrCCB, pCCBGuest, RT_UOFFSETOF(CCBC, abCDB));
+ }
+
+# ifdef RT_STRICT
+ uint8_t uCode;
+ unsigned uCodeOffs = pBusLogic->fMbxIs24Bit ? RT_OFFSETOF(Mailbox24, uCmdState) : RT_OFFSETOF(Mailbox32, u.out.uActionCode);
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrMailboxIncoming + uCodeOffs, &uCode, sizeof(uCode));
+ Assert(uCode == BUSLOGIC_MAILBOX_INCOMING_COMPLETION_FREE);
+# endif
+
+ /* Update mailbox. */
+ if (pBusLogic->fMbxIs24Bit)
+ {
+ Mailbox24 Mbx24;
+
+ Mbx24.uCmdState = MbxIn.u.in.uCompletionCode;
+ U32_TO_ADDR(Mbx24.aPhysAddrCCB, MbxIn.u32PhysAddrCCB);
+ Log(("24-bit mailbox: completion code=%u, CCB at %RGp\n", Mbx24.uCmdState, (RTGCPHYS)ADDR_TO_U32(Mbx24.aPhysAddrCCB)));
+ blPhysWrite(pBusLogic, GCPhysAddrMailboxIncoming, &Mbx24, sizeof(Mailbox24));
+ }
+ else
+ {
+ Log(("32-bit mailbox: completion code=%u, CCB at %RGp\n", MbxIn.u.in.uCompletionCode, GCPhysAddrCCB));
+ blPhysWrite(pBusLogic, GCPhysAddrMailboxIncoming, &MbxIn, sizeof(Mailbox32));
+ }
+
+ /* Advance to next mailbox position. */
+ pBusLogic->uMailboxIncomingPositionCurrent++;
+ if (pBusLogic->uMailboxIncomingPositionCurrent >= pBusLogic->cMailbox)
+ pBusLogic->uMailboxIncomingPositionCurrent = 0;
+
+# ifdef LOG_ENABLED
+ ASMAtomicIncU32(&pBusLogic->cInMailboxesReady);
+# endif
+
+ buslogicSetInterrupt(pBusLogic, false, BL_INTR_IMBL);
+
+ PDMCritSectLeave(&pBusLogic->CritSectIntr);
+}
+
+# ifdef LOG_ENABLED
+
+/**
+ * Dumps the content of a mailbox for debugging purposes.
+ *
+ * @return nothing
+ * @param pMailbox The mailbox to dump.
+ * @param fOutgoing true if dumping the outgoing state.
+ * false if dumping the incoming state.
+ */
+static void buslogicR3DumpMailboxInfo(PMailbox32 pMailbox, bool fOutgoing)
+{
+ Log(("%s: Dump for %s mailbox:\n", __FUNCTION__, fOutgoing ? "outgoing" : "incoming"));
+ Log(("%s: u32PhysAddrCCB=%#x\n", __FUNCTION__, pMailbox->u32PhysAddrCCB));
+ if (fOutgoing)
+ {
+ Log(("%s: uActionCode=%u\n", __FUNCTION__, pMailbox->u.out.uActionCode));
+ }
+ else
+ {
+ Log(("%s: uHostAdapterStatus=%u\n", __FUNCTION__, pMailbox->u.in.uHostAdapterStatus));
+ Log(("%s: uTargetDeviceStatus=%u\n", __FUNCTION__, pMailbox->u.in.uTargetDeviceStatus));
+ Log(("%s: uCompletionCode=%u\n", __FUNCTION__, pMailbox->u.in.uCompletionCode));
+ }
+}
+
+/**
+ * Dumps the content of a command control block for debugging purposes.
+ *
+ * @returns nothing.
+ * @param pCCB Pointer to the command control block to dump.
+ * @param fIs24BitCCB Flag to determine CCB format.
+ */
+static void buslogicR3DumpCCBInfo(PCCBU pCCB, bool fIs24BitCCB)
+{
+ Log(("%s: Dump for %s Command Control Block:\n", __FUNCTION__, fIs24BitCCB ? "24-bit" : "32-bit"));
+ Log(("%s: uOpCode=%#x\n", __FUNCTION__, pCCB->c.uOpcode));
+ Log(("%s: uDataDirection=%u\n", __FUNCTION__, pCCB->c.uDataDirection));
+ Log(("%s: cbCDB=%u\n", __FUNCTION__, pCCB->c.cbCDB));
+ Log(("%s: cbSenseData=%u\n", __FUNCTION__, pCCB->c.cbSenseData));
+ Log(("%s: uHostAdapterStatus=%u\n", __FUNCTION__, pCCB->c.uHostAdapterStatus));
+ Log(("%s: uDeviceStatus=%u\n", __FUNCTION__, pCCB->c.uDeviceStatus));
+ if (fIs24BitCCB)
+ {
+ Log(("%s: cbData=%u\n", __FUNCTION__, LEN_TO_U32(pCCB->o.acbData)));
+ Log(("%s: PhysAddrData=%#x\n", __FUNCTION__, ADDR_TO_U32(pCCB->o.aPhysAddrData)));
+ Log(("%s: uTargetId=%u\n", __FUNCTION__, pCCB->o.uTargetId));
+ Log(("%s: uLogicalUnit=%u\n", __FUNCTION__, pCCB->o.uLogicalUnit));
+ }
+ else
+ {
+ Log(("%s: cbData=%u\n", __FUNCTION__, pCCB->n.cbData));
+ Log(("%s: PhysAddrData=%#x\n", __FUNCTION__, pCCB->n.u32PhysAddrData));
+ Log(("%s: uTargetId=%u\n", __FUNCTION__, pCCB->n.uTargetId));
+ Log(("%s: uLogicalUnit=%u\n", __FUNCTION__, pCCB->n.uLogicalUnit));
+ Log(("%s: fTagQueued=%d\n", __FUNCTION__, pCCB->n.fTagQueued));
+ Log(("%s: uQueueTag=%u\n", __FUNCTION__, pCCB->n.uQueueTag));
+ Log(("%s: fLegacyTagEnable=%u\n", __FUNCTION__, pCCB->n.fLegacyTagEnable));
+ Log(("%s: uLegacyQueueTag=%u\n", __FUNCTION__, pCCB->n.uLegacyQueueTag));
+ Log(("%s: PhysAddrSenseData=%#x\n", __FUNCTION__, pCCB->n.u32PhysAddrSenseData));
+ }
+ Log(("%s: uCDB[0]=%#x\n", __FUNCTION__, pCCB->c.abCDB[0]));
+ for (int i = 1; i < pCCB->c.cbCDB; i++)
+ Log(("%s: uCDB[%d]=%u\n", __FUNCTION__, i, pCCB->c.abCDB[i]));
+}
+
+# endif /* LOG_ENABLED */
+
+/**
+ * Allocate data buffer.
+ *
+ * @param pDevIns PDM device instance.
+ * @param fIs24Bit Flag whether the 24bit SG format is used.
+ * @param GCSGList Guest physical address of S/G list.
+ * @param cEntries Number of list entries to read.
+ * @param pSGEList Pointer to 32-bit S/G list storage.
+ */
+static void buslogicR3ReadSGEntries(PPDMDEVINS pDevIns, bool fIs24Bit, RTGCPHYS GCSGList,
+ uint32_t cEntries, SGE32 *pSGEList)
+{
+ /* Read the S/G entries. Convert 24-bit entries to 32-bit format. */
+ if (fIs24Bit)
+ {
+ SGE24 aSGE24[32];
+ Assert(cEntries <= RT_ELEMENTS(aSGE24));
+
+ Log2(("Converting %u 24-bit S/G entries to 32-bit\n", cEntries));
+ PDMDevHlpPhysRead(pDevIns, GCSGList, &aSGE24, cEntries * sizeof(SGE24));
+ for (uint32_t i = 0; i < cEntries; ++i)
+ {
+ pSGEList[i].cbSegment = LEN_TO_U32(aSGE24[i].acbSegment);
+ pSGEList[i].u32PhysAddrSegmentBase = ADDR_TO_U32(aSGE24[i].aPhysAddrSegmentBase);
+ }
+ }
+ else
+ PDMDevHlpPhysRead(pDevIns, GCSGList, pSGEList, cEntries * sizeof(SGE32));
+}
+
+/**
+ * Determines the size of th guest data buffer.
+ *
+ * @returns VBox status code.
+ * @param pDevIns PDM device instance.
+ * @param pCCBGuest The CCB of the guest.
+ * @param fIs24Bit Flag whether the 24bit SG format is used.
+ * @param pcbBuf Where to store the size of the guest data buffer on success.
+ */
+static int buslogicR3QueryDataBufferSize(PPDMDEVINS pDevIns, PCCBU pCCBGuest, bool fIs24Bit, size_t *pcbBuf)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cbDataCCB;
+ uint32_t u32PhysAddrCCB;
+ size_t cbBuf = 0;
+
+ /* Extract the data length and physical address from the CCB. */
+ if (fIs24Bit)
+ {
+ u32PhysAddrCCB = ADDR_TO_U32(pCCBGuest->o.aPhysAddrData);
+ cbDataCCB = LEN_TO_U32(pCCBGuest->o.acbData);
+ }
+ else
+ {
+ u32PhysAddrCCB = pCCBGuest->n.u32PhysAddrData;
+ cbDataCCB = pCCBGuest->n.cbData;
+ }
+
+#if 1
+ /* Hack for NT 10/91: A CCB describes a 2K buffer, but TEST UNIT READY is executed. This command
+ * returns no data, hence the buffer must be left alone!
+ */
+ if (pCCBGuest->c.abCDB[0] == 0)
+ cbDataCCB = 0;
+#endif
+
+ if ( (pCCBGuest->c.uDataDirection != BUSLOGIC_CCB_DIRECTION_NO_DATA)
+ && cbDataCCB)
+ {
+ /*
+ * The BusLogic adapter can handle two different data buffer formats.
+ * The first one is that the data pointer entry in the CCB points to
+ * the buffer directly. In second mode the data pointer points to a
+ * scatter gather list which describes the buffer.
+ */
+ if ( (pCCBGuest->c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_SCATTER_GATHER)
+ || (pCCBGuest->c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER))
+ {
+ uint32_t cScatterGatherGCRead;
+ uint32_t iScatterGatherEntry;
+ SGE32 aScatterGatherReadGC[32]; /* A buffer for scatter gather list entries read from guest memory. */
+ uint32_t cScatterGatherGCLeft = cbDataCCB / (fIs24Bit ? sizeof(SGE24) : sizeof(SGE32));
+ RTGCPHYS GCPhysAddrScatterGatherCurrent = u32PhysAddrCCB;
+
+ /* Count number of bytes to transfer. */
+ do
+ {
+ cScatterGatherGCRead = (cScatterGatherGCLeft < RT_ELEMENTS(aScatterGatherReadGC))
+ ? cScatterGatherGCLeft
+ : RT_ELEMENTS(aScatterGatherReadGC);
+ cScatterGatherGCLeft -= cScatterGatherGCRead;
+
+ buslogicR3ReadSGEntries(pDevIns, fIs24Bit, GCPhysAddrScatterGatherCurrent, cScatterGatherGCRead, aScatterGatherReadGC);
+
+ for (iScatterGatherEntry = 0; iScatterGatherEntry < cScatterGatherGCRead; iScatterGatherEntry++)
+ cbBuf += aScatterGatherReadGC[iScatterGatherEntry].cbSegment;
+
+ /* Set address to the next entries to read. */
+ GCPhysAddrScatterGatherCurrent += cScatterGatherGCRead * (fIs24Bit ? sizeof(SGE24) : sizeof(SGE32));
+ } while (cScatterGatherGCLeft > 0);
+
+ Log(("%s: cbBuf=%d\n", __FUNCTION__, cbBuf));
+ }
+ else if ( pCCBGuest->c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB
+ || pCCBGuest->c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_DATA_LENGTH)
+ cbBuf = cbDataCCB;
+ }
+
+ if (RT_SUCCESS(rc))
+ *pcbBuf = cbBuf;
+
+ return rc;
+}
+
+/**
+ * Copy from guest to host memory worker.
+ *
+ * @copydoc BUSLOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) buslogicR3CopyBufferFromGuestWorker(PBUSLOGIC pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc BUSLOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) buslogicR3CopyBufferToGuestWorker(PBUSLOGIC pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ blPhysWrite(pThis, GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Walks the guest S/G buffer calling the given copy worker for every buffer.
+ *
+ * @returns The amout of bytes actually copied.
+ * @param pThis Pointer to the Buslogic device state.
+ * @param pReq Pointe to the request state.
+ * @param pfnCopyWorker The copy method to apply for each guest buffer.
+ * @param pSgBuf The host S/G buffer.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t buslogicR3SgBufWalker(PBUSLOGIC pThis, PBUSLOGICREQ pReq,
+ PBUSLOGICR3MEMCOPYCALLBACK pfnCopyWorker,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ PPDMDEVINS pDevIns = pThis->CTX_SUFF(pDevIns);
+ uint32_t cbDataCCB;
+ uint32_t u32PhysAddrCCB;
+ size_t cbCopied = 0;
+
+ /*
+ * Add the amount to skip to the host buffer size to avoid a
+ * few conditionals later on.
+ */
+ cbCopy += cbSkip;
+
+ /* Extract the data length and physical address from the CCB. */
+ if (pReq->fIs24Bit)
+ {
+ u32PhysAddrCCB = ADDR_TO_U32(pReq->CCBGuest.o.aPhysAddrData);
+ cbDataCCB = LEN_TO_U32(pReq->CCBGuest.o.acbData);
+ }
+ else
+ {
+ u32PhysAddrCCB = pReq->CCBGuest.n.u32PhysAddrData;
+ cbDataCCB = pReq->CCBGuest.n.cbData;
+ }
+
+#if 1
+ /* Hack for NT 10/91: A CCB describes a 2K buffer, but TEST UNIT READY is executed. This command
+ * returns no data, hence the buffer must be left alone!
+ */
+ if (pReq->CCBGuest.c.abCDB[0] == 0)
+ cbDataCCB = 0;
+#endif
+
+ LogFlowFunc(("pReq=%#p cbDataCCB=%u direction=%u cbCopy=%zu\n", pReq, cbDataCCB,
+ pReq->CCBGuest.c.uDataDirection, cbCopy));
+
+ if ( (cbDataCCB > 0)
+ && ( pReq->CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_IN
+ || pReq->CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_OUT
+ || pReq->CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_UNKNOWN))
+ {
+ if ( (pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_SCATTER_GATHER)
+ || (pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER))
+ {
+ uint32_t cScatterGatherGCRead;
+ uint32_t iScatterGatherEntry;
+ SGE32 aScatterGatherReadGC[32]; /* Number of scatter gather list entries read from guest memory. */
+ uint32_t cScatterGatherGCLeft = cbDataCCB / (pReq->fIs24Bit ? sizeof(SGE24) : sizeof(SGE32));
+ RTGCPHYS GCPhysAddrScatterGatherCurrent = u32PhysAddrCCB;
+
+ do
+ {
+ cScatterGatherGCRead = (cScatterGatherGCLeft < RT_ELEMENTS(aScatterGatherReadGC))
+ ? cScatterGatherGCLeft
+ : RT_ELEMENTS(aScatterGatherReadGC);
+ cScatterGatherGCLeft -= cScatterGatherGCRead;
+
+ buslogicR3ReadSGEntries(pDevIns, pReq->fIs24Bit, GCPhysAddrScatterGatherCurrent,
+ cScatterGatherGCRead, aScatterGatherReadGC);
+
+ for (iScatterGatherEntry = 0; iScatterGatherEntry < cScatterGatherGCRead && cbCopy > 0; iScatterGatherEntry++)
+ {
+ RTGCPHYS GCPhysAddrDataBase;
+ size_t cbCopyThis;
+
+ Log(("%s: iScatterGatherEntry=%u\n", __FUNCTION__, iScatterGatherEntry));
+
+ GCPhysAddrDataBase = (RTGCPHYS)aScatterGatherReadGC[iScatterGatherEntry].u32PhysAddrSegmentBase;
+ cbCopyThis = RT_MIN(cbCopy, aScatterGatherReadGC[iScatterGatherEntry].cbSegment);
+
+ Log(("%s: GCPhysAddrDataBase=%RGp cbCopyThis=%zu\n", __FUNCTION__, GCPhysAddrDataBase, cbCopyThis));
+
+ pfnCopyWorker(pThis, GCPhysAddrDataBase, pSgBuf, cbCopyThis, &cbSkip);
+ cbCopied += cbCopyThis;
+ cbCopy -= cbCopyThis;
+ }
+
+ /* Set address to the next entries to read. */
+ GCPhysAddrScatterGatherCurrent += cScatterGatherGCRead * (pReq->fIs24Bit ? sizeof(SGE24) : sizeof(SGE32));
+ } while ( cScatterGatherGCLeft > 0
+ && cbCopy > 0);
+
+ }
+ else if ( pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB
+ || pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_DATA_LENGTH)
+ {
+ /* The buffer is not scattered. */
+ RTGCPHYS GCPhysAddrDataBase = u32PhysAddrCCB;
+
+ AssertMsg(GCPhysAddrDataBase != 0, ("Physical address is 0\n"));
+
+ Log(("Non-scattered buffer:\n"));
+ Log(("u32PhysAddrData=%#x\n", u32PhysAddrCCB));
+ Log(("cbData=%u\n", cbDataCCB));
+ Log(("GCPhysAddrDataBase=0x%RGp\n", GCPhysAddrDataBase));
+
+ /* Copy the data into the guest memory. */
+ pfnCopyWorker(pThis, GCPhysAddrDataBase, pSgBuf, RT_MIN(cbDataCCB, cbCopy), &cbSkip);
+ cbCopied += RT_MIN(cbDataCCB, cbCopy);
+ }
+ }
+
+ return cbCopied - RT_MIN(cbSkip, cbCopied);
+}
+
+/**
+ * Copies a data buffer into the S/G buffer set up by the guest.
+ *
+ * @returns Amount of bytes copied to the guest.
+ * @param pThis The BusLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pSgBuf The S/G buffer to copy from.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t buslogicR3CopySgBufToGuest(PBUSLOGIC pThis, PBUSLOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return buslogicR3SgBufWalker(pThis, pReq, buslogicR3CopyBufferToGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copies the guest S/G buffer into a host data buffer.
+ *
+ * @returns Amount of bytes copied from the guest.
+ * @param pThis The BusLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pSgBuf The S/G buffer to copy into.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t buslogicR3CopySgBufFromGuest(PBUSLOGIC pThis, PBUSLOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return buslogicR3SgBufWalker(pThis, pReq, buslogicR3CopyBufferFromGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+/** Convert sense buffer length taking into account shortcut values. */
+static uint32_t buslogicR3ConvertSenseBufferLength(uint32_t cbSense)
+{
+ /* Convert special sense buffer length values. */
+ if (cbSense == 0)
+ cbSense = 14; /* 0 means standard 14-byte buffer. */
+ else if (cbSense == 1)
+ cbSense = 0; /* 1 means no sense data. */
+ else if (cbSense < 8)
+ AssertMsgFailed(("Reserved cbSense value of %d used!\n", cbSense));
+
+ return cbSense;
+}
+
+/**
+ * Free the sense buffer.
+ *
+ * @returns nothing.
+ * @param pReq Pointer to the request state.
+ * @param fCopy If sense data should be copied to guest memory.
+ */
+static void buslogicR3SenseBufferFree(PBUSLOGICREQ pReq, bool fCopy)
+{
+ uint32_t cbSenseBuffer;
+
+ cbSenseBuffer = buslogicR3ConvertSenseBufferLength(pReq->CCBGuest.c.cbSenseData);
+
+ /* Copy the sense buffer into guest memory if requested. */
+ if (fCopy && cbSenseBuffer)
+ {
+ PBUSLOGIC pThis = pReq->pTargetDevice->CTX_SUFF(pBusLogic);
+ RTGCPHYS GCPhysAddrSenseBuffer;
+
+ /* With 32-bit CCBs, the (optional) sense buffer physical address is provided separately.
+ * On the other hand, with 24-bit CCBs, the sense buffer is simply located at the end of
+ * the CCB, right after the variable-length CDB.
+ */
+ if (pReq->fIs24Bit)
+ {
+ GCPhysAddrSenseBuffer = pReq->GCPhysAddrCCB;
+ GCPhysAddrSenseBuffer += pReq->CCBGuest.c.cbCDB + RT_OFFSETOF(CCB24, abCDB);
+ }
+ else
+ GCPhysAddrSenseBuffer = pReq->CCBGuest.n.u32PhysAddrSenseData;
+
+ Log3(("%s: sense buffer: %.*Rhxs\n", __FUNCTION__, cbSenseBuffer, pReq->pbSenseBuffer));
+ blPhysWrite(pThis, GCPhysAddrSenseBuffer, pReq->pbSenseBuffer, cbSenseBuffer);
+ }
+
+ RTMemFree(pReq->pbSenseBuffer);
+ pReq->pbSenseBuffer = NULL;
+}
+
+/**
+ * Alloc the sense buffer.
+ *
+ * @returns VBox status code.
+ * @param pReq Pointer to the task state.
+ */
+static int buslogicR3SenseBufferAlloc(PBUSLOGICREQ pReq)
+{
+ pReq->pbSenseBuffer = NULL;
+
+ uint32_t cbSenseBuffer = buslogicR3ConvertSenseBufferLength(pReq->CCBGuest.c.cbSenseData);
+ if (cbSenseBuffer)
+ {
+ pReq->pbSenseBuffer = (uint8_t *)RTMemAllocZ(cbSenseBuffer);
+ if (!pReq->pbSenseBuffer)
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Parses the command buffer and executes it.
+ *
+ * @returns VBox status code.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ */
+static int buslogicProcessCommand(PBUSLOGIC pBusLogic)
+{
+ int rc = VINF_SUCCESS;
+ bool fSuppressIrq = false;
+
+ LogFlowFunc(("pBusLogic=%#p\n", pBusLogic));
+ AssertMsg(pBusLogic->uOperationCode != 0xff, ("There is no command to execute\n"));
+
+ switch (pBusLogic->uOperationCode)
+ {
+ case BUSLOGICCOMMAND_TEST_CMDC_INTERRUPT:
+ /* Valid command, no reply. */
+ pBusLogic->cbReplyParametersLeft = 0;
+ break;
+ case BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION:
+ {
+ PReplyInquirePCIHostAdapterInformation pReply = (PReplyInquirePCIHostAdapterInformation)pBusLogic->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquirePCIHostAdapterInformation));
+
+ /* It seems VMware does not provide valid information here too, lets do the same :) */
+ pReply->InformationIsValid = 0;
+ pReply->IsaIOPort = pBusLogic->uISABaseCode;
+ pReply->IRQ = PCIDevGetInterruptLine(&pBusLogic->dev);
+ pBusLogic->cbReplyParametersLeft = sizeof(ReplyInquirePCIHostAdapterInformation);
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT:
+ {
+ /* no-op */
+ pBusLogic->cbReplyParametersLeft = 0;
+ break;
+ }
+ case BUSLOGICCOMMAND_MODIFY_IO_ADDRESS:
+ {
+ /* Modify the ISA-compatible I/O port base. Note that this technically
+ * violates the PCI spec, as this address is not reported through PCI.
+ * However, it is required for compatibility with old drivers.
+ */
+#ifdef IN_RING3
+ Log(("ISA I/O for PCI (code %x)\n", pBusLogic->aCommandBuffer[0]));
+ buslogicR3RegisterISARange(pBusLogic, pBusLogic->aCommandBuffer[0]);
+ pBusLogic->cbReplyParametersLeft = 0;
+ fSuppressIrq = true;
+ break;
+#else
+ AssertMsgFailed(("Must never get here!\n"));
+ break;
+#endif
+ }
+ case BUSLOGICCOMMAND_INQUIRE_BOARD_ID:
+ {
+ /* The special option byte is important: If it is '0' or 'B', Windows NT drivers
+ * for Adaptec AHA-154x may claim the adapter. The BusLogic drivers will claim
+ * the adapter only when the byte is *not* '0' or 'B'.
+ */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ pBusLogic->aReplyBuffer[0] = 'A'; /* Firmware option bytes */
+ pBusLogic->aReplyBuffer[1] = '0'; /* Special option byte */
+ }
+ else
+ {
+ pBusLogic->aReplyBuffer[0] = 'A'; /* Firmware option bytes */
+ pBusLogic->aReplyBuffer[1] = 'A'; /* Special option byte */
+ }
+
+ /* We report version 5.07B. This reply will provide the first two digits. */
+ pBusLogic->aReplyBuffer[2] = '5'; /* Major version 5 */
+ pBusLogic->aReplyBuffer[3] = '0'; /* Minor version 0 */
+ pBusLogic->cbReplyParametersLeft = 4; /* Reply is 4 bytes long */
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER:
+ {
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ /* Newer ASPI4DOS.SYS versions expect this command to fail. */
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ pBusLogic->aReplyBuffer[0] = '7';
+ pBusLogic->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER:
+ {
+ pBusLogic->aReplyBuffer[0] = 'B';
+ pBusLogic->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS:
+ /* The parameter list length is determined by the first byte of the command buffer. */
+ if (pBusLogic->iParameter == 1)
+ {
+ /* First pass - set the number of following parameter bytes. */
+ pBusLogic->cbCommandParametersLeft = pBusLogic->aCommandBuffer[0];
+ Log(("Set HA options: %u bytes follow\n", pBusLogic->cbCommandParametersLeft));
+ }
+ else
+ {
+ /* Second pass - process received data. */
+ Log(("Set HA options: received %u bytes\n", pBusLogic->aCommandBuffer[0]));
+ /* We ignore the data - it only concerns the SCSI hardware protocol. */
+ }
+ pBusLogic->cbReplyParametersLeft = 0;
+ break;
+
+ case BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND:
+ /* The parameter list length is at least 12 bytes; the 12th byte determines
+ * the number of additional CDB bytes that will follow.
+ */
+ if (pBusLogic->iParameter == 12)
+ {
+ /* First pass - set the number of following CDB bytes. */
+ pBusLogic->cbCommandParametersLeft = pBusLogic->aCommandBuffer[11];
+ Log(("Execute SCSI cmd: %u more bytes follow\n", pBusLogic->cbCommandParametersLeft));
+ }
+ else
+ {
+ PESCMD pCmd;
+
+ /* Second pass - process received data. */
+ Log(("Execute SCSI cmd: received %u bytes\n", pBusLogic->aCommandBuffer[0]));
+
+ pCmd = (PESCMD)pBusLogic->aCommandBuffer;
+ Log(("Addr %08X, cbData %08X, cbCDB=%u\n", pCmd->u32PhysAddrData, pCmd->cbData, pCmd->cbCDB));
+ }
+ // This is currently a dummy - just fails every command.
+ pBusLogic->cbReplyParametersLeft = 4;
+ pBusLogic->aReplyBuffer[0] = pBusLogic->aReplyBuffer[1] = 0;
+ pBusLogic->aReplyBuffer[2] = 0x11; /* HBA status (timeout). */
+ pBusLogic->aReplyBuffer[3] = 0; /* Device status. */
+ break;
+
+ case BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER:
+ {
+ /* Not supported on AHA-154x. */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+ if (pBusLogic->aCommandBuffer[0] > sizeof(pBusLogic->aReplyBuffer))
+ {
+ Log(("Requested too much adapter model number data (%u)!\n", pBusLogic->aCommandBuffer[0]));
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+ memset(pBusLogic->aReplyBuffer, 0, sizeof(pBusLogic->aReplyBuffer));
+ const char aModelName[] = "958D "; /* Trailing \0 is fine, that's the filler anyway. */
+ int cCharsToTransfer = pBusLogic->cbReplyParametersLeft <= sizeof(aModelName)
+ ? pBusLogic->cbReplyParametersLeft
+ : sizeof(aModelName);
+
+ for (int i = 0; i < cCharsToTransfer; i++)
+ pBusLogic->aReplyBuffer[i] = aModelName[i];
+
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_CONFIGURATION:
+ {
+ uint8_t uIrq;
+
+ if (pBusLogic->uIsaIrq)
+ uIrq = pBusLogic->uIsaIrq;
+ else
+ uIrq = PCIDevGetInterruptLine(&pBusLogic->dev);
+
+ pBusLogic->cbReplyParametersLeft = sizeof(ReplyInquireConfiguration);
+ PReplyInquireConfiguration pReply = (PReplyInquireConfiguration)pBusLogic->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquireConfiguration));
+
+ pReply->uHostAdapterId = 7; /* The controller has always 7 as ID. */
+ pReply->fDmaChannel6 = 1; /* DMA channel 6 is a good default. */
+
+ /* The PCI IRQ is not necessarily representable in this structure.
+ * If that is the case, the guest likely won't function correctly,
+ * therefore we log a warning. Note that for ISA configurations, we
+ * can only allow IRQs that can be supported; for PCI, the HBA
+ * has no control over IRQ assignment.
+ */
+ switch (uIrq)
+ {
+ case 9: pReply->fIrqChannel9 = 1; break;
+ case 10: pReply->fIrqChannel10 = 1; break;
+ case 11: pReply->fIrqChannel11 = 1; break;
+ case 12: pReply->fIrqChannel12 = 1; break;
+ case 14: pReply->fIrqChannel14 = 1; break;
+ case 15: pReply->fIrqChannel15 = 1; break;
+ default:
+ LogRel(("Warning: PCI IRQ %d cannot be represented as ISA!\n", uIrq));
+ break;
+ }
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION:
+ {
+ /* Some Adaptec AHA-154x drivers (e.g. OS/2) execute this command and expect
+ * it to fail. If it succeeds, the drivers refuse to load. However, some newer
+ * Adaptec 154x models supposedly support it too??
+ */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+ pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+ PReplyInquireExtendedSetupInformation pReply = (PReplyInquireExtendedSetupInformation)pBusLogic->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquireExtendedSetupInformation));
+
+ /** @todo should this reflect the RAM contents (AutoSCSIRam)? */
+ pReply->uBusType = 'E'; /* EISA style */
+ pReply->u16ScatterGatherLimit = 8192;
+ pReply->cMailbox = pBusLogic->cMailbox;
+ pReply->uMailboxAddressBase = (uint32_t)pBusLogic->GCPhysAddrMailboxOutgoingBase;
+ pReply->fLevelSensitiveInterrupt = true;
+ pReply->fHostWideSCSI = true;
+ pReply->fHostUltraSCSI = true;
+ memcpy(pReply->aFirmwareRevision, "07B", sizeof(pReply->aFirmwareRevision));
+
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION:
+ {
+ /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+ pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+ PReplyInquireSetupInformation pReply = (PReplyInquireSetupInformation)pBusLogic->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquireSetupInformation));
+ pReply->fSynchronousInitiationEnabled = true;
+ pReply->fParityCheckingEnabled = true;
+ pReply->cMailbox = pBusLogic->cMailbox;
+ U32_TO_ADDR(pReply->MailboxAddress, pBusLogic->GCPhysAddrMailboxOutgoingBase);
+ /* The 'D' signature (actually 'SD' for Storage Dimensions, and 'BD' for BusLogic)
+ * prevents Adaptec's OS/2 drivers from getting too friendly with BusLogic hardware
+ * and upsetting the HBA state.
+ */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ pReply->uSignature = 0; /* Zeros for Adaptec. */
+ pReply->uCharacterD = 0;
+ }
+ else
+ {
+ pReply->uSignature = 'B';
+ pReply->uCharacterD = 'D'; /* BusLogic model. */
+ }
+ pReply->uHostBusType = 'F'; /* PCI bus. */
+ break;
+ }
+ case BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM:
+ {
+ /*
+ * First element in the command buffer contains start offset to read from
+ * and second one the number of bytes to read.
+ */
+ uint8_t uOffset = pBusLogic->aCommandBuffer[0];
+ pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[1];
+
+ pBusLogic->fUseLocalRam = true;
+ pBusLogic->iReply = uOffset;
+ break;
+ }
+ case BUSLOGICCOMMAND_INITIALIZE_MAILBOX:
+ {
+ PRequestInitMbx pRequest = (PRequestInitMbx)pBusLogic->aCommandBuffer;
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ if (!pRequest->cMailbox)
+ {
+ Log(("cMailboxes=%u (24-bit mode), fail!\n", pBusLogic->cMailbox));
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pBusLogic->fMbxIs24Bit = true;
+ pBusLogic->cMailbox = pRequest->cMailbox;
+ pBusLogic->GCPhysAddrMailboxOutgoingBase = (RTGCPHYS)ADDR_TO_U32(pRequest->aMailboxBaseAddr);
+ /* The area for incoming mailboxes is right after the last entry of outgoing mailboxes. */
+ pBusLogic->GCPhysAddrMailboxIncomingBase = pBusLogic->GCPhysAddrMailboxOutgoingBase + (pBusLogic->cMailbox * sizeof(Mailbox24));
+
+ Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxOutgoingBase));
+ Log(("GCPhysAddrMailboxIncomingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxIncomingBase));
+ Log(("cMailboxes=%u (24-bit mode)\n", pBusLogic->cMailbox));
+ LogRel(("Initialized 24-bit mailbox, %d entries at %08x\n", pRequest->cMailbox, ADDR_TO_U32(pRequest->aMailboxBaseAddr)));
+
+ pBusLogic->regStatus &= ~BL_STAT_INREQ;
+ break;
+ }
+ case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+ {
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ PRequestInitializeExtendedMailbox pRequest = (PRequestInitializeExtendedMailbox)pBusLogic->aCommandBuffer;
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ if (!pRequest->cMailbox)
+ {
+ Log(("cMailboxes=%u (32-bit mode), fail!\n", pBusLogic->cMailbox));
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pBusLogic->fMbxIs24Bit = false;
+ pBusLogic->cMailbox = pRequest->cMailbox;
+ pBusLogic->GCPhysAddrMailboxOutgoingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress;
+ /* The area for incoming mailboxes is right after the last entry of outgoing mailboxes. */
+ pBusLogic->GCPhysAddrMailboxIncomingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress + (pBusLogic->cMailbox * sizeof(Mailbox32));
+
+ Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxOutgoingBase));
+ Log(("GCPhysAddrMailboxIncomingBase=%RGp\n", pBusLogic->GCPhysAddrMailboxIncomingBase));
+ Log(("cMailboxes=%u (32-bit mode)\n", pBusLogic->cMailbox));
+ LogRel(("Initialized 32-bit mailbox, %d entries at %08x\n", pRequest->cMailbox, pRequest->uMailboxBaseAddress));
+
+ pBusLogic->regStatus &= ~BL_STAT_INREQ;
+ break;
+ }
+ case BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE:
+ {
+ if (pBusLogic->aCommandBuffer[0] == 0)
+ pBusLogic->fStrictRoundRobinMode = false;
+ else if (pBusLogic->aCommandBuffer[0] == 1)
+ pBusLogic->fStrictRoundRobinMode = true;
+ else
+ AssertMsgFailed(("Invalid round robin mode %d\n", pBusLogic->aCommandBuffer[0]));
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_CCB_FORMAT:
+ {
+ if (pBusLogic->aCommandBuffer[0] == 0)
+ pBusLogic->fExtendedLunCCBFormat = false;
+ else if (pBusLogic->aCommandBuffer[0] == 1)
+ pBusLogic->fExtendedLunCCBFormat = true;
+ else
+ AssertMsgFailed(("Invalid CCB format %d\n", pBusLogic->aCommandBuffer[0]));
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_0_TO_7:
+ /* This is supposed to send TEST UNIT READY to each target/LUN.
+ * We cheat and skip that, since we already know what's attached
+ */
+ memset(pBusLogic->aReplyBuffer, 0, 8);
+ for (int i = 0; i < 8; ++i)
+ {
+ if (pBusLogic->aDeviceStates[i].fPresent)
+ pBusLogic->aReplyBuffer[i] = 1;
+ }
+ pBusLogic->aReplyBuffer[7] = 0; /* HA hardcoded at ID 7. */
+ pBusLogic->cbReplyParametersLeft = 8;
+ break;
+ case BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_8_TO_15:
+ /* See note about cheating above. */
+ memset(pBusLogic->aReplyBuffer, 0, 8);
+ for (int i = 0; i < 8; ++i)
+ {
+ if (pBusLogic->aDeviceStates[i + 8].fPresent)
+ pBusLogic->aReplyBuffer[i] = 1;
+ }
+ pBusLogic->cbReplyParametersLeft = 8;
+ break;
+ case BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES:
+ {
+ /* Each bit which is set in the 16bit wide variable means a present device. */
+ uint16_t u16TargetsPresentMask = 0;
+
+ for (uint8_t i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+ {
+ if (pBusLogic->aDeviceStates[i].fPresent)
+ u16TargetsPresentMask |= (1 << i);
+ }
+ pBusLogic->aReplyBuffer[0] = (uint8_t)u16TargetsPresentMask;
+ pBusLogic->aReplyBuffer[1] = (uint8_t)(u16TargetsPresentMask >> 8);
+ pBusLogic->cbReplyParametersLeft = 2;
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD:
+ {
+ if (pBusLogic->aCommandBuffer[0] > sizeof(pBusLogic->aReplyBuffer))
+ {
+ Log(("Requested too much synch period inquiry (%u)!\n", pBusLogic->aCommandBuffer[0]));
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pBusLogic->cbReplyParametersLeft = pBusLogic->aCommandBuffer[0];
+ for (uint8_t i = 0; i < pBusLogic->cbReplyParametersLeft; i++)
+ pBusLogic->aReplyBuffer[i] = 0; /** @todo Figure if we need something other here. It's not needed for the linux driver */
+
+ break;
+ }
+ case BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT:
+ {
+ /* Not supported on AHA-154x HBAs. */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ if (pBusLogic->aCommandBuffer[0] == 0)
+ pBusLogic->fIRQEnabled = false;
+ else
+ pBusLogic->fIRQEnabled = true;
+ /* No interrupt signaled regardless of enable/disable. */
+ fSuppressIrq = true;
+ break;
+ }
+ case BUSLOGICCOMMAND_ECHO_COMMAND_DATA:
+ {
+ pBusLogic->aReplyBuffer[0] = pBusLogic->aCommandBuffer[0];
+ pBusLogic->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT:
+ {
+ uint8_t uEnable = pBusLogic->aCommandBuffer[0];
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ Log(("Enable OMBR: %u\n", uEnable));
+ /* Only 0/1 are accepted. */
+ if (uEnable > 1)
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ else
+ {
+ pBusLogic->LocalRam.structured.autoSCSIData.uReserved6 = uEnable;
+ fSuppressIrq = true;
+ }
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_PREEMPT_TIME_ON_BUS:
+ {
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->LocalRam.structured.autoSCSIData.uBusOnDelay = pBusLogic->aCommandBuffer[0];
+ Log(("Bus-on time: %d\n", pBusLogic->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_TIME_OFF_BUS:
+ {
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->LocalRam.structured.autoSCSIData.uBusOffDelay = pBusLogic->aCommandBuffer[0];
+ Log(("Bus-off time: %d\n", pBusLogic->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_BUS_TRANSFER_RATE:
+ {
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->LocalRam.structured.autoSCSIData.uDMATransferRate = pBusLogic->aCommandBuffer[0];
+ Log(("Bus transfer rate: %02X\n", pBusLogic->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO:
+ {
+ RTGCPHYS GCPhysFifoBuf;
+ Addr24 addr;
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ addr.hi = pBusLogic->aCommandBuffer[0];
+ addr.mid = pBusLogic->aCommandBuffer[1];
+ addr.lo = pBusLogic->aCommandBuffer[2];
+ GCPhysFifoBuf = (RTGCPHYS)ADDR_TO_U32(addr);
+ Log(("Write busmaster FIFO at: %04X\n", ADDR_TO_U32(addr)));
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysFifoBuf,
+ &pBusLogic->LocalRam.u8View[64], 64);
+ break;
+ }
+ case BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO:
+ {
+ RTGCPHYS GCPhysFifoBuf;
+ Addr24 addr;
+
+ pBusLogic->cbReplyParametersLeft = 0;
+ addr.hi = pBusLogic->aCommandBuffer[0];
+ addr.mid = pBusLogic->aCommandBuffer[1];
+ addr.lo = pBusLogic->aCommandBuffer[2];
+ GCPhysFifoBuf = (RTGCPHYS)ADDR_TO_U32(addr);
+ Log(("Read busmaster FIFO at: %04X\n", ADDR_TO_U32(addr)));
+ blPhysWrite(pBusLogic, GCPhysFifoBuf, &pBusLogic->LocalRam.u8View[64], 64);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid command %#x\n", pBusLogic->uOperationCode));
+ RT_FALL_THRU();
+ case BUSLOGICCOMMAND_EXT_BIOS_INFO:
+ case BUSLOGICCOMMAND_UNLOCK_MAILBOX:
+ /* Commands valid for Adaptec 154xC which we don't handle since
+ * we pretend being 154xB compatible. Just mark the command as invalid.
+ */
+ Log(("Command %#x not valid for this adapter\n", pBusLogic->uOperationCode));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ break;
+ case BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND: /* Should be handled already. */
+ AssertMsgFailed(("Invalid mailbox execute state!\n"));
+ }
+
+ Log(("uOperationCode=%#x, cbReplyParametersLeft=%d\n", pBusLogic->uOperationCode, pBusLogic->cbReplyParametersLeft));
+
+ /* Fail command if too much parameter data requested. */
+ if ((pBusLogic->cbCommandParametersLeft + pBusLogic->iParameter) > sizeof(pBusLogic->aCommandBuffer))
+ {
+ Log(("Invalid command parameter length (%u)\n", pBusLogic->cbCommandParametersLeft));
+ pBusLogic->cbReplyParametersLeft = 0;
+ pBusLogic->cbCommandParametersLeft = 0;
+ pBusLogic->regStatus |= BL_STAT_CMDINV;
+ }
+
+ /* Set the data in ready bit in the status register in case the command has a reply. */
+ if (pBusLogic->cbReplyParametersLeft)
+ pBusLogic->regStatus |= BL_STAT_DIRRDY;
+ else if (!pBusLogic->cbCommandParametersLeft)
+ buslogicCommandComplete(pBusLogic, fSuppressIrq);
+
+ return rc;
+}
+
+/**
+ * Read a register from the BusLogic adapter.
+ *
+ * @returns VBox status code.
+ * @param pBusLogic Pointer to the BusLogic instance data.
+ * @param iRegister The index of the register to read.
+ * @param pu32 Where to store the register content.
+ */
+static int buslogicRegisterRead(PBUSLOGIC pBusLogic, unsigned iRegister, uint32_t *pu32)
+{
+ static const char achAhaSig[] = "ADAP";
+ int rc = VINF_SUCCESS;
+
+ switch (iRegister)
+ {
+ case BUSLOGIC_REGISTER_STATUS:
+ {
+ *pu32 = pBusLogic->regStatus;
+
+ /* If the diagnostic active bit is set, we are in a guest-initiated
+ * hard reset. If the guest reads the status register and waits for
+ * the host adapter ready bit to be set, we terminate the reset right
+ * away. However, guests may also expect the reset condition to clear
+ * automatically after a period of time, in which case we can't show
+ * the DIAG bit at all.
+ */
+ if (pBusLogic->regStatus & BL_STAT_DACT)
+ {
+ uint64_t u64AccessTime = PDMDevHlpTMTimeVirtGetNano(pBusLogic->CTX_SUFF(pDevIns));
+
+ pBusLogic->regStatus &= ~BL_STAT_DACT;
+ pBusLogic->regStatus |= BL_STAT_HARDY;
+
+ if (u64AccessTime - pBusLogic->u64ResetTime > BUSLOGIC_RESET_DURATION_NS)
+ {
+ /* If reset already expired, let the guest see that right away. */
+ *pu32 = pBusLogic->regStatus;
+ pBusLogic->u64ResetTime = 0;
+ }
+ }
+ break;
+ }
+ case BUSLOGIC_REGISTER_DATAIN:
+ {
+ if (pBusLogic->fUseLocalRam)
+ *pu32 = pBusLogic->LocalRam.u8View[pBusLogic->iReply];
+ else
+ *pu32 = pBusLogic->aReplyBuffer[pBusLogic->iReply];
+
+ /* Careful about underflow - guest can read data register even if
+ * no data is available.
+ */
+ if (pBusLogic->cbReplyParametersLeft)
+ {
+ pBusLogic->iReply++;
+ pBusLogic->cbReplyParametersLeft--;
+ if (!pBusLogic->cbReplyParametersLeft)
+ {
+ /*
+ * Reply finished, set command complete bit, unset data-in ready bit and
+ * interrupt the guest if enabled.
+ * NB: Some commands do not set the CMDC bit / raise completion interrupt.
+ */
+ if (pBusLogic->uOperationCode == BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM)
+ buslogicCommandComplete(pBusLogic, true /* fSuppressIrq */);
+ else
+ buslogicCommandComplete(pBusLogic, false);
+ }
+ }
+ LogFlowFunc(("data=%02x, iReply=%d, cbReplyParametersLeft=%u\n", *pu32,
+ pBusLogic->iReply, pBusLogic->cbReplyParametersLeft));
+ break;
+ }
+ case BUSLOGIC_REGISTER_INTERRUPT:
+ {
+ *pu32 = pBusLogic->regInterrupt;
+ break;
+ }
+ case BUSLOGIC_REGISTER_GEOMETRY:
+ {
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ *pu32 = achAhaSig[pBusLogic->uAhaSigIdx];
+ pBusLogic->uAhaSigIdx = (pBusLogic->uAhaSigIdx + 1) & 3;
+ }
+ else
+ *pu32 = pBusLogic->regGeometry;
+ break;
+ }
+ default:
+ *pu32 = UINT32_C(0xffffffff);
+ }
+
+ Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+ __FUNCTION__, pu32, 1, pu32, iRegister, rc));
+
+ return rc;
+}
+
+/**
+ * Write a value to a register.
+ *
+ * @returns VBox status code.
+ * @param pBusLogic Pointer to the BusLogic instance data.
+ * @param iRegister The index of the register to read.
+ * @param uVal The value to write.
+ */
+static int buslogicRegisterWrite(PBUSLOGIC pBusLogic, unsigned iRegister, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (iRegister)
+ {
+ case BUSLOGIC_REGISTER_CONTROL:
+ {
+ if ((uVal & BL_CTRL_RHARD) || (uVal & BL_CTRL_RSOFT))
+ {
+#ifdef IN_RING3
+ bool fHardReset = !!(uVal & BL_CTRL_RHARD);
+
+ LogRel(("BusLogic: %s reset\n", fHardReset ? "hard" : "soft"));
+ buslogicR3InitiateReset(pBusLogic, fHardReset);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ }
+
+ rc = PDMCritSectEnter(&pBusLogic->CritSectIntr, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+#ifdef LOG_ENABLED
+ uint32_t cMailboxesReady = ASMAtomicXchgU32(&pBusLogic->cInMailboxesReady, 0);
+ Log(("%u incoming mailboxes were ready when this interrupt was cleared\n", cMailboxesReady));
+#endif
+
+ if (uVal & BL_CTRL_RINT)
+ buslogicClearInterrupt(pBusLogic);
+
+ PDMCritSectLeave(&pBusLogic->CritSectIntr);
+
+ break;
+ }
+ case BUSLOGIC_REGISTER_COMMAND:
+ {
+ /* Fast path for mailbox execution command. */
+ if ((uVal == BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND) && (pBusLogic->uOperationCode == 0xff))
+ {
+ /// @todo Should fail if BL_STAT_INREQ is set
+ /* If there are no mailboxes configured, don't even try to do anything. */
+ if (pBusLogic->cMailbox)
+ {
+ ASMAtomicIncU32(&pBusLogic->cMailboxesReady);
+ if (!ASMAtomicXchgBool(&pBusLogic->fNotificationSent, true))
+ {
+ /* Send new notification to the queue. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pBusLogic->CTX_SUFF(pNotifierQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pBusLogic->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+ }
+
+ return rc;
+ }
+
+ /*
+ * Check if we are already fetch command parameters from the guest.
+ * If not we initialize executing a new command.
+ */
+ if (pBusLogic->uOperationCode == 0xff)
+ {
+ pBusLogic->uOperationCode = uVal;
+ pBusLogic->iParameter = 0;
+
+ /* Mark host adapter as busy and clear the invalid status bit. */
+ pBusLogic->regStatus &= ~(BL_STAT_HARDY | BL_STAT_CMDINV);
+
+ /* Get the number of bytes for parameters from the command code. */
+ switch (pBusLogic->uOperationCode)
+ {
+ case BUSLOGICCOMMAND_TEST_CMDC_INTERRUPT:
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER:
+ case BUSLOGICCOMMAND_INQUIRE_BOARD_ID:
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER:
+ case BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION:
+ case BUSLOGICCOMMAND_INQUIRE_CONFIGURATION:
+ case BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_0_TO_7:
+ case BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_8_TO_15:
+ case BUSLOGICCOMMAND_INQUIRE_TARGET_DEVICES:
+ pBusLogic->cbCommandParametersLeft = 0;
+ break;
+ case BUSLOGICCOMMAND_MODIFY_IO_ADDRESS:
+ case BUSLOGICCOMMAND_INQUIRE_EXTENDED_SETUP_INFORMATION:
+ case BUSLOGICCOMMAND_DISABLE_HOST_ADAPTER_INTERRUPT:
+ case BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER:
+ /* These commands are not on AHA-154x, some Adaptec drivers (ASPI4DOS.SYS) test them. */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ pBusLogic->cbCommandParametersLeft = 0;
+ break;
+ }
+ RT_FALL_THRU();
+ case BUSLOGICCOMMAND_INQUIRE_SETUP_INFORMATION:
+ case BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE:
+ case BUSLOGICCOMMAND_SET_CCB_FORMAT:
+ case BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD:
+ case BUSLOGICCOMMAND_ECHO_COMMAND_DATA:
+ case BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT:
+ case BUSLOGICCOMMAND_SET_PREEMPT_TIME_ON_BUS:
+ case BUSLOGICCOMMAND_SET_TIME_OFF_BUS:
+ case BUSLOGICCOMMAND_SET_BUS_TRANSFER_RATE:
+ pBusLogic->cbCommandParametersLeft = 1;
+ break;
+ case BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM:
+ pBusLogic->cbCommandParametersLeft = 2;
+ break;
+ case BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO:
+ case BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO:
+ pBusLogic->cbCommandParametersLeft = 3;
+ break;
+ case BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT:
+ pBusLogic->cbCommandParametersLeft = 4;
+ break;
+ case BUSLOGICCOMMAND_INITIALIZE_MAILBOX:
+ pBusLogic->cbCommandParametersLeft = sizeof(RequestInitMbx);
+ break;
+ case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+ /* Some Adaptec drivers (ASPI4DOS.SYS) test this command. */
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ {
+ pBusLogic->cbCommandParametersLeft = 0;
+ break;
+ }
+ pBusLogic->cbCommandParametersLeft = sizeof(RequestInitializeExtendedMailbox);
+ break;
+ case BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS:
+ /* There must be at least one byte following this command. */
+ pBusLogic->cbCommandParametersLeft = 1;
+ break;
+ case BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND:
+ /* 12 bytes + variable-length CDB. */
+ pBusLogic->cbCommandParametersLeft = 12;
+ break;
+ case BUSLOGICCOMMAND_EXT_BIOS_INFO:
+ case BUSLOGICCOMMAND_UNLOCK_MAILBOX:
+ /* Invalid commands. */
+ pBusLogic->cbCommandParametersLeft = 0;
+ break;
+ case BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND: /* Should not come here anymore. */
+ default:
+ AssertMsgFailed(("Invalid operation code %#x\n", uVal));
+ }
+ }
+ else
+ {
+#ifndef IN_RING3
+ /* This command must be executed in R3 as it rehooks the ISA I/O port. */
+ if (pBusLogic->uOperationCode == BUSLOGICCOMMAND_MODIFY_IO_ADDRESS)
+ {
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ break;
+ }
+#endif
+ /*
+ * The real adapter would set the Command register busy bit in the status register.
+ * The guest has to wait until it is unset.
+ * We don't need to do it because the guest does not continue execution while we are in this
+ * function.
+ */
+ pBusLogic->aCommandBuffer[pBusLogic->iParameter] = uVal;
+ pBusLogic->iParameter++;
+ pBusLogic->cbCommandParametersLeft--;
+ }
+
+ /* Start execution of command if there are no parameters left. */
+ if (!pBusLogic->cbCommandParametersLeft)
+ {
+ rc = buslogicProcessCommand(pBusLogic);
+ AssertMsgRC(rc, ("Processing command failed rc=%Rrc\n", rc));
+ }
+ break;
+ }
+
+ /* On BusLogic adapters, the interrupt and geometry registers are R/W.
+ * That is different from Adaptec 154x where those are read only.
+ */
+ case BUSLOGIC_REGISTER_INTERRUPT:
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ break;
+ pBusLogic->regInterrupt = uVal;
+ break;
+
+ case BUSLOGIC_REGISTER_GEOMETRY:
+ if (pBusLogic->uDevType == DEV_AHA_1540B)
+ break;
+ pBusLogic->regGeometry = uVal;
+ break;
+
+ default:
+ AssertMsgFailed(("Register not available\n"));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+
+ return rc;
+}
+
+/**
+ * Memory mapped I/O Handler for read operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param GCPhysAddr Physical address (in GC) where the read starts.
+ * @param pv Where to store the result.
+ * @param cb Number of bytes read.
+ */
+PDMBOTHCBDECL(int) buslogicMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ RT_NOREF_PV(pDevIns); RT_NOREF_PV(pvUser); RT_NOREF_PV(GCPhysAddr); RT_NOREF_PV(pv); RT_NOREF_PV(cb);
+
+ /* the linux driver does not make use of the MMIO area. */
+ AssertMsgFailed(("MMIO Read\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Memory mapped I/O Handler for write operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param GCPhysAddr Physical address (in GC) where the read starts.
+ * @param pv Where to fetch the result.
+ * @param cb Number of bytes to write.
+ */
+PDMBOTHCBDECL(int) buslogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ RT_NOREF_PV(pDevIns); RT_NOREF_PV(pvUser); RT_NOREF_PV(GCPhysAddr); RT_NOREF_PV(pv); RT_NOREF_PV(cb);
+
+ /* the linux driver does not make use of the MMIO area. */
+ AssertMsgFailed(("MMIO Write\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for IN operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param pu32 Where to store the result.
+ * @param cb Number of bytes read.
+ */
+PDMBOTHCBDECL(int) buslogicIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32, unsigned cb)
+{
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ unsigned iRegister = uPort % 4;
+ RT_NOREF_PV(pvUser); RT_NOREF_PV(cb);
+
+ Assert(cb == 1);
+
+ return buslogicRegisterRead(pBusLogic, iRegister, pu32);
+}
+
+/**
+ * Port I/O Handler for OUT operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param u32 The value to output.
+ * @param cb The value size in bytes.
+ */
+PDMBOTHCBDECL(int) buslogicIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32, unsigned cb)
+{
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ unsigned iRegister = uPort % 4;
+ uint8_t uVal = (uint8_t)u32;
+ RT_NOREF2(pvUser, cb);
+
+ Assert(cb == 1);
+
+ int rc = buslogicRegisterWrite(pBusLogic, iRegister, (uint8_t)uVal);
+
+ Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x uPort=%#x rc=%Rrc\n",
+ pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, uPort, rc));
+
+ return rc;
+}
+
+#ifdef IN_RING3
+
+static int buslogicR3PrepareBIOSSCSIRequest(PBUSLOGIC pThis)
+{
+ uint32_t uTargetDevice;
+ uint32_t uLun;
+ uint8_t *pbCdb;
+ size_t cbCdb;
+ size_t cbBuf;
+
+ int rc = vboxscsiSetupRequest(&pThis->VBoxSCSI, &uLun, &pbCdb, &cbCdb, &cbBuf, &uTargetDevice);
+ AssertMsgRCReturn(rc, ("Setting up SCSI request failed rc=%Rrc\n", rc), rc);
+
+ if ( uTargetDevice < RT_ELEMENTS(pThis->aDeviceStates)
+ && pThis->aDeviceStates[uTargetDevice].pDrvBase)
+ {
+ PBUSLOGICDEVICE pTgtDev = &pThis->aDeviceStates[uTargetDevice];
+ PDMMEDIAEXIOREQ hIoReq;
+ PBUSLOGICREQ pReq;
+
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqAlloc(pTgtDev->pDrvMediaEx, &hIoReq, (void **)&pReq,
+ 0, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+
+ pReq->fBIOS = true;
+ pReq->hIoReq = hIoReq;
+ pReq->pTargetDevice = pTgtDev;
+
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqSendScsiCmd(pTgtDev->pDrvMediaEx, pReq->hIoReq, uLun,
+ pbCdb, cbCdb, PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN,
+ cbBuf, NULL, 0, &pReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc == VINF_SUCCESS || rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ {
+ uint8_t u8ScsiSts = pReq->u8ScsiSts;
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, u8ScsiSts);
+ }
+ else if (rc == VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ return rc;
+ }
+
+ /* Device is not present. */
+ AssertMsg(pbCdb[0] == SCSI_INQUIRY,
+ ("Device is not present but command is not inquiry\n"));
+
+ SCSIINQUIRYDATA ScsiInquiryData;
+
+ memset(&ScsiInquiryData, 0, sizeof(SCSIINQUIRYDATA));
+ ScsiInquiryData.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+ ScsiInquiryData.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+
+ memcpy(pThis->VBoxSCSI.pbBuf, &ScsiInquiryData, 5);
+
+ rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, SCSI_STATUS_OK);
+ AssertMsgRCReturn(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc), rc);
+
+ return rc;
+}
+
+
+/**
+ * Port I/O Handler for IN operations - BIOS port.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param pu32 Where to store the result.
+ * @param cb Number of bytes read.
+ */
+static DECLCALLBACK(int) buslogicR3BiosIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser, cb);
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ Assert(cb == 1);
+
+ int rc = vboxscsiReadRegister(&pBusLogic->VBoxSCSI, (uPort - BUSLOGIC_BIOS_IO_PORT), pu32);
+
+ //Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+ // __FUNCTION__, pu32, 1, pu32, (uPort - BUSLOGIC_BIOS_IO_PORT), rc));
+
+ return rc;
+}
+
+/**
+ * Port I/O Handler for OUT operations - BIOS port.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param u32 The value to output.
+ * @param cb The value size in bytes.
+ */
+static DECLCALLBACK(int) buslogicR3BiosIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pvUser, cb);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x uPort=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, uPort));
+
+ /*
+ * If there is already a request form the BIOS pending ignore this write
+ * because it should not happen.
+ */
+ if (ASMAtomicReadBool(&pThis->fBiosReqPending))
+ return VINF_SUCCESS;
+
+ Assert(cb == 1);
+
+ int rc = vboxscsiWriteRegister(&pThis->VBoxSCSI, (uPort - BUSLOGIC_BIOS_IO_PORT), (uint8_t)u32);
+ if (rc == VERR_MORE_DATA)
+ {
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, true);
+ /* Send a notifier to the PDM queue that there are pending requests. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotifierQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pThis->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for primary port range OUT string operations.
+ * @see FNIOMIOPORTOUTSTRING for details.
+ */
+static DECLCALLBACK(int) buslogicR3BiosIoPortWriteStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port,
+ uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pvUser);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ Log2(("#%d %s: pvUser=%#p cb=%d Port=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+ /*
+ * If there is already a request form the BIOS pending ignore this write
+ * because it should not happen.
+ */
+ if (ASMAtomicReadBool(&pThis->fBiosReqPending))
+ return VINF_SUCCESS;
+
+ int rc = vboxscsiWriteString(pDevIns, &pThis->VBoxSCSI, (Port - BUSLOGIC_BIOS_IO_PORT), pbSrc, pcTransfers, cb);
+ if (rc == VERR_MORE_DATA)
+ {
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, true);
+ /* Send a notifier to the PDM queue that there are pending requests. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotifierQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pThis->CTX_SUFF(pNotifierQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+ else if (RT_FAILURE(rc))
+ AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Port I/O Handler for primary port range IN string operations.
+ * @see FNIOMIOPORTINSTRING for details.
+ */
+static DECLCALLBACK(int) buslogicR3BiosIoPortReadStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port,
+ uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pvUser);
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ LogFlowFunc(("#%d %s: pvUser=%#p cb=%d Port=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+ return vboxscsiReadString(pDevIns, &pBusLogic->VBoxSCSI, (Port - BUSLOGIC_BIOS_IO_PORT),
+ pbDst, pcTransfers, cb);
+}
+
+/**
+ * Update the ISA I/O range.
+ *
+ * @returns nothing.
+ * @param pBusLogic Pointer to the BusLogic device instance.
+ * @param uBaseCode Encoded ISA I/O base; only low 3 bits are used.
+ */
+static int buslogicR3RegisterISARange(PBUSLOGIC pBusLogic, uint8_t uBaseCode)
+{
+ uint8_t uCode = uBaseCode & MAX_ISA_BASE;
+ uint16_t uNewBase = g_aISABases[uCode];
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("ISA I/O code %02X, new base %X\n", uBaseCode, uNewBase));
+
+ /* Check if the same port range is already registered. */
+ if (uNewBase != pBusLogic->IOISABase)
+ {
+ /* Unregister the old range, if any. */
+ if (pBusLogic->IOISABase)
+ rc = PDMDevHlpIOPortDeregister(pBusLogic->CTX_SUFF(pDevIns), pBusLogic->IOISABase, 4);
+
+ if (RT_SUCCESS(rc))
+ {
+ pBusLogic->IOISABase = 0; /* First mark as unregistered. */
+ pBusLogic->uISABaseCode = ISA_BASE_DISABLED;
+
+ if (uNewBase)
+ {
+ /* Register the new range if requested. */
+ rc = PDMDevHlpIOPortRegister(pBusLogic->CTX_SUFF(pDevIns), uNewBase, 4, NULL,
+ buslogicIOPortWrite, buslogicIOPortRead,
+ NULL, NULL,
+ "BusLogic ISA");
+ if (RT_SUCCESS(rc))
+ {
+ pBusLogic->IOISABase = uNewBase;
+ pBusLogic->uISABaseCode = uCode;
+ }
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (uNewBase)
+ {
+ Log(("ISA I/O base: %x\n", uNewBase));
+ LogRel(("BusLogic: ISA I/O base: %x\n", uNewBase));
+ }
+ else
+ {
+ Log(("Disabling ISA I/O ports.\n"));
+ LogRel(("BusLogic: ISA I/O disabled\n"));
+ }
+ }
+
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) buslogicR3MmioMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(pPciDev, iRegion);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ int rc = VINF_SUCCESS;
+
+ Log2(("%s: registering MMIO area at GCPhysAddr=%RGp cb=%RGp\n", __FUNCTION__, GCPhysAddress, cb));
+
+ Assert(cb >= 32);
+
+ if (enmType == PCI_ADDRESS_SPACE_MEM)
+ {
+ /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+ rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ buslogicMMIOWrite, buslogicMMIORead, "BusLogic MMIO");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/,
+ "buslogicMMIOWrite", "buslogicMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/,
+ "buslogicMMIOWrite", "buslogicMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->MMIOBase = GCPhysAddress;
+ }
+ else if (enmType == PCI_ADDRESS_SPACE_IO)
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+ NULL, buslogicIOPortWrite, buslogicIOPortRead, NULL, NULL, "BusLogic PCI");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+ 0, "buslogicIOPortWrite", "buslogicIOPortRead", NULL, NULL, "BusLogic PCI");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, (RTIOPORT)GCPhysAddress, 32,
+ 0, "buslogicIOPortWrite", "buslogicIOPortRead", NULL, NULL, "BusLogic PCI");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->IOPortBase = (RTIOPORT)GCPhysAddress;
+ }
+ else
+ AssertMsgFailed(("Invalid enmType=%d\n", enmType));
+
+ return rc;
+}
+
+static int buslogicR3ReqComplete(PBUSLOGIC pThis, PBUSLOGICREQ pReq, int rcReq)
+{
+ RT_NOREF(rcReq);
+ PBUSLOGICDEVICE pTgtDev = pReq->pTargetDevice;
+
+ LogFlowFunc(("before decrement %u\n", pTgtDev->cOutstandingRequests));
+ ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+ LogFlowFunc(("after decrement %u\n", pTgtDev->cOutstandingRequests));
+
+ if (pReq->fBIOS)
+ {
+ uint8_t u8ScsiSts = pReq->u8ScsiSts;
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ int rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, u8ScsiSts);
+ AssertMsgRC(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc));
+ }
+ else
+ {
+ if (pReq->pbSenseBuffer)
+ buslogicR3SenseBufferFree(pReq, (pReq->u8ScsiSts != SCSI_STATUS_OK));
+
+ /* Update residual data length. */
+ if ( (pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_DATA_LENGTH)
+ || (pReq->CCBGuest.c.uOpcode == BUSLOGIC_CCB_OPCODE_INITIATOR_CCB_RESIDUAL_SCATTER_GATHER))
+ {
+ size_t cbResidual = 0;
+ int rc = pTgtDev->pDrvMediaEx->pfnIoReqQueryResidual(pTgtDev->pDrvMediaEx, pReq->hIoReq, &cbResidual);
+ AssertRC(rc); Assert(cbResidual == (uint32_t)cbResidual);
+
+ if (pReq->fIs24Bit)
+ U32_TO_LEN(pReq->CCBGuest.o.acbData, (uint32_t)cbResidual);
+ else
+ pReq->CCBGuest.n.cbData = (uint32_t)cbResidual;
+ }
+
+ /*
+ * Save vital things from the request and free it before posting completion
+ * to avoid that the guest submits a new request with the same ID as the still
+ * allocated one.
+ */
+#ifdef LOG_ENABLED
+ bool fIs24Bit = pReq->fIs24Bit;
+#endif
+ uint8_t u8ScsiSts = pReq->u8ScsiSts;
+ RTGCPHYS GCPhysAddrCCB = pReq->GCPhysAddrCCB;
+ CCBU CCBGuest;
+ memcpy(&CCBGuest, &pReq->CCBGuest, sizeof(CCBU));
+
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ if (u8ScsiSts == SCSI_STATUS_OK)
+ buslogicR3SendIncomingMailbox(pThis, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_CMD_COMPLETED,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITHOUT_ERROR);
+ else if (u8ScsiSts == SCSI_STATUS_CHECK_CONDITION)
+ buslogicR3SendIncomingMailbox(pThis, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_CMD_COMPLETED,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_CHECK_CONDITION,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+ else
+ AssertMsgFailed(("invalid completion status %u\n", u8ScsiSts));
+
+#ifdef LOG_ENABLED
+ buslogicR3DumpCCBInfo(&CCBGuest, fIs24Bit);
+#endif
+ }
+
+ if (pTgtDev->cOutstandingRequests == 0 && pThis->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pThis->pDevInsR3);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) buslogicR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PBUSLOGICDEVICE pBusLogicDevice = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaPort);
+ PPDMDEVINS pDevIns = pBusLogicDevice->CTX_SUFF(pBusLogic)->CTX_SUFF(pDevIns);
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pBusLogicDevice->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) buslogicR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ PBUSLOGICREQ pReq = (PBUSLOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = 0;
+ if (RT_UNLIKELY(pReq->fBIOS))
+ cbCopied = vboxscsiCopyToBuf(&pTgtDev->CTX_SUFF(pBusLogic)->VBoxSCSI, pSgBuf, offDst, cbCopy);
+ else
+ cbCopied = buslogicR3CopySgBufToGuest(pTgtDev->CTX_SUFF(pBusLogic), pReq, pSgBuf, offDst, cbCopy);
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) buslogicR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ PBUSLOGICREQ pReq = (PBUSLOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = 0;
+ if (RT_UNLIKELY(pReq->fBIOS))
+ cbCopied = vboxscsiCopyFromBuf(&pTgtDev->CTX_SUFF(pBusLogic)->VBoxSCSI, pSgBuf, offSrc, cbCopy);
+ else
+ cbCopied = buslogicR3CopySgBufFromGuest(pTgtDev->CTX_SUFF(pBusLogic), pReq, pSgBuf, offSrc, cbCopy);
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) buslogicR3IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF(hIoReq);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ buslogicR3ReqComplete(pTgtDev->CTX_SUFF(pBusLogic), (PBUSLOGICREQ)pvIoReqAlloc, rcReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) buslogicR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF3(hIoReq, pvIoReqAlloc, enmState);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ /* Make sure the request is not accounted for so the VM can suspend successfully. */
+ uint32_t cTasksActive = ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+ if (!cTasksActive && pTgtDev->CTX_SUFF(pBusLogic)->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pTgtDev->CTX_SUFF(pBusLogic)->pDevInsR3);
+ break;
+ }
+ case PDMMEDIAEXIOREQSTATE_ACTIVE:
+ /* Make sure the request is accounted for so the VM suspends only when the request is complete. */
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+ break;
+ default:
+ AssertMsgFailed(("Invalid request state given %u\n", enmState));
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) buslogicR3MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ PBUSLOGIC pThis = pTgtDev->CTX_SUFF(pBusLogic);
+
+ if (pThis->pMediaNotify)
+ {
+ int rc = VMR3ReqCallNoWait(PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns)), VMCPUID_ANY,
+ (PFNRT)pThis->pMediaNotify->pfnEjected, 2,
+ pThis->pMediaNotify, pTgtDev->iLUN);
+ AssertRC(rc);
+ }
+}
+
+static int buslogicR3DeviceSCSIRequestSetup(PBUSLOGIC pBusLogic, RTGCPHYS GCPhysAddrCCB)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t uTargetIdCCB;
+ CCBU CCBGuest;
+
+ /* Fetch the CCB from guest memory. */
+ /** @todo How much do we really have to read? */
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrCCB,
+ &CCBGuest, sizeof(CCB32));
+
+ uTargetIdCCB = pBusLogic->fMbxIs24Bit ? CCBGuest.o.uTargetId : CCBGuest.n.uTargetId;
+ if (RT_LIKELY(uTargetIdCCB < RT_ELEMENTS(pBusLogic->aDeviceStates)))
+ {
+ PBUSLOGICDEVICE pTgtDev = &pBusLogic->aDeviceStates[uTargetIdCCB];
+
+#ifdef LOG_ENABLED
+ buslogicR3DumpCCBInfo(&CCBGuest, pBusLogic->fMbxIs24Bit);
+#endif
+
+ /* Check if device is present on bus. If not return error immediately and don't process this further. */
+ if (RT_LIKELY(pTgtDev->fPresent))
+ {
+ PDMMEDIAEXIOREQ hIoReq;
+ PBUSLOGICREQ pReq;
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqAlloc(pTgtDev->pDrvMediaEx, &hIoReq, (void **)&pReq,
+ GCPhysAddrCCB, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->pTargetDevice = pTgtDev;
+ pReq->GCPhysAddrCCB = GCPhysAddrCCB;
+ pReq->fBIOS = false;
+ pReq->hIoReq = hIoReq;
+ pReq->fIs24Bit = pBusLogic->fMbxIs24Bit;
+
+ /* Make a copy of the CCB */
+ memcpy(&pReq->CCBGuest, &CCBGuest, sizeof(CCBGuest));
+
+ /* Alloc required buffers. */
+ rc = buslogicR3SenseBufferAlloc(pReq);
+ AssertMsgRC(rc, ("Mapping sense buffer failed rc=%Rrc\n", rc));
+
+ size_t cbBuf = 0;
+ rc = buslogicR3QueryDataBufferSize(pBusLogic->CTX_SUFF(pDevIns), &pReq->CCBGuest, pReq->fIs24Bit, &cbBuf);
+ AssertRC(rc);
+
+ uint32_t uLun = pReq->fIs24Bit ? pReq->CCBGuest.o.uLogicalUnit
+ : pReq->CCBGuest.n.uLogicalUnit;
+
+ PDMMEDIAEXIOREQSCSITXDIR enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN;
+ size_t cbSense = buslogicR3ConvertSenseBufferLength(CCBGuest.c.cbSenseData);
+
+ if (CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_NO_DATA)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_NONE;
+ else if (CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_OUT)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE;
+ else if (CCBGuest.c.uDataDirection == BUSLOGIC_CCB_DIRECTION_IN)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqSendScsiCmd(pTgtDev->pDrvMediaEx, pReq->hIoReq, uLun,
+ &pReq->CCBGuest.c.abCDB[0], pReq->CCBGuest.c.cbCDB,
+ enmXferDir, cbBuf, pReq->pbSenseBuffer, cbSense,
+ &pReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ buslogicR3ReqComplete(pBusLogic, pReq, rc);
+ }
+ else
+ buslogicR3SendIncomingMailbox(pBusLogic, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_SELECTION_TIMEOUT,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+ }
+ else
+ buslogicR3SendIncomingMailbox(pBusLogic, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_SCSI_SELECTION_TIMEOUT,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+ }
+ else
+ buslogicR3SendIncomingMailbox(pBusLogic, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_PARAMETER,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+
+ return rc;
+}
+
+static int buslogicR3DeviceSCSIRequestAbort(PBUSLOGIC pBusLogic, RTGCPHYS GCPhysAddrCCB)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t uTargetIdCCB;
+ CCBU CCBGuest;
+
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCPhysAddrCCB,
+ &CCBGuest, sizeof(CCB32));
+
+ uTargetIdCCB = pBusLogic->fMbxIs24Bit ? CCBGuest.o.uTargetId : CCBGuest.n.uTargetId;
+ if (RT_LIKELY(uTargetIdCCB < RT_ELEMENTS(pBusLogic->aDeviceStates)))
+ buslogicR3SendIncomingMailbox(pBusLogic, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_ABORT_QUEUE_GENERATED,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_ABORTED_NOT_FOUND);
+ else
+ buslogicR3SendIncomingMailbox(pBusLogic, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_PARAMETER,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+
+ return rc;
+}
+
+/**
+ * Read a mailbox from guest memory. Convert 24-bit mailboxes to
+ * 32-bit format.
+ *
+ * @returns Mailbox guest physical address.
+ * @param pBusLogic Pointer to the BusLogic instance data.
+ * @param pMbx Pointer to the mailbox to read into.
+ */
+static RTGCPHYS buslogicR3ReadOutgoingMailbox(PBUSLOGIC pBusLogic, PMailbox32 pMbx)
+{
+ RTGCPHYS GCMailbox;
+
+ if (pBusLogic->fMbxIs24Bit)
+ {
+ Mailbox24 Mbx24;
+
+ GCMailbox = pBusLogic->GCPhysAddrMailboxOutgoingBase + (pBusLogic->uMailboxOutgoingPositionCurrent * sizeof(Mailbox24));
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCMailbox, &Mbx24, sizeof(Mailbox24));
+ pMbx->u32PhysAddrCCB = ADDR_TO_U32(Mbx24.aPhysAddrCCB);
+ pMbx->u.out.uActionCode = Mbx24.uCmdState;
+ }
+ else
+ {
+ GCMailbox = pBusLogic->GCPhysAddrMailboxOutgoingBase + (pBusLogic->uMailboxOutgoingPositionCurrent * sizeof(Mailbox32));
+ PDMDevHlpPhysRead(pBusLogic->CTX_SUFF(pDevIns), GCMailbox, pMbx, sizeof(Mailbox32));
+ }
+
+ return GCMailbox;
+}
+
+/**
+ * Read mailbox from the guest and execute command.
+ *
+ * @returns VBox status code.
+ * @param pBusLogic Pointer to the BusLogic instance data.
+ */
+static int buslogicR3ProcessMailboxNext(PBUSLOGIC pBusLogic)
+{
+ RTGCPHYS GCPhysAddrMailboxCurrent;
+ Mailbox32 MailboxGuest;
+ int rc = VINF_SUCCESS;
+
+ if (!pBusLogic->fStrictRoundRobinMode)
+ {
+ /* Search for a filled mailbox - stop if we have scanned all mailboxes. */
+ uint8_t uMailboxPosCur = pBusLogic->uMailboxOutgoingPositionCurrent;
+
+ do
+ {
+ /* Fetch mailbox from guest memory. */
+ GCPhysAddrMailboxCurrent = buslogicR3ReadOutgoingMailbox(pBusLogic, &MailboxGuest);
+
+ /* Check the next mailbox. */
+ buslogicR3OutgoingMailboxAdvance(pBusLogic);
+ } while ( MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE
+ && uMailboxPosCur != pBusLogic->uMailboxOutgoingPositionCurrent);
+ }
+ else
+ {
+ /* Fetch mailbox from guest memory. */
+ GCPhysAddrMailboxCurrent = buslogicR3ReadOutgoingMailbox(pBusLogic, &MailboxGuest);
+ }
+
+ /*
+ * Check if the mailbox is actually loaded.
+ * It might be possible that the guest notified us without
+ * a loaded mailbox. Do nothing in that case but leave a
+ * log entry.
+ */
+ if (MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE)
+ {
+ Log(("No loaded mailbox left\n"));
+ return VERR_NO_DATA;
+ }
+
+ LogFlow(("Got loaded mailbox at slot %u, CCB phys %RGp\n", pBusLogic->uMailboxOutgoingPositionCurrent, (RTGCPHYS)MailboxGuest.u32PhysAddrCCB));
+#ifdef LOG_ENABLED
+ buslogicR3DumpMailboxInfo(&MailboxGuest, true);
+#endif
+
+ /* We got the mailbox, mark it as free in the guest. */
+ uint8_t uActionCode = BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE;
+ unsigned uCodeOffs = pBusLogic->fMbxIs24Bit ? RT_OFFSETOF(Mailbox24, uCmdState) : RT_OFFSETOF(Mailbox32, u.out.uActionCode);
+ blPhysWrite(pBusLogic, GCPhysAddrMailboxCurrent + uCodeOffs, &uActionCode, sizeof(uActionCode));
+
+ if (MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_START_COMMAND)
+ rc = buslogicR3DeviceSCSIRequestSetup(pBusLogic, (RTGCPHYS)MailboxGuest.u32PhysAddrCCB);
+ else if (MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_ABORT_COMMAND)
+ {
+ LogFlow(("Aborting mailbox\n"));
+ rc = buslogicR3DeviceSCSIRequestAbort(pBusLogic, (RTGCPHYS)MailboxGuest.u32PhysAddrCCB);
+ }
+ else
+ AssertMsgFailed(("Invalid outgoing mailbox action code %u\n", MailboxGuest.u.out.uActionCode));
+
+ AssertRC(rc);
+
+ /* Advance to the next mailbox. */
+ if (pBusLogic->fStrictRoundRobinMode)
+ buslogicR3OutgoingMailboxAdvance(pBusLogic);
+
+ return rc;
+}
+
+/**
+ * Transmit queue consumer
+ * Queue a new async task.
+ *
+ * @returns Success indicator.
+ * If false the item will not be removed and the flushing will stop.
+ * @param pDevIns The device instance.
+ * @param pItem The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) buslogicR3NotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+ RT_NOREF(pItem);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ int rc = SUPSemEventSignal(pThis->pSupDrvSession, pThis->hEvtProcess);
+ AssertRC(rc);
+
+ return true;
+}
+
+/** @callback_method_impl{FNSSMDEVLIVEEXEC} */
+static DECLCALLBACK(int) buslogicR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ /* Save the device config. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+ SSMR3PutBool(pSSM, pThis->aDeviceStates[i].fPresent);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/** @callback_method_impl{FNSSMDEVSAVEEXEC} */
+static DECLCALLBACK(int) buslogicR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ uint32_t cReqsSuspended = 0;
+
+ /* Every device first. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ SSMR3PutBool(pSSM, pDevice->fPresent);
+ SSMR3PutU32(pSSM, pDevice->cOutstandingRequests);
+
+ if (pDevice->fPresent)
+ cReqsSuspended += pDevice->pDrvMediaEx->pfnIoReqGetSuspendedCount(pDevice->pDrvMediaEx);
+ }
+ /* Now the main device state. */
+ SSMR3PutU8 (pSSM, pBusLogic->regStatus);
+ SSMR3PutU8 (pSSM, pBusLogic->regInterrupt);
+ SSMR3PutU8 (pSSM, pBusLogic->regGeometry);
+ SSMR3PutMem (pSSM, &pBusLogic->LocalRam, sizeof(pBusLogic->LocalRam));
+ SSMR3PutU8 (pSSM, pBusLogic->uOperationCode);
+ SSMR3PutMem (pSSM, &pBusLogic->aCommandBuffer, sizeof(pBusLogic->aCommandBuffer));
+ SSMR3PutU8 (pSSM, pBusLogic->iParameter);
+ SSMR3PutU8 (pSSM, pBusLogic->cbCommandParametersLeft);
+ SSMR3PutBool (pSSM, pBusLogic->fUseLocalRam);
+ SSMR3PutMem (pSSM, pBusLogic->aReplyBuffer, sizeof(pBusLogic->aReplyBuffer));
+ SSMR3PutU8 (pSSM, pBusLogic->iReply);
+ SSMR3PutU8 (pSSM, pBusLogic->cbReplyParametersLeft);
+ SSMR3PutBool (pSSM, pBusLogic->fIRQEnabled);
+ SSMR3PutU8 (pSSM, pBusLogic->uISABaseCode);
+ SSMR3PutU32 (pSSM, pBusLogic->cMailbox);
+ SSMR3PutBool (pSSM, pBusLogic->fMbxIs24Bit);
+ SSMR3PutGCPhys(pSSM, pBusLogic->GCPhysAddrMailboxOutgoingBase);
+ SSMR3PutU32 (pSSM, pBusLogic->uMailboxOutgoingPositionCurrent);
+ SSMR3PutU32 (pSSM, pBusLogic->cMailboxesReady);
+ SSMR3PutBool (pSSM, pBusLogic->fNotificationSent);
+ SSMR3PutGCPhys(pSSM, pBusLogic->GCPhysAddrMailboxIncomingBase);
+ SSMR3PutU32 (pSSM, pBusLogic->uMailboxIncomingPositionCurrent);
+ SSMR3PutBool (pSSM, pBusLogic->fStrictRoundRobinMode);
+ SSMR3PutBool (pSSM, pBusLogic->fExtendedLunCCBFormat);
+
+ vboxscsiR3SaveExec(&pBusLogic->VBoxSCSI, pSSM);
+
+ SSMR3PutU32(pSSM, cReqsSuspended);
+
+ /* Save the physical CCB address of all suspended requests. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates) && cReqsSuspended; i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+ if (pDevice->fPresent)
+ {
+ uint32_t cThisReqsSuspended = pDevice->pDrvMediaEx->pfnIoReqGetSuspendedCount(pDevice->pDrvMediaEx);
+
+ cReqsSuspended -= cThisReqsSuspended;
+ if (cThisReqsSuspended)
+ {
+ PDMMEDIAEXIOREQ hIoReq;
+ PBUSLOGICREQ pReq;
+ int rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedStart(pDevice->pDrvMediaEx, &hIoReq,
+ (void **)&pReq);
+ AssertRCBreak(rc);
+
+ for (;;)
+ {
+ SSMR3PutU32(pSSM, (uint32_t)pReq->GCPhysAddrCCB);
+
+ cThisReqsSuspended--;
+ if (!cThisReqsSuspended)
+ break;
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pDevice->pDrvMediaEx, hIoReq,
+ &hIoReq, (void **)&pReq);
+ AssertRCBreak(rc);
+ }
+ }
+ }
+ }
+
+ return SSMR3PutU32(pSSM, UINT32_MAX);
+}
+
+/** @callback_method_impl{FNSSMDEVLOADDONE} */
+static DECLCALLBACK(int) buslogicR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ buslogicR3RegisterISARange(pThis, pThis->uISABaseCode);
+
+ /* Kick of any requests we might need to redo. */
+ if (pThis->VBoxSCSI.fBusy)
+ {
+
+ /* The BIOS had a request active when we got suspended. Resume it. */
+ int rc = buslogicR3PrepareBIOSSCSIRequest(pThis);
+ AssertRC(rc);
+ }
+ else if (pThis->cReqsRedo)
+ {
+ for (unsigned i = 0; i < pThis->cReqsRedo; i++)
+ {
+ int rc = buslogicR3DeviceSCSIRequestSetup(pThis, pThis->paGCPhysAddrCCBRedo[i]);
+ AssertRC(rc);
+ }
+
+ RTMemFree(pThis->paGCPhysAddrCCBRedo);
+ pThis->paGCPhysAddrCCBRedo = NULL;
+ pThis->cReqsRedo = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @callback_method_impl{FNSSMDEVLOADEXEC} */
+static DECLCALLBACK(int) buslogicR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PBUSLOGIC pBusLogic = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ int rc = VINF_SUCCESS;
+
+ /* We support saved states only from this and older versions. */
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Every device first. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pBusLogic->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pBusLogic->aDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ bool fPresent;
+ rc = SSMR3GetBool(pSSM, &fPresent);
+ AssertRCReturn(rc, rc);
+ if (pDevice->fPresent != fPresent)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Target %u config mismatch: config=%RTbool state=%RTbool"), i, pDevice->fPresent, fPresent);
+
+ if (uPass == SSM_PASS_FINAL)
+ SSMR3GetU32(pSSM, (uint32_t *)&pDevice->cOutstandingRequests);
+ }
+
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ /* Now the main device state. */
+ SSMR3GetU8 (pSSM, (uint8_t *)&pBusLogic->regStatus);
+ SSMR3GetU8 (pSSM, (uint8_t *)&pBusLogic->regInterrupt);
+ SSMR3GetU8 (pSSM, (uint8_t *)&pBusLogic->regGeometry);
+ SSMR3GetMem (pSSM, &pBusLogic->LocalRam, sizeof(pBusLogic->LocalRam));
+ SSMR3GetU8 (pSSM, &pBusLogic->uOperationCode);
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_PRE_CMDBUF_RESIZE)
+ SSMR3GetMem (pSSM, &pBusLogic->aCommandBuffer, sizeof(pBusLogic->aCommandBuffer));
+ else
+ SSMR3GetMem (pSSM, &pBusLogic->aCommandBuffer, BUSLOGIC_COMMAND_SIZE_OLD);
+ SSMR3GetU8 (pSSM, &pBusLogic->iParameter);
+ SSMR3GetU8 (pSSM, &pBusLogic->cbCommandParametersLeft);
+ SSMR3GetBool (pSSM, &pBusLogic->fUseLocalRam);
+ SSMR3GetMem (pSSM, pBusLogic->aReplyBuffer, sizeof(pBusLogic->aReplyBuffer));
+ SSMR3GetU8 (pSSM, &pBusLogic->iReply);
+ SSMR3GetU8 (pSSM, &pBusLogic->cbReplyParametersLeft);
+ SSMR3GetBool (pSSM, &pBusLogic->fIRQEnabled);
+ SSMR3GetU8 (pSSM, &pBusLogic->uISABaseCode);
+ SSMR3GetU32 (pSSM, &pBusLogic->cMailbox);
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_PRE_24BIT_MBOX)
+ SSMR3GetBool (pSSM, &pBusLogic->fMbxIs24Bit);
+ SSMR3GetGCPhys(pSSM, &pBusLogic->GCPhysAddrMailboxOutgoingBase);
+ SSMR3GetU32 (pSSM, &pBusLogic->uMailboxOutgoingPositionCurrent);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pBusLogic->cMailboxesReady);
+ SSMR3GetBool (pSSM, (bool *)&pBusLogic->fNotificationSent);
+ SSMR3GetGCPhys(pSSM, &pBusLogic->GCPhysAddrMailboxIncomingBase);
+ SSMR3GetU32 (pSSM, &pBusLogic->uMailboxIncomingPositionCurrent);
+ SSMR3GetBool (pSSM, &pBusLogic->fStrictRoundRobinMode);
+ SSMR3GetBool (pSSM, &pBusLogic->fExtendedLunCCBFormat);
+
+ rc = vboxscsiR3LoadExec(&pBusLogic->VBoxSCSI, pSSM);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("BusLogic: Failed to restore BIOS state: %Rrc.\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic: Failed to restore BIOS state\n"));
+ }
+
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_PRE_ERROR_HANDLING)
+ {
+ /* Check if there are pending tasks saved. */
+ uint32_t cTasks = 0;
+
+ SSMR3GetU32(pSSM, &cTasks);
+
+ if (cTasks)
+ {
+ pBusLogic->paGCPhysAddrCCBRedo = (PRTGCPHYS)RTMemAllocZ(cTasks * sizeof(RTGCPHYS));
+ if (RT_LIKELY(pBusLogic->paGCPhysAddrCCBRedo))
+ {
+ pBusLogic->cReqsRedo = cTasks;
+
+ for (uint32_t i = 0; i < cTasks; i++)
+ {
+ uint32_t u32PhysAddrCCB;
+
+ rc = SSMR3GetU32(pSSM, &u32PhysAddrCCB);
+ if (RT_FAILURE(rc))
+ break;
+
+ pBusLogic->paGCPhysAddrCCBRedo[i] = u32PhysAddrCCB;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t u32;
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_SUCCESS(rc))
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return rc;
+}
+
+/**
+ * Gets the pointer to the status LED of a device - called from the SCSI driver.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire. Always 0 here as the driver
+ * doesn't know about other LUN's.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) buslogicR3DeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PBUSLOGICDEVICE pDevice = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, ILed);
+ if (iLUN == 0)
+ {
+ *ppLed = &pDevice->Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) buslogicR3DeviceQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PBUSLOGICDEVICE pDevice = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDevice->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pDevice->IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pDevice->IMediaExPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pDevice->ILed);
+ return NULL;
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) buslogicR3StatusQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PBUSLOGIC pBusLogic = RT_FROM_MEMBER(pInterface, BUSLOGIC, ILeds);
+ if (iLUN < BUSLOGIC_MAX_DEVICES)
+ {
+ *ppLed = &pBusLogic->aDeviceStates[iLUN].Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) buslogicR3StatusQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PBUSLOGIC pThis = RT_FROM_MEMBER(pInterface, BUSLOGIC, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->ILeds);
+ return NULL;
+}
+
+/**
+ * The worker thread processing requests from the guest.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThread The thread structure.
+ */
+static DECLCALLBACK(int) buslogicR3Worker(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pDevIns);
+ PBUSLOGIC pThis = (PBUSLOGIC)pThread->pvUser;
+ int rc = VINF_SUCCESS;
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, true);
+ bool fNotificationSent = ASMAtomicXchgBool(&pThis->fNotificationSent, false);
+ if (!fNotificationSent)
+ {
+ Assert(ASMAtomicReadBool(&pThis->fWrkThreadSleeping));
+ rc = SUPSemEventWaitNoResume(pThis->pSupDrvSession, pThis->hEvtProcess, RT_INDEFINITE_WAIT);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+ LogFlowFunc(("Woken up with rc=%Rrc\n", rc));
+ ASMAtomicWriteBool(&pThis->fNotificationSent, false);
+ }
+
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, false);
+
+ /* Check whether there is a BIOS request pending and process it first. */
+ if (ASMAtomicReadBool(&pThis->fBiosReqPending))
+ {
+ rc = buslogicR3PrepareBIOSSCSIRequest(pThis);
+ AssertRC(rc);
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, false);
+ }
+ else
+ {
+ ASMAtomicXchgU32(&pThis->cMailboxesReady, 0); /** @todo Actually not required anymore but to stay compatible with older saved states. */
+
+ /* Process mailboxes. */
+ do
+ {
+ rc = buslogicR3ProcessMailboxNext(pThis);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_NO_DATA, ("Processing mailbox failed rc=%Rrc\n", rc));
+ } while (RT_SUCCESS(rc));
+ }
+ } /* While running */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the worker thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) buslogicR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ return SUPSemEventSignal(pThis->pSupDrvSession, pThis->hEvtProcess);
+}
+
+/**
+ * BusLogic debugger info callback.
+ *
+ * @param pDevIns The device instance.
+ * @param pHlp The output helpers.
+ * @param pszArgs The arguments.
+ */
+static DECLCALLBACK(void) buslogicR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ static const char *apszModels[] = { "BusLogic BT-958D", "BusLogic BT-545C", "Adaptec AHA-1540B" };
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ unsigned i;
+ bool fVerbose = false;
+
+ /* Parse arguments. */
+ if (pszArgs)
+ fVerbose = strstr(pszArgs, "verbose") != NULL;
+
+ /* Show basic information. */
+ pHlp->pfnPrintf(pHlp, "%s#%d: %s ",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance,
+ pThis->uDevType >= RT_ELEMENTS(apszModels) ? "Uknown model" : apszModels[pThis->uDevType]);
+ if (pThis->uIsaIrq)
+ pHlp->pfnPrintf(pHlp, "ISA I/O=%RTiop IRQ=%u ",
+ pThis->IOISABase,
+ pThis->uIsaIrq);
+ else
+ pHlp->pfnPrintf(pHlp, "PCI I/O=%RTiop ISA I/O=%RTiop MMIO=%RGp IRQ=%u ",
+ pThis->IOPortBase, pThis->IOISABase, pThis->MMIOBase,
+ PCIDevGetInterruptLine(&pThis->dev));
+ pHlp->pfnPrintf(pHlp, "GC=%RTbool R0=%RTbool\n",
+ !!pThis->fGCEnabled, !!pThis->fR0Enabled);
+
+ /* Print mailbox state. */
+ if (pThis->regStatus & BL_STAT_INREQ)
+ pHlp->pfnPrintf(pHlp, "Mailbox not initialized\n");
+ else
+ pHlp->pfnPrintf(pHlp, "%u-bit mailbox with %u entries at %RGp (%d LUN CCBs)\n",
+ pThis->fMbxIs24Bit ? 24 : 32, pThis->cMailbox,
+ pThis->GCPhysAddrMailboxOutgoingBase,
+ pThis->fMbxIs24Bit ? 8 : pThis->fExtendedLunCCBFormat ? 64 : 8);
+
+ /* Print register contents. */
+ pHlp->pfnPrintf(pHlp, "Registers: STAT=%02x INTR=%02x GEOM=%02x\n",
+ pThis->regStatus, pThis->regInterrupt, pThis->regGeometry);
+
+ /* Print miscellaneous state. */
+ pHlp->pfnPrintf(pHlp, "HAC interrupts: %s\n",
+ pThis->fIRQEnabled ? "on" : "off");
+
+ /* Print the current command, if any. */
+ if (pThis->uOperationCode != 0xff )
+ pHlp->pfnPrintf(pHlp, "Current command: %02X\n", pThis->uOperationCode);
+
+ if (fVerbose && (pThis->regStatus & BL_STAT_INREQ) == 0)
+ {
+ RTGCPHYS GCMailbox;
+
+ /* Dump the mailbox contents. */
+ if (pThis->fMbxIs24Bit)
+ {
+ Mailbox24 Mbx24;
+
+ /* Outgoing mailbox, 24-bit format. */
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase;
+ pHlp->pfnPrintf(pHlp, " Outgoing mailbox entries (24-bit) at %06X:\n", GCMailbox);
+ for (i = 0; i < pThis->cMailbox; ++i)
+ {
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCMailbox, &Mbx24, sizeof(Mailbox24));
+ pHlp->pfnPrintf(pHlp, " slot %03d: CCB at %06X action code %02X", i, ADDR_TO_U32(Mbx24.aPhysAddrCCB), Mbx24.uCmdState);
+ pHlp->pfnPrintf(pHlp, "%s\n", pThis->uMailboxOutgoingPositionCurrent == i ? " *" : "");
+ GCMailbox += sizeof(Mailbox24);
+ }
+
+ /* Incoming mailbox, 24-bit format. */
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase + (pThis->cMailbox * sizeof(Mailbox24));
+ pHlp->pfnPrintf(pHlp, " Incoming mailbox entries (24-bit) at %06X:\n", GCMailbox);
+ for (i = 0; i < pThis->cMailbox; ++i)
+ {
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCMailbox, &Mbx24, sizeof(Mailbox24));
+ pHlp->pfnPrintf(pHlp, " slot %03d: CCB at %06X completion code %02X", i, ADDR_TO_U32(Mbx24.aPhysAddrCCB), Mbx24.uCmdState);
+ pHlp->pfnPrintf(pHlp, "%s\n", pThis->uMailboxIncomingPositionCurrent == i ? " *" : "");
+ GCMailbox += sizeof(Mailbox24);
+ }
+
+ }
+ else
+ {
+ Mailbox32 Mbx32;
+
+ /* Outgoing mailbox, 32-bit format. */
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase;
+ pHlp->pfnPrintf(pHlp, " Outgoing mailbox entries (32-bit) at %08X:\n", (uint32_t)GCMailbox);
+ for (i = 0; i < pThis->cMailbox; ++i)
+ {
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCMailbox, &Mbx32, sizeof(Mailbox32));
+ pHlp->pfnPrintf(pHlp, " slot %03d: CCB at %08X action code %02X", i, Mbx32.u32PhysAddrCCB, Mbx32.u.out.uActionCode);
+ pHlp->pfnPrintf(pHlp, "%s\n", pThis->uMailboxOutgoingPositionCurrent == i ? " *" : "");
+ GCMailbox += sizeof(Mailbox32);
+ }
+
+ /* Incoming mailbox, 32-bit format. */
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase + (pThis->cMailbox * sizeof(Mailbox32));
+ pHlp->pfnPrintf(pHlp, " Outgoing mailbox entries (32-bit) at %08X:\n", (uint32_t)GCMailbox);
+ for (i = 0; i < pThis->cMailbox; ++i)
+ {
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCMailbox, &Mbx32, sizeof(Mailbox32));
+ pHlp->pfnPrintf(pHlp, " slot %03d: CCB at %08X completion code %02X BTSTAT %02X SDSTAT %02X", i,
+ Mbx32.u32PhysAddrCCB, Mbx32.u.in.uCompletionCode, Mbx32.u.in.uHostAdapterStatus, Mbx32.u.in.uTargetDeviceStatus);
+ pHlp->pfnPrintf(pHlp, "%s\n", pThis->uMailboxOutgoingPositionCurrent == i ? " *" : "");
+ GCMailbox += sizeof(Mailbox32);
+ }
+
+ }
+ }
+}
+
+/* -=-=-=-=- Helper -=-=-=-=- */
+
+ /**
+ * Checks if all asynchronous I/O is finished.
+ *
+ * Used by buslogicR3Reset, buslogicR3Suspend and buslogicR3PowerOff.
+ *
+ * @returns true if quiesced, false if busy.
+ * @param pDevIns The device instance.
+ */
+static bool buslogicR3AllAsyncIOIsFinished(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pThisDevice = &pThis->aDeviceStates[i];
+ if (pThisDevice->pDrvBase)
+ {
+ if (pThisDevice->cOutstandingRequests != 0)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Callback employed by buslogicR3Suspend and buslogicR3PowerOff.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) buslogicR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns)
+{
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for buslogicR3Suspend and buslogicR3PowerOff.
+ */
+static void buslogicR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, buslogicR3IsAsyncSuspendOrPowerOffDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ AssertMsg(!pThis->fNotificationSent, ("The PDM Queue should be empty at this point\n"));
+ }
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pThisDevice = &pThis->aDeviceStates[i];
+ if (pThisDevice->pDrvMediaEx)
+ pThisDevice->pDrvMediaEx->pfnNotifySuspend(pThisDevice->pDrvMediaEx);
+ }
+}
+
+/**
+ * Suspend notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) buslogicR3Suspend(PPDMDEVINS pDevIns)
+{
+ Log(("buslogicR3Suspend\n"));
+ buslogicR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * Detach notification.
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) buslogicR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+
+ Log(("%s:\n", __FUNCTION__));
+
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("BusLogic: Device does not support hotplugging\n"));
+
+ /*
+ * Zero some important members.
+ */
+ pDevice->fPresent = false;
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+}
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int) buslogicR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[iLUN];
+ int rc;
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("BusLogic: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /* the usual paranoia */
+ AssertRelease(!pDevice->pDrvBase);
+ AssertRelease(!pDevice->pDrvMedia);
+ AssertRelease(!pDevice->pDrvMediaEx);
+ Assert(pDevice->iLUN == iLUN);
+
+ /*
+ * Try attach the SCSI driver and get the interfaces,
+ * required as well as optional.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMedia),
+ ("BusLogic configuration error: LUN#%d misses the basic media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pDevice->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMediaEx),
+ ("BusLogic configuration error: LUN#%d misses the extended media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqAllocSizeSet(pDevice->pDrvMediaEx, sizeof(BUSLOGICREQ));
+ AssertMsgRCReturn(rc, ("BusLogic configuration error: LUN#%u: Failed to set I/O request size!", pDevice->iLUN),
+ rc);
+
+ pDevice->fPresent = true;
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pDevice->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pDevice->fPresent = false;
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+ }
+ return rc;
+}
+
+/**
+ * Callback employed by buslogicR3Reset.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) buslogicR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ buslogicR3HwReset(pThis, true);
+ return true;
+}
+
+/**
+ * @copydoc FNPDMDEVRESET
+ */
+static DECLCALLBACK(void) buslogicR3Reset(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, buslogicR3IsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ buslogicR3HwReset(pThis, true);
+ }
+}
+
+static DECLCALLBACK(void) buslogicR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ RT_NOREF(offDelta);
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->pNotifierQueueRC = PDMQueueRCPtr(pThis->pNotifierQueueR3);
+
+ for (uint32_t i = 0; i < BUSLOGIC_MAX_DEVICES; i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[i];
+
+ pDevice->pBusLogicRC = PDMINS_2_DATA_RCPTR(pDevIns);
+ }
+
+}
+
+/**
+ * Poweroff notification.
+ *
+ * @param pDevIns Pointer to the device instance
+ */
+static DECLCALLBACK(void) buslogicR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("buslogicR3PowerOff\n"));
+ buslogicR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) buslogicR3Destruct(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ PDMR3CritSectDelete(&pThis->CritSectIntr);
+
+ if (pThis->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ SUPSemEventClose(pThis->pSupDrvSession, pThis->hEvtProcess);
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) buslogicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PBUSLOGIC pThis = PDMINS_2_DATA(pDevIns, PBUSLOGIC);
+ int rc = VINF_SUCCESS;
+ bool fBootable = true;
+ char achCfgStr[16];
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Init instance data (do early because of constructor).
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->IBase.pfnQueryInterface = buslogicR3StatusQueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = buslogicR3StatusQueryStatusLed;
+
+ PCIDevSetVendorId (&pThis->dev, 0x104b); /* BusLogic */
+ PCIDevSetDeviceId (&pThis->dev, 0x1040); /* BT-958 */
+ PCIDevSetCommand (&pThis->dev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS);
+ PCIDevSetRevisionId (&pThis->dev, 0x01);
+ PCIDevSetClassProg (&pThis->dev, 0x00); /* SCSI */
+ PCIDevSetClassSub (&pThis->dev, 0x00); /* SCSI */
+ PCIDevSetClassBase (&pThis->dev, 0x01); /* Mass storage */
+ PCIDevSetBaseAddress (&pThis->dev, 0, true /*IO*/, false /*Pref*/, false /*64-bit*/, 0x00000000);
+ PCIDevSetBaseAddress (&pThis->dev, 1, false /*IO*/, false /*Pref*/, false /*64-bit*/, 0x00000000);
+ PCIDevSetSubSystemVendorId(&pThis->dev, 0x104b);
+ PCIDevSetSubSystemId (&pThis->dev, 0x1040);
+ PCIDevSetInterruptLine (&pThis->dev, 0x00);
+ PCIDevSetInterruptPin (&pThis->dev, 0x01);
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg,
+ "GCEnabled\0"
+ "R0Enabled\0"
+ "Bootable\0"
+ "AdapterType\0"
+ "ISACompat\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("BusLogic configuration error: unknown option specified"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &pThis->fGCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic configuration error: failed to read GCEnabled as boolean"));
+ Log(("%s: fGCEnabled=%d\n", __FUNCTION__, pThis->fGCEnabled));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &pThis->fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic configuration error: failed to read R0Enabled as boolean"));
+ Log(("%s: fR0Enabled=%d\n", __FUNCTION__, pThis->fR0Enabled));
+ rc = CFGMR3QueryBoolDef(pCfg, "Bootable", &fBootable, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic configuration error: failed to read Bootable as boolean"));
+ Log(("%s: fBootable=%RTbool\n", __FUNCTION__, fBootable));
+
+ /* Figure out the emulated device type. */
+ rc = CFGMR3QueryStringDef(pCfg, "AdapterType", achCfgStr, sizeof(achCfgStr), "BT-958D");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic configuration error: failed to read AdapterType as string"));
+ Log(("%s: AdapterType=%s\n", __FUNCTION__, achCfgStr));
+
+ /* Grok the AdapterType setting. */
+ if (!strcmp(achCfgStr, "BT-958D")) /* Default PCI device, 32-bit and 24-bit addressing. */
+ {
+ pThis->uDevType = DEV_BT_958D;
+ pThis->uDefaultISABaseCode = ISA_BASE_DISABLED;
+ }
+ else if (!strcmp(achCfgStr, "BT-545C")) /* ISA device, 24-bit addressing only. */
+ {
+ pThis->uDevType = DEV_BT_545C;
+ pThis->uIsaIrq = 11;
+ }
+ else if (!strcmp(achCfgStr, "AHA-1540B")) /* Competitor ISA device. */
+ {
+ pThis->uDevType = DEV_AHA_1540B;
+ pThis->uIsaIrq = 11;
+ }
+ else
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("BusLogic configuration error: invalid AdapterType setting"));
+
+ /* Only the first instance defaults to having the ISA compatibility ports enabled. */
+ if (iInstance == 0)
+ rc = CFGMR3QueryStringDef(pCfg, "ISACompat", achCfgStr, sizeof(achCfgStr), "Alternate");
+ else
+ rc = CFGMR3QueryStringDef(pCfg, "ISACompat", achCfgStr, sizeof(achCfgStr), "Disabled");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("BusLogic configuration error: failed to read ISACompat as string"));
+ Log(("%s: ISACompat=%s\n", __FUNCTION__, achCfgStr));
+
+ /* Grok the ISACompat setting. */
+ if (!strcmp(achCfgStr, "Disabled"))
+ pThis->uDefaultISABaseCode = ISA_BASE_DISABLED;
+ else if (!strcmp(achCfgStr, "Primary"))
+ pThis->uDefaultISABaseCode = 0; /* I/O base at 330h. */
+ else if (!strcmp(achCfgStr, "Alternate"))
+ pThis->uDefaultISABaseCode = 1; /* I/O base at 334h. */
+ else
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("BusLogic configuration error: invalid ISACompat setting"));
+
+ /*
+ * Register the PCI device and its I/O regions if applicable.
+ */
+ if (!pThis->uIsaIrq)
+ {
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->dev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 32, PCI_ADDRESS_SPACE_IO, buslogicR3MmioMap);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 32, PCI_ADDRESS_SPACE_MEM, buslogicR3MmioMap);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (fBootable)
+ {
+ /* Register I/O port space for BIOS access. */
+ rc = PDMDevHlpIOPortRegister(pDevIns, BUSLOGIC_BIOS_IO_PORT, 4, NULL,
+ buslogicR3BiosIoPortWrite, buslogicR3BiosIoPortRead,
+ buslogicR3BiosIoPortWriteStr, buslogicR3BiosIoPortReadStr,
+ "BusLogic BIOS");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register BIOS I/O handlers"));
+ }
+
+ /* Set up the compatibility I/O range. */
+ rc = buslogicR3RegisterISARange(pThis, pThis->uDefaultISABaseCode);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register ISA I/O handlers"));
+
+ /* Initialize task queue. */
+ rc = PDMDevHlpQueueCreate(pDevIns, sizeof(PDMQUEUEITEMCORE), 5, 0,
+ buslogicR3NotifyQueueConsumer, true, "BusLogicTask", &pThis->pNotifierQueueR3);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->pNotifierQueueR0 = PDMQueueR0Ptr(pThis->pNotifierQueueR3);
+ pThis->pNotifierQueueRC = PDMQueueRCPtr(pThis->pNotifierQueueR3);
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSectIntr, RT_SRC_POS, "BusLogic-Intr#%u", pDevIns->iInstance);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic: cannot create critical section"));
+
+ /*
+ * Create event semaphore and worker thread.
+ */
+ rc = SUPSemEventCreate(pThis->pSupDrvSession, &pThis->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("BusLogic: Failed to create SUP event semaphore"));
+
+ char szDevTag[20];
+ RTStrPrintf(szDevTag, sizeof(szDevTag), "BUSLOGIC-%u", iInstance);
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThis->pThreadWrk, pThis, buslogicR3Worker,
+ buslogicR3WorkerWakeUp, 0, RTTHREADTYPE_IO, szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("BusLogic: Failed to create worker thread %s"), szDevTag);
+
+ /* Initialize per device state. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aDeviceStates); i++)
+ {
+ char szName[24];
+ PBUSLOGICDEVICE pDevice = &pThis->aDeviceStates[i];
+
+ char *pszName;
+ if (RTStrAPrintf(&pszName, "Device%u", i) < 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ /* Initialize static parts of the device. */
+ pDevice->iLUN = i;
+ pDevice->pBusLogicR3 = pThis;
+ pDevice->pBusLogicR0 = PDMINS_2_DATA_R0PTR(pDevIns);
+ pDevice->pBusLogicRC = PDMINS_2_DATA_RCPTR(pDevIns);
+ pDevice->Led.u32Magic = PDMLED_MAGIC;
+ pDevice->IBase.pfnQueryInterface = buslogicR3DeviceQueryInterface;
+ pDevice->IMediaPort.pfnQueryDeviceLocation = buslogicR3QueryDeviceLocation;
+ pDevice->IMediaExPort.pfnIoReqCompleteNotify = buslogicR3IoReqCompleteNotify;
+ pDevice->IMediaExPort.pfnIoReqCopyFromBuf = buslogicR3IoReqCopyFromBuf;
+ pDevice->IMediaExPort.pfnIoReqCopyToBuf = buslogicR3IoReqCopyToBuf;
+ pDevice->IMediaExPort.pfnIoReqQueryBuf = NULL;
+ pDevice->IMediaExPort.pfnIoReqQueryDiscardRanges = NULL;
+ pDevice->IMediaExPort.pfnIoReqStateChanged = buslogicR3IoReqStateChanged;
+ pDevice->IMediaExPort.pfnMediumEjected = buslogicR3MediumEjected;
+ pDevice->ILed.pfnQueryStatusLed = buslogicR3DeviceQueryStatusLed;
+
+ /* Attach SCSI driver. */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, pszName);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMedia),
+ ("Buslogic configuration error: LUN#%d misses the basic media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pDevice->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMediaEx),
+ ("Buslogic configuration error: LUN#%d misses the extended media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqAllocSizeSet(pDevice->pDrvMediaEx, sizeof(BUSLOGICREQ));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("Buslogic configuration error: LUN#%u: Failed to set I/O request size!"),
+ pDevice->iLUN);
+
+ pDevice->fPresent = true;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pDevice->fPresent = false;
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+ rc = VINF_SUCCESS;
+ Log(("BusLogic: no driver attached to device %s\n", szName));
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("BusLogic: Failed to attach %s\n", szName));
+ return rc;
+ }
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThis->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot attach to status driver"));
+ }
+
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, BUSLOGIC_SAVED_STATE_MINOR_VERSION, sizeof(*pThis), NULL,
+ NULL, buslogicR3LiveExec, NULL,
+ NULL, buslogicR3SaveExec, NULL,
+ NULL, buslogicR3LoadExec, buslogicR3LoadDone);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register save state handlers"));
+
+ /*
+ * Register the debugger info callback.
+ */
+ char szTmp[128];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%s%d", pDevIns->pReg->szName, pDevIns->iInstance);
+ PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "BusLogic HBA info", buslogicR3Info);
+
+ rc = buslogicR3HwReset(pThis, true);
+ AssertMsgRC(rc, ("hardware reset of BusLogic host adapter failed rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceBusLogic =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "buslogic",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "BusLogic BT-958 SCSI host adapter.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION |
+ PDM_DEVREG_FLAGS_FIRST_RESET_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(BUSLOGIC),
+ /* pfnConstruct */
+ buslogicR3Construct,
+ /* pfnDestruct */
+ buslogicR3Destruct,
+ /* pfnRelocate */
+ buslogicR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ buslogicR3Reset,
+ /* pfnSuspend */
+ buslogicR3Suspend,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ buslogicR3Attach,
+ /* pfnDetach */
+ buslogicR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ buslogicR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Storage/DevFdc.cpp b/src/VBox/Devices/Storage/DevFdc.cpp
new file mode 100644
index 00000000..84215f86
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevFdc.cpp
@@ -0,0 +1,3114 @@
+/* $Id: DevFdc.cpp $ */
+/** @file
+ * VBox storage devices - Floppy disk controller
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * QEMU Floppy disk emulator (Intel 82078)
+ *
+ * Copyright (c) 2003 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_FDC
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#define FDC_SAVESTATE_CURRENT 2 /* The new and improved saved state. */
+#define FDC_SAVESTATE_OLD 1 /* The original saved state. */
+
+#define MAX_FD 2
+
+
+/********************************************************/
+/* debug Floppy devices */
+/* #define DEBUG_FLOPPY */
+
+#ifndef VBOX
+ #ifdef DEBUG_FLOPPY
+ #define FLOPPY_DPRINTF(fmt, args...) \
+ do { printf("FLOPPY: " fmt , ##args); } while (0)
+ #endif
+#else /* !VBOX */
+# ifdef LOG_ENABLED
+# define FLOPPY_DPRINTF(...) Log(("floppy: " __VA_ARGS__))
+# else
+# define FLOPPY_DPRINTF(...) do { } while (0)
+# endif
+#endif /* !VBOX */
+
+#ifndef VBOX
+#define FLOPPY_ERROR(fmt, args...) \
+ do { printf("FLOPPY ERROR: %s: " fmt, __func__ , ##args); } while (0)
+#else /* VBOX */
+# define FLOPPY_ERROR RTLogPrintf
+#endif /* VBOX */
+
+#ifdef VBOX
+typedef struct fdctrl_t fdctrl_t;
+#endif /* VBOX */
+
+/********************************************************/
+/* Floppy drive emulation */
+
+#define GET_CUR_DRV(fdctrl) ((fdctrl)->cur_drv)
+#define SET_CUR_DRV(fdctrl, drive) ((fdctrl)->cur_drv = (drive))
+
+/* Will always be a fixed parameter for us */
+#define FD_SECTOR_LEN 512
+#define FD_SECTOR_SC 2 /* Sector size code */
+#define FD_RESET_SENSEI_COUNT 4 /* Number of sense interrupts on RESET */
+
+/* Floppy disk drive emulation */
+typedef enum fdisk_type_t {
+ FDRIVE_DISK_288 = 0x01, /* 2.88 MB disk */
+ FDRIVE_DISK_144 = 0x02, /* 1.44 MB disk */
+ FDRIVE_DISK_720 = 0x03, /* 720 kB disk */
+ FDRIVE_DISK_USER = 0x04, /* User defined geometry */
+ FDRIVE_DISK_NONE = 0x05 /* No disk */
+} fdisk_type_t;
+
+typedef enum fdrive_type_t {
+ FDRIVE_DRV_144 = 0x00, /* 1.44 MB 3"5 drive */
+ FDRIVE_DRV_288 = 0x01, /* 2.88 MB 3"5 drive */
+ FDRIVE_DRV_120 = 0x02, /* 1.2 MB 5"25 drive */
+ FDRIVE_DRV_NONE = 0x03 /* No drive connected */
+#ifdef VBOX
+ , FDRIVE_DRV_FAKE_15_6 = 0x0e /* Fake 15.6 MB drive. */
+ , FDRIVE_DRV_FAKE_63_5 = 0x0f /* Fake 63.5 MB drive. */
+#endif
+} fdrive_type_t;
+
+typedef uint8_t fdrive_flags_t;
+#define FDISK_DBL_SIDES UINT8_C(0x01)
+
+typedef enum fdrive_rate_t {
+ FDRIVE_RATE_500K = 0x00, /* 500 Kbps */
+ FDRIVE_RATE_300K = 0x01, /* 300 Kbps */
+ FDRIVE_RATE_250K = 0x02, /* 250 Kbps */
+ FDRIVE_RATE_1M = 0x03 /* 1 Mbps */
+} fdrive_rate_t;
+
+/**
+ * The status for one drive.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIMEDIAPORT
+ * @implements PDMIMOUNTNOTIFY
+ */
+typedef struct fdrive_t {
+#ifndef VBOX
+ BlockDriverState *bs;
+#else /* VBOX */
+ /** Pointer to the owning device instance. */
+ R3PTRTYPE(PPDMDEVINS) pDevIns;
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** The LUN #. */
+ RTUINT iLUN;
+ /** The LED for this LUN. */
+ PDMLED Led;
+#endif
+ /* Drive status */
+ fdrive_type_t drive;
+ uint8_t perpendicular; /* 2.88 MB access mode */
+ uint8_t dsk_chg; /* Disk change line */
+ /* Position */
+ uint8_t head;
+ uint8_t track;
+ uint8_t sect;
+ uint8_t ltrk; /* Logical track */
+ /* Media */
+ fdrive_flags_t flags;
+ uint8_t last_sect; /* Nb sector per track */
+ uint8_t max_track; /* Nb of tracks */
+ uint16_t bps; /* Bytes per sector */
+ uint8_t ro; /* Is read-only */
+ uint8_t media_rate; /* Data rate of medium */
+} fdrive_t;
+
+#define NUM_SIDES(drv) (drv->flags & FDISK_DBL_SIDES ? 2 : 1)
+
+static void fd_init(fdrive_t *drv, bool fInit)
+{
+ /* Drive */
+#ifndef VBOX
+ drv->drive = FDRIVE_DRV_NONE;
+#else /* VBOX */
+ if (fInit) {
+ /* Fixate the drive type at init time if possible. */
+ if (drv->pDrvMedia) {
+ PDMMEDIATYPE enmType = drv->pDrvMedia->pfnGetType(drv->pDrvMedia);
+ switch (enmType) {
+ case PDMMEDIATYPE_FLOPPY_360:
+ case PDMMEDIATYPE_FLOPPY_1_20:
+ drv->drive = FDRIVE_DRV_120;
+ break;
+ case PDMMEDIATYPE_FLOPPY_720:
+ case PDMMEDIATYPE_FLOPPY_1_44:
+ drv->drive = FDRIVE_DRV_144;
+ break;
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_2_88:
+ drv->drive = FDRIVE_DRV_288;
+ break;
+ case PDMMEDIATYPE_FLOPPY_FAKE_15_6:
+ drv->drive = FDRIVE_DRV_FAKE_15_6;
+ break;
+ case PDMMEDIATYPE_FLOPPY_FAKE_63_5:
+ drv->drive = FDRIVE_DRV_FAKE_63_5;
+ break;
+ }
+ } else {
+ drv->drive = FDRIVE_DRV_NONE;
+ }
+ } /* else: The BIOS (and others) get the drive type via the CMOS, so
+ don't change it after the VM has been constructed. */
+#endif /* VBOX */
+ drv->perpendicular = 0;
+ /* Disk */
+ drv->last_sect = 0;
+ drv->max_track = 0;
+}
+
+static int fd_sector_calc(uint8_t head, uint8_t track, uint8_t sect,
+ uint8_t last_sect, uint8_t num_sides)
+{
+ return (((track * num_sides) + head) * last_sect) + sect - 1; /* sect >= 1 */
+}
+
+/* Returns current position, in sectors, for given drive */
+static int fd_sector(fdrive_t *drv)
+{
+ return fd_sector_calc(drv->head, drv->track, drv->sect, drv->last_sect, NUM_SIDES(drv));
+}
+
+/* Seek to a new position:
+ * returns 0 if already on right track
+ * returns 1 if track changed
+ * returns 2 if track is invalid
+ * returns 3 if sector is invalid
+ * returns 4 if seek is disabled
+ * returns 5 if no media in drive
+ */
+static int fd_seek(fdrive_t *drv, uint8_t head, uint8_t track, uint8_t sect,
+ int enable_seek)
+{
+ int sector;
+ int ret;
+
+ if (!drv->last_sect) {
+ FLOPPY_DPRINTF("no disk in drive (max=%d %d %02x %02x)\n",
+ 1, (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 5;
+ }
+ if (track > drv->max_track ||
+ (head != 0 && (drv->flags & FDISK_DBL_SIDES) == 0)) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 2;
+ }
+ if (sect > drv->last_sect || sect < 1) {
+ FLOPPY_DPRINTF("try to read %d %02x %02x (max=%d %d %02x %02x)\n",
+ head, track, sect, 1,
+ (drv->flags & FDISK_DBL_SIDES) == 0 ? 0 : 1,
+ drv->max_track, drv->last_sect);
+ return 3;
+ }
+ sector = fd_sector_calc(head, track, sect, drv->last_sect, NUM_SIDES(drv));
+ ret = 0;
+ if (sector != fd_sector(drv)) {
+#if 0
+ if (!enable_seek) {
+ FLOPPY_ERROR("no implicit seek %d %02x %02x (max=%d %02x %02x)\n",
+ head, track, sect, 1, drv->max_track, drv->last_sect);
+ return 4;
+ }
+#else
+ RT_NOREF(enable_seek);
+#endif
+ drv->head = head;
+ if (drv->track != track)
+ ret = 1;
+ drv->track = track;
+ drv->sect = sect;
+ }
+ drv->ltrk = drv->track;
+
+ return ret;
+}
+
+/* Set drive back to track 0 */
+static void fd_recalibrate(fdrive_t *drv)
+{
+ FLOPPY_DPRINTF("recalibrate\n");
+ drv->head = 0;
+ drv->track = 0;
+ drv->ltrk = 0;
+ drv->sect = 1;
+}
+
+/* Recognize floppy formats */
+typedef struct fd_format_t {
+ fdrive_type_t drive;
+ fdisk_type_t disk;
+ uint8_t last_sect; /**< Number of sectors. */
+ uint8_t max_track; /**< Number of tracks. */
+ uint8_t max_head; /**< Max head number. */
+ fdrive_rate_t rate;
+ const char *str;
+} fd_format_t;
+
+/* Note: Low-density disks (160K/180K/320K/360K) use 250 Kbps data rate
+ * in 40-track drives, but 300 Kbps in high-capacity 80-track drives.
+ */
+static fd_format_t fd_formats[] = {
+ /* First entry is default format */
+ /* 1.44 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB 3\"1/2", },
+ /* 2.88 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB 3\"1/2", },
+ { FDRIVE_DRV_288, FDRIVE_DISK_288, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB 3\"1/2", },
+ /* 720 kB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 80, 1, FDRIVE_RATE_250K, "720 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 80, 1, FDRIVE_RATE_250K, "800 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 82, 1, FDRIVE_RATE_250K, "820 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 83, 1, FDRIVE_RATE_250K, "830 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB 3\"1/2", },
+ /* 1.2 MB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 15, 80, 1, FDRIVE_RATE_500K, "1.2 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 16, 80, 1, FDRIVE_RATE_500K, "1.28 MB 5\"1/4", }, /* CP Backup 5.25" HD */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 82, 1, FDRIVE_RATE_500K, "1.48 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 83, 1, FDRIVE_RATE_500K, "1.49 MB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB 5\"1/4", },
+ /* 720 kB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 80, 1, FDRIVE_RATE_250K, "720 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 11, 80, 1, FDRIVE_RATE_250K, "880 kB 5\"1/4", },
+ /* 360 kB 5"1/4 floppy disks (newer 9-sector formats) */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 1, FDRIVE_RATE_300K, "360 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 0, FDRIVE_RATE_300K, "180 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 40, 1, FDRIVE_RATE_300K, "400 kB 5\"1/4", }, /* CP Backup 5.25" DD */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 41, 1, FDRIVE_RATE_300K, "410 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 42, 1, FDRIVE_RATE_300K, "420 kB 5\"1/4", },
+ /* 320 kB 5"1/4 floppy disks (old 8-sector formats) */
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 1, FDRIVE_RATE_300K, "320 kB 5\"1/4", },
+ { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 0, FDRIVE_RATE_300K, "160 kB 5\"1/4", },
+ /* 1.2 MB and low density 3"1/2 floppy 'aliases' */
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 15, 80, 1, FDRIVE_RATE_500K, "1.2 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_144, 16, 80, 1, FDRIVE_RATE_500K, "1.28 MB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 40, 1, FDRIVE_RATE_300K, "400 kB 3\"1/2", }, /* CP Backup 5.25" DD */
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 40, 1, FDRIVE_RATE_300K, "360 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 40, 0, FDRIVE_RATE_300K, "180 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 8, 40, 1, FDRIVE_RATE_300K, "320 kB 3\"1/2", },
+ { FDRIVE_DRV_144, FDRIVE_DISK_720, 8, 40, 0, FDRIVE_RATE_300K, "160 kB 3\"1/2", },
+#ifdef VBOX /* For larger than real life floppy images (see DrvBlock.cpp). */
+ /* 15.6 MB fake floppy disk (just need something big). */
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_USER, 63, 255, 1, FDRIVE_RATE_1M, "15.6 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_288, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_288, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_288, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_288, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_288, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_144, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 9, 80, 1, FDRIVE_RATE_250K, "720 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 10, 80, 1, FDRIVE_RATE_250K, "800 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 10, 82, 1, FDRIVE_RATE_250K, "820 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 10, 83, 1, FDRIVE_RATE_250K, "830 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, FDRIVE_DISK_720, 9, 80, 0, FDRIVE_RATE_250K, "360 kB fake 15.6", },
+ /* 63.5 MB fake floppy disk (just need something big). */
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_USER, 255, 255, 1, FDRIVE_RATE_1M, "63.5 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_USER, 63, 255, 1, FDRIVE_RATE_1M, "15.6 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_288, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_288, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_288, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_288, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_288, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_144, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 9, 80, 1, FDRIVE_RATE_250K, "720 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 10, 80, 1, FDRIVE_RATE_250K, "800 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 10, 82, 1, FDRIVE_RATE_250K, "820 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 10, 83, 1, FDRIVE_RATE_250K, "830 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, FDRIVE_DISK_720, 9, 80, 0, FDRIVE_RATE_250K, "360 kB fake 63.5", },
+#endif
+ /* end */
+ { FDRIVE_DRV_NONE, FDRIVE_DISK_NONE, (uint8_t)-1, (uint8_t)-1, 0, (fdrive_rate_t)0, NULL, },
+};
+
+/* Revalidate a disk drive after a disk change */
+static void fd_revalidate(fdrive_t *drv)
+{
+ const fd_format_t *parse;
+ uint64_t nb_sectors, size;
+ int i, first_match, match;
+ int nb_heads, max_track, last_sect, ro;
+
+ FLOPPY_DPRINTF("revalidate\n");
+#ifndef VBOX
+ if (drv->bs != NULL && bdrv_is_inserted(drv->bs)) {
+ ro = bdrv_is_read_only(drv->bs);
+ bdrv_get_geometry_hint(drv->bs, &nb_heads, &max_track, &last_sect);
+#else /* VBOX */
+ if ( drv->pDrvMedia
+ && drv->pDrvMount
+ && drv->pDrvMount->pfnIsMounted (drv->pDrvMount)) {
+ ro = drv->pDrvMedia->pfnIsReadOnly (drv->pDrvMedia);
+ nb_heads = max_track = last_sect = 0;
+#endif /* VBOX */
+ if (nb_heads != 0 && max_track != 0 && last_sect != 0) {
+ FLOPPY_DPRINTF("User defined disk (%d %d %d)",
+ nb_heads - 1, max_track, last_sect);
+ } else {
+#ifndef VBOX
+ bdrv_get_geometry(drv->bs, &nb_sectors);
+#else /* VBOX */
+ {
+ uint64_t size2 = drv->pDrvMedia->pfnGetSize (drv->pDrvMedia);
+ nb_sectors = size2 / FD_SECTOR_LEN;
+ }
+#endif /* VBOX */
+ match = -1;
+ first_match = -1;
+ for (i = 0;; i++) {
+ parse = &fd_formats[i];
+ if (parse->drive == FDRIVE_DRV_NONE)
+ break;
+ if (drv->drive == parse->drive ||
+ drv->drive == FDRIVE_DRV_NONE) {
+ size = (parse->max_head + 1) * parse->max_track *
+ parse->last_sect;
+ if (nb_sectors == size) {
+ match = i;
+ break;
+ }
+ if (first_match == -1)
+ first_match = i;
+ }
+ }
+ if (match == -1) {
+ if (first_match == -1)
+ match = 1;
+ else
+ match = first_match;
+ parse = &fd_formats[match];
+ }
+ nb_heads = parse->max_head + 1;
+ max_track = parse->max_track;
+ last_sect = parse->last_sect;
+ drv->drive = parse->drive;
+#ifdef VBOX
+ drv->media_rate = parse->rate;
+#endif
+ FLOPPY_DPRINTF("%s floppy disk (%d h %d t %d s) %s\n", parse->str,
+ nb_heads, max_track, last_sect, ro ? "ro" : "rw");
+ LogRel(("FDC: %s floppy disk (%d h %d t %d s) %s\n", parse->str,
+ nb_heads, max_track, last_sect, ro ? "ro" : "rw"));
+ }
+ if (nb_heads == 1) {
+ drv->flags &= ~FDISK_DBL_SIDES;
+ } else {
+ drv->flags |= FDISK_DBL_SIDES;
+ }
+ drv->max_track = max_track;
+ drv->last_sect = last_sect;
+ drv->ro = ro;
+ } else {
+ FLOPPY_DPRINTF("No disk in drive\n");
+ drv->last_sect = 0;
+ drv->max_track = 0;
+ drv->flags &= ~FDISK_DBL_SIDES;
+ drv->dsk_chg = true; /* Disk change line active. */
+ }
+}
+
+/********************************************************/
+/* Intel 82078 floppy disk controller emulation */
+
+static void fdctrl_reset(fdctrl_t *fdctrl, int do_irq);
+static void fdctrl_reset_fifo(fdctrl_t *fdctrl);
+#ifndef VBOX
+static int fdctrl_transfer_handler (void *opaque, int nchan,
+ int dma_pos, int dma_len);
+#else /* VBOX: */
+static DECLCALLBACK(uint32_t) fdctrl_transfer_handler (PPDMDEVINS pDevIns,
+ void *opaque,
+ unsigned nchan,
+ uint32_t dma_pos,
+ uint32_t dma_len);
+#endif /* VBOX */
+static void fdctrl_raise_irq(fdctrl_t *fdctrl, uint8_t status0);
+static fdrive_t *get_cur_drv(fdctrl_t *fdctrl);
+
+static void fdctrl_result_timer(void *opaque);
+static uint32_t fdctrl_read_statusA(fdctrl_t *fdctrl);
+static uint32_t fdctrl_read_statusB(fdctrl_t *fdctrl);
+static uint32_t fdctrl_read_dor(fdctrl_t *fdctrl);
+static void fdctrl_write_dor(fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_tape(fdctrl_t *fdctrl);
+static void fdctrl_write_tape(fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_main_status(fdctrl_t *fdctrl);
+static void fdctrl_write_rate(fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_data(fdctrl_t *fdctrl);
+static void fdctrl_write_data(fdctrl_t *fdctrl, uint32_t value);
+static uint32_t fdctrl_read_dir(fdctrl_t *fdctrl);
+static void fdctrl_write_ccr(fdctrl_t *fdctrl, uint32_t value);
+
+enum {
+ FD_DIR_WRITE = 0,
+ FD_DIR_READ = 1,
+ FD_DIR_SCANE = 2,
+ FD_DIR_SCANL = 3,
+ FD_DIR_SCANH = 4,
+ FD_DIR_FORMAT = 5
+};
+
+enum {
+ FD_STATE_MULTI = 0x01, /* multi track flag */
+ FD_STATE_FORMAT = 0x02, /* format flag */
+ FD_STATE_SEEK = 0x04 /* seek flag */
+};
+
+enum {
+ FD_REG_SRA = 0x00,
+ FD_REG_SRB = 0x01,
+ FD_REG_DOR = 0x02,
+ FD_REG_TDR = 0x03,
+ FD_REG_MSR = 0x04,
+ FD_REG_DSR = 0x04,
+ FD_REG_FIFO = 0x05,
+ FD_REG_DIR = 0x07,
+ FD_REG_CCR = 0x07
+};
+
+enum {
+ FD_CMD_READ_TRACK = 0x02,
+ FD_CMD_SPECIFY = 0x03,
+ FD_CMD_SENSE_DRIVE_STATUS = 0x04,
+ FD_CMD_WRITE = 0x05,
+ FD_CMD_READ = 0x06,
+ FD_CMD_RECALIBRATE = 0x07,
+ FD_CMD_SENSE_INTERRUPT_STATUS = 0x08,
+ FD_CMD_WRITE_DELETED = 0x09,
+ FD_CMD_READ_ID = 0x0a,
+ FD_CMD_READ_DELETED = 0x0c,
+ FD_CMD_FORMAT_TRACK = 0x0d,
+ FD_CMD_DUMPREG = 0x0e,
+ FD_CMD_SEEK = 0x0f,
+ FD_CMD_VERSION = 0x10,
+ FD_CMD_SCAN_EQUAL = 0x11,
+ FD_CMD_PERPENDICULAR_MODE = 0x12,
+ FD_CMD_CONFIGURE = 0x13,
+ FD_CMD_LOCK = 0x14,
+ FD_CMD_VERIFY = 0x16,
+ FD_CMD_POWERDOWN_MODE = 0x17,
+ FD_CMD_PART_ID = 0x18,
+ FD_CMD_SCAN_LOW_OR_EQUAL = 0x19,
+ FD_CMD_SCAN_HIGH_OR_EQUAL = 0x1d,
+ FD_CMD_SAVE = 0x2e,
+ FD_CMD_OPTION = 0x33,
+ FD_CMD_RESTORE = 0x4e,
+ FD_CMD_DRIVE_SPECIFICATION_COMMAND = 0x8e,
+ FD_CMD_RELATIVE_SEEK_OUT = 0x8f,
+ FD_CMD_FORMAT_AND_WRITE = 0xcd,
+ FD_CMD_RELATIVE_SEEK_IN = 0xcf
+};
+
+enum {
+ FD_CONFIG_PRETRK = 0xff, /* Pre-compensation set to track 0 */
+ FD_CONFIG_FIFOTHR = 0x0f, /* FIFO threshold set to 1 byte */
+ FD_CONFIG_POLL = 0x10, /* Poll enabled */
+ FD_CONFIG_EFIFO = 0x20, /* FIFO disabled */
+ FD_CONFIG_EIS = 0x40 /* No implied seeks */
+};
+
+enum {
+ FD_SR0_EQPMT = 0x10,
+ FD_SR0_SEEK = 0x20,
+ FD_SR0_ABNTERM = 0x40,
+ FD_SR0_INVCMD = 0x80,
+ FD_SR0_RDYCHG = 0xc0
+};
+
+enum {
+ FD_SR1_MA = 0x01, /* Missing address mark */
+ FD_SR1_NW = 0x02, /* Not writable */
+ FD_SR1_ND = 0x04, /* No data */
+ FD_SR1_EC = 0x80 /* End of cylinder */
+};
+
+enum {
+ FD_SR2_MD = 0x01, /* Missing data address mark */
+ FD_SR2_SNS = 0x04, /* Scan not satisfied */
+ FD_SR2_SEH = 0x08 /* Scan equal hit */
+};
+
+enum {
+ FD_SRA_DIR = 0x01,
+ FD_SRA_nWP = 0x02,
+ FD_SRA_nINDX = 0x04,
+ FD_SRA_HDSEL = 0x08,
+ FD_SRA_nTRK0 = 0x10,
+ FD_SRA_STEP = 0x20,
+ FD_SRA_nDRV2 = 0x40,
+ FD_SRA_INTPEND = 0x80
+};
+
+enum {
+ FD_SRB_MTR0 = 0x01,
+ FD_SRB_MTR1 = 0x02,
+ FD_SRB_WGATE = 0x04,
+ FD_SRB_RDATA = 0x08,
+ FD_SRB_WDATA = 0x10,
+ FD_SRB_DR0 = 0x20
+};
+
+enum {
+#if MAX_FD == 4
+ FD_DOR_SELMASK = 0x03,
+#else
+ FD_DOR_SELMASK = 0x01,
+#endif
+ FD_DOR_nRESET = 0x04,
+ FD_DOR_DMAEN = 0x08,
+ FD_DOR_MOTEN0 = 0x10,
+ FD_DOR_MOTEN1 = 0x20,
+ FD_DOR_MOTEN2 = 0x40,
+ FD_DOR_MOTEN3 = 0x80
+};
+
+enum {
+#if MAX_FD == 4
+ FD_TDR_BOOTSEL = 0x0c
+#else
+ FD_TDR_BOOTSEL = 0x04
+#endif
+};
+
+enum {
+ FD_DSR_DRATEMASK= 0x03,
+ FD_DSR_PWRDOWN = 0x40,
+ FD_DSR_SWRESET = 0x80
+};
+
+enum {
+ FD_MSR_DRV0BUSY = 0x01,
+ FD_MSR_DRV1BUSY = 0x02,
+ FD_MSR_DRV2BUSY = 0x04,
+ FD_MSR_DRV3BUSY = 0x08,
+ FD_MSR_CMDBUSY = 0x10,
+ FD_MSR_NONDMA = 0x20,
+ FD_MSR_DIO = 0x40,
+ FD_MSR_RQM = 0x80
+};
+
+enum {
+ FD_DIR_DSKCHG = 0x80
+};
+
+#define FD_MULTI_TRACK(state) ((state) & FD_STATE_MULTI)
+#define FD_DID_SEEK(state) ((state) & FD_STATE_SEEK)
+#define FD_FORMAT_CMD(state) ((state) & FD_STATE_FORMAT)
+
+#ifdef VBOX
+/**
+ * Floppy controller state.
+ *
+ * @implements PDMILEDPORTS
+ */
+#endif
+struct fdctrl_t {
+#ifndef VBOX
+ fdctrl_t *fdctrl;
+#endif
+ /* Controller's identification */
+ uint8_t version;
+ /* HW */
+#ifndef VBOX
+ int irq;
+ int dma_chann;
+#else
+ uint8_t irq_lvl;
+ uint8_t dma_chann;
+#endif
+ uint32_t io_base;
+ /* Controller state */
+#ifndef VBOX
+ QEMUTimer *result_timer;
+#else
+ struct TMTIMER *result_timer;
+#endif
+ uint8_t sra;
+ uint8_t srb;
+ uint8_t dor;
+ uint8_t tdr;
+ uint8_t dsr;
+ uint8_t msr;
+ uint8_t cur_drv;
+ uint8_t status0;
+ uint8_t status1;
+ uint8_t status2;
+ /* Command FIFO */
+ uint8_t fifo[FD_SECTOR_LEN];
+ uint32_t data_pos;
+ uint32_t data_len;
+ uint8_t data_state;
+ uint8_t data_dir;
+ uint8_t eot; /* last wanted sector */
+ /* States kept only to be returned back */
+ /* Timers state */
+ uint8_t timer0;
+ uint8_t timer1;
+ /* precompensation */
+ uint8_t precomp_trk;
+ uint8_t config;
+ uint8_t lock;
+ /* Power down config (also with status regB access mode */
+ uint8_t pwrd;
+ /* Floppy drives */
+ uint8_t num_floppies;
+ fdrive_t drives[MAX_FD];
+ uint8_t reset_sensei;
+#ifdef VBOX
+ /** Pointer to device instance. */
+ PPDMDEVINS pDevIns;
+
+ /** Status LUN: The base interface. */
+ PDMIBASE IBaseStatus;
+ /** Status LUN: The Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: The Partner of ILeds. */
+ PPDMILEDCONNECTORS pLedsConnector;
+#endif
+};
+
+static uint32_t fdctrl_read (void *opaque, uint32_t reg)
+{
+ fdctrl_t *fdctrl = (fdctrl_t *)opaque;
+ uint32_t retval;
+
+ switch (reg) {
+ case FD_REG_SRA:
+ retval = fdctrl_read_statusA(fdctrl);
+ break;
+ case FD_REG_SRB:
+ retval = fdctrl_read_statusB(fdctrl);
+ break;
+ case FD_REG_DOR:
+ retval = fdctrl_read_dor(fdctrl);
+ break;
+ case FD_REG_TDR:
+ retval = fdctrl_read_tape(fdctrl);
+ break;
+ case FD_REG_MSR:
+ retval = fdctrl_read_main_status(fdctrl);
+ break;
+ case FD_REG_FIFO:
+ retval = fdctrl_read_data(fdctrl);
+ break;
+ case FD_REG_DIR:
+ retval = fdctrl_read_dir(fdctrl);
+ break;
+ default:
+ retval = (uint32_t)(-1);
+ break;
+ }
+ FLOPPY_DPRINTF("read reg%d: 0x%02x\n", reg & 7, retval);
+
+ return retval;
+}
+
+static void fdctrl_write (void *opaque, uint32_t reg, uint32_t value)
+{
+ fdctrl_t *fdctrl = (fdctrl_t *)opaque;
+
+ FLOPPY_DPRINTF("write reg%d: 0x%02x\n", reg & 7, value);
+
+ switch (reg) {
+ case FD_REG_DOR:
+ fdctrl_write_dor(fdctrl, value);
+ break;
+ case FD_REG_TDR:
+ fdctrl_write_tape(fdctrl, value);
+ break;
+ case FD_REG_DSR:
+ fdctrl_write_rate(fdctrl, value);
+ break;
+ case FD_REG_FIFO:
+ fdctrl_write_data(fdctrl, value);
+ break;
+ case FD_REG_CCR:
+ fdctrl_write_ccr(fdctrl, value);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Change IRQ state */
+static void fdctrl_reset_irq(fdctrl_t *fdctrl)
+{
+ if (!(fdctrl->sra & FD_SRA_INTPEND))
+ return;
+ FLOPPY_DPRINTF("Reset interrupt\n");
+#ifdef VBOX
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 0);
+#else
+ qemu_set_irq(fdctrl->irq, 0);
+#endif
+ fdctrl->sra &= ~FD_SRA_INTPEND;
+}
+
+static void fdctrl_raise_irq(fdctrl_t *fdctrl, uint8_t status0)
+{
+ if (!(fdctrl->sra & FD_SRA_INTPEND)) {
+ FLOPPY_DPRINTF("Raising interrupt...\n");
+#ifdef VBOX
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 1);
+#else
+ qemu_set_irq(fdctrl->irq, 1);
+#endif
+ fdctrl->sra |= FD_SRA_INTPEND;
+ }
+ if (status0 & FD_SR0_SEEK) {
+ fdrive_t *cur_drv;
+
+ /* A seek clears the disk change line (if a disk is inserted). */
+ cur_drv = get_cur_drv(fdctrl);
+ if (cur_drv->max_track)
+ cur_drv->dsk_chg = false;
+ }
+
+ fdctrl->reset_sensei = 0;
+ fdctrl->status0 = status0;
+ FLOPPY_DPRINTF("Set interrupt status to 0x%02x\n", fdctrl->status0);
+}
+
+/* Reset controller */
+static void fdctrl_reset(fdctrl_t *fdctrl, int do_irq)
+{
+ int i;
+
+ FLOPPY_DPRINTF("reset controller\n");
+ fdctrl_reset_irq(fdctrl);
+ /* Initialise controller */
+ fdctrl->sra = 0;
+ fdctrl->srb = 0xc0;
+#ifdef VBOX
+ if (!fdctrl->drives[1].pDrvMedia)
+#else
+ if (!fdctrl->drives[1].bs)
+#endif
+ fdctrl->sra |= FD_SRA_nDRV2;
+ fdctrl->cur_drv = 0;
+ fdctrl->dor = FD_DOR_nRESET;
+ fdctrl->dor |= (fdctrl->dma_chann != 0xff) ? FD_DOR_DMAEN : 0;
+ fdctrl->msr = FD_MSR_RQM;
+ /* FIFO state */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 0;
+ fdctrl->data_state = 0;
+ fdctrl->data_dir = FD_DIR_WRITE;
+ for (i = 0; i < MAX_FD; i++)
+ fd_recalibrate(&fdctrl->drives[i]);
+ fdctrl_reset_fifo(fdctrl);
+ if (do_irq) {
+ fdctrl_raise_irq(fdctrl, FD_SR0_RDYCHG);
+ fdctrl->reset_sensei = FD_RESET_SENSEI_COUNT;
+ }
+}
+
+static inline fdrive_t *drv0(fdctrl_t *fdctrl)
+{
+ return &fdctrl->drives[(fdctrl->tdr & FD_TDR_BOOTSEL) >> 2];
+}
+
+static inline fdrive_t *drv1(fdctrl_t *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (1 << 2))
+ return &fdctrl->drives[1];
+ else
+ return &fdctrl->drives[0];
+}
+
+#if MAX_FD == 4
+static inline fdrive_t *drv2(fdctrl_t *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (2 << 2))
+ return &fdctrl->drives[2];
+ else
+ return &fdctrl->drives[1];
+}
+
+static inline fdrive_t *drv3(fdctrl_t *fdctrl)
+{
+ if ((fdctrl->tdr & FD_TDR_BOOTSEL) < (3 << 2))
+ return &fdctrl->drives[3];
+ else
+ return &fdctrl->drives[2];
+}
+#endif
+
+static fdrive_t *get_cur_drv(fdctrl_t *fdctrl)
+{
+ switch (fdctrl->cur_drv) {
+ case 0: return drv0(fdctrl);
+ case 1: return drv1(fdctrl);
+#if MAX_FD == 4
+ case 2: return drv2(fdctrl);
+ case 3: return drv3(fdctrl);
+#endif
+ default: return NULL;
+ }
+}
+
+/* Status A register : 0x00 (read-only) */
+static uint32_t fdctrl_read_statusA(fdctrl_t *fdctrl)
+{
+ uint32_t retval = fdctrl->sra;
+
+ FLOPPY_DPRINTF("status register A: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Status B register : 0x01 (read-only) */
+static uint32_t fdctrl_read_statusB(fdctrl_t *fdctrl)
+{
+ uint32_t retval = fdctrl->srb;
+
+ FLOPPY_DPRINTF("status register B: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Digital output register : 0x02 */
+static uint32_t fdctrl_read_dor(fdctrl_t *fdctrl)
+{
+ uint32_t retval = fdctrl->dor;
+
+ /* Selected drive */
+ retval |= fdctrl->cur_drv;
+ FLOPPY_DPRINTF("digital output register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_dor(fdctrl_t *fdctrl, uint32_t value)
+{
+ FLOPPY_DPRINTF("digital output register set to 0x%02x\n", value);
+
+ /* Motors */
+ if (value & FD_DOR_MOTEN0)
+ fdctrl->srb |= FD_SRB_MTR0;
+ else
+ fdctrl->srb &= ~FD_SRB_MTR0;
+ if (value & FD_DOR_MOTEN1)
+ fdctrl->srb |= FD_SRB_MTR1;
+ else
+ fdctrl->srb &= ~FD_SRB_MTR1;
+
+ /* Drive */
+ if (value & 1)
+ fdctrl->srb |= FD_SRB_DR0;
+ else
+ fdctrl->srb &= ~FD_SRB_DR0;
+
+ /* Reset */
+ if (!(value & FD_DOR_nRESET)) {
+ if (fdctrl->dor & FD_DOR_nRESET) {
+ FLOPPY_DPRINTF("controller enter RESET state\n");
+ }
+ } else {
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("controller out of RESET state\n");
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ }
+ }
+ /* Selected drive */
+ fdctrl->cur_drv = value & FD_DOR_SELMASK;
+
+ fdctrl->dor = value;
+}
+
+/* Tape drive register : 0x03 */
+static uint32_t fdctrl_read_tape(fdctrl_t *fdctrl)
+{
+ uint32_t retval = fdctrl->tdr;
+
+ FLOPPY_DPRINTF("tape drive register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_write_tape(fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("tape drive register set to 0x%02x\n", value);
+ /* Disk boot selection indicator */
+ fdctrl->tdr = value & FD_TDR_BOOTSEL;
+ /* Tape indicators: never allow */
+}
+
+/* Main status register : 0x04 (read) */
+static uint32_t fdctrl_read_main_status(fdctrl_t *fdctrl)
+{
+ uint32_t retval = fdctrl->msr;
+
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ fdctrl->dor |= FD_DOR_nRESET;
+
+ FLOPPY_DPRINTF("main status register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* Data select rate register : 0x04 (write) */
+static void fdctrl_write_rate(fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("select rate register set to 0x%02x\n", value);
+ /* Reset: autoclear */
+ if (value & FD_DSR_SWRESET) {
+ fdctrl->dor &= ~FD_DOR_nRESET;
+ fdctrl_reset(fdctrl, 1);
+ fdctrl->dor |= FD_DOR_nRESET;
+ }
+ if (value & FD_DSR_PWRDOWN) {
+ fdctrl_reset(fdctrl, 1);
+ }
+ fdctrl->dsr = value;
+}
+
+/* Configuration control register : 0x07 (write) */
+static void fdctrl_write_ccr(fdctrl_t *fdctrl, uint32_t value)
+{
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ FLOPPY_DPRINTF("configuration control register set to 0x%02x\n", value);
+
+ /* Only the rate selection bits used in AT mode, and we
+ * store those in the DSR.
+ */
+ fdctrl->dsr = (fdctrl->dsr & ~FD_DSR_DRATEMASK) | (value & FD_DSR_DRATEMASK);
+}
+
+static int fdctrl_media_changed(fdrive_t *drv)
+{
+#ifdef VBOX
+ return drv->dsk_chg;
+#else
+ int ret;
+
+ if (!drv->bs)
+ return 0;
+ ret = bdrv_media_changed(drv->bs);
+ if (ret) {
+ fd_revalidate(drv);
+ }
+ return ret;
+#endif
+}
+
+/* Digital input register : 0x07 (read-only) */
+static uint32_t fdctrl_read_dir(fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+#ifdef VBOX
+ /* The change line signal is reported by the currently selected
+ * drive. If the corresponding motor on bit is not set, the drive
+ * is *not* selected!
+ */
+ if (fdctrl_media_changed(get_cur_drv(fdctrl))
+ && (fdctrl->dor & (0x10 << fdctrl->cur_drv)))
+#else
+ if (fdctrl_media_changed(drv0(fdctrl))
+ || fdctrl_media_changed(drv1(fdctrl))
+#if MAX_FD == 4
+ || fdctrl_media_changed(drv2(fdctrl))
+ || fdctrl_media_changed(drv3(fdctrl))
+#endif
+ )
+#endif
+ retval |= FD_DIR_DSKCHG;
+ if (retval != 0)
+ FLOPPY_DPRINTF("Floppy digital input register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+/* FIFO state control */
+static void fdctrl_reset_fifo(fdctrl_t *fdctrl)
+{
+ fdctrl->data_dir = FD_DIR_WRITE;
+ fdctrl->data_pos = 0;
+ fdctrl->msr &= ~(FD_MSR_CMDBUSY | FD_MSR_DIO);
+}
+
+/* Set FIFO status for the host to read */
+static void fdctrl_set_fifo(fdctrl_t *fdctrl, int fifo_len, int do_irq)
+{
+ fdctrl->data_dir = FD_DIR_READ;
+ fdctrl->data_len = fifo_len;
+ fdctrl->data_pos = 0;
+ fdctrl->msr |= FD_MSR_CMDBUSY | FD_MSR_RQM | FD_MSR_DIO;
+ if (do_irq)
+ fdctrl_raise_irq(fdctrl, 0x00);
+}
+
+/* Set an error: unimplemented/unknown command */
+static void fdctrl_unimplemented(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ FLOPPY_ERROR("unimplemented command 0x%02x\n", fdctrl->fifo[0]);
+ fdctrl->fifo[0] = FD_SR0_INVCMD;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+/* Seek to next sector */
+static int fdctrl_seek_to_next_sect(fdctrl_t *fdctrl, fdrive_t *cur_drv)
+{
+ FLOPPY_DPRINTF("seek to next sector (%d %02x %02x => %d)\n",
+ cur_drv->head, cur_drv->track, cur_drv->sect,
+ fd_sector(cur_drv));
+ /* XXX: cur_drv->sect >= cur_drv->last_sect should be an
+ error in fact */
+ if (cur_drv->sect >= cur_drv->last_sect ||
+ cur_drv->sect == fdctrl->eot) {
+ cur_drv->sect = 1;
+ if (FD_MULTI_TRACK(fdctrl->data_state)) {
+ if (cur_drv->head == 0 &&
+ (cur_drv->flags & FDISK_DBL_SIDES) != 0) {
+ cur_drv->head = 1;
+ } else {
+ cur_drv->head = 0;
+ cur_drv->ltrk++;
+ if ((cur_drv->flags & FDISK_DBL_SIDES) == 0)
+ return 0;
+ }
+ } else {
+ cur_drv->ltrk++;
+ return 0;
+ }
+ FLOPPY_DPRINTF("seek to next track (%d %02x %02x => %d)\n",
+ cur_drv->head, cur_drv->track,
+ cur_drv->sect, fd_sector(cur_drv));
+ } else {
+ cur_drv->sect++;
+ }
+ return 1;
+}
+
+/* Callback for transfer end (stop or abort) */
+static void fdctrl_stop_transfer(fdctrl_t *fdctrl, uint8_t status0,
+ uint8_t status1, uint8_t status2)
+{
+ fdrive_t *cur_drv;
+
+ cur_drv = get_cur_drv(fdctrl);
+ FLOPPY_DPRINTF("transfer status: %02x %02x %02x (%02x)\n",
+ status0, status1, status2,
+ status0 | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl));
+ fdctrl->fifo[0] = status0 | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl);
+ fdctrl->fifo[1] = status1;
+ fdctrl->fifo[2] = status2;
+ fdctrl->fifo[3] = cur_drv->ltrk;
+ fdctrl->fifo[4] = cur_drv->head;
+ fdctrl->fifo[5] = cur_drv->sect;
+ fdctrl->fifo[6] = FD_SECTOR_SC;
+ FLOPPY_DPRINTF("ST0:%02x ST1:%02x ST2:%02x C:%02x H:%02x R:%02x N:%02x\n",
+ fdctrl->fifo[0], fdctrl->fifo[1], fdctrl->fifo[2], fdctrl->fifo[3],
+ fdctrl->fifo[4], fdctrl->fifo[5], fdctrl->fifo[6]);
+
+ fdctrl->data_dir = FD_DIR_READ;
+ if (!(fdctrl->msr & FD_MSR_NONDMA)) {
+#ifdef VBOX
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 0);
+#else
+ DMA_release_DREQ(fdctrl->dma_chann);
+#endif
+ }
+ fdctrl->msr |= FD_MSR_RQM | FD_MSR_DIO;
+ fdctrl->msr &= ~FD_MSR_NONDMA;
+ fdctrl_set_fifo(fdctrl, 7, 1);
+}
+
+/* Prepare a data transfer (either DMA or FIFO) */
+static void fdctrl_start_transfer(fdctrl_t *fdctrl, int direction)
+{
+ fdrive_t *cur_drv;
+ uint8_t kh, kt, ks;
+ int did_seek = 0;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[2];
+ kh = fdctrl->fifo[3];
+ ks = fdctrl->fifo[4];
+ FLOPPY_DPRINTF("Start transfer at %d %d %02x %02x (%d)\n",
+ GET_CUR_DRV(fdctrl), kh, kt, ks,
+ fd_sector_calc(kh, kt, ks, cur_drv->last_sect, NUM_SIDES(cur_drv)));
+ FLOPPY_DPRINTF("CMD:%02x SEL:%02x C:%02x H:%02x R:%02x N:%02x EOT:%02x GPL:%02x DTL:%02x\n",
+ fdctrl->fifo[0], fdctrl->fifo[1], fdctrl->fifo[2],
+ fdctrl->fifo[3], fdctrl->fifo[4], fdctrl->fifo[5],
+ fdctrl->fifo[6], fdctrl->fifo[7], fdctrl->fifo[8]);
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & FD_CONFIG_EIS)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_EC, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 5:
+ /* No disk in drive */
+ /// @todo This is wrong! Command should not complete.
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | 0x08, /*FD_SR1_MA |*/ FD_SR1_ND, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ did_seek = 1;
+ break;
+ default:
+ break;
+ }
+ /* Check the data rate. If the programmed data rate does not match
+ * the currently inserted medium, the operation has to fail.
+ */
+#ifdef VBOX
+ if ((fdctrl->dsr & FD_DSR_DRATEMASK) != cur_drv->media_rate) {
+ FLOPPY_DPRINTF("data rate mismatch (fdc=%d, media=%d)\n",
+ fdctrl->dsr & FD_DSR_DRATEMASK, cur_drv->media_rate);
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, FD_SR2_MD);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ }
+#endif
+ /* Set the FIFO state */
+ fdctrl->data_dir = direction;
+ fdctrl->data_pos = 0;
+ fdctrl->msr |= FD_MSR_CMDBUSY;
+ if (fdctrl->fifo[0] & 0x80)
+ fdctrl->data_state |= FD_STATE_MULTI;
+ else
+ fdctrl->data_state &= ~FD_STATE_MULTI;
+ if (did_seek)
+ fdctrl->data_state |= FD_STATE_SEEK;
+ else
+ fdctrl->data_state &= ~FD_STATE_SEEK;
+ if (fdctrl->fifo[5] == 00) {
+ fdctrl->data_len = fdctrl->fifo[8];
+ } else {
+ int tmp;
+ fdctrl->data_len = 128 << (fdctrl->fifo[5] > 7 ? 7 : fdctrl->fifo[5]);
+ tmp = (fdctrl->fifo[6] - ks + 1);
+ if (fdctrl->fifo[0] & 0x80)
+ tmp += fdctrl->fifo[6];
+ fdctrl->data_len *= tmp;
+ }
+ fdctrl->eot = fdctrl->fifo[6];
+ if (fdctrl->dor & FD_DOR_DMAEN) {
+ int dma_mode;
+ /* DMA transfer are enabled. Check if DMA channel is well programmed */
+#ifndef VBOX
+ dma_mode = DMA_get_channel_mode(fdctrl->dma_chann);
+#else
+ dma_mode = PDMDevHlpDMAGetChannelMode (fdctrl->pDevIns, fdctrl->dma_chann);
+#endif
+ dma_mode = (dma_mode >> 2) & 3;
+ FLOPPY_DPRINTF("dma_mode=%d direction=%d (%d - %d)\n",
+ dma_mode, direction,
+ (128 << fdctrl->fifo[5]) *
+ (cur_drv->last_sect - ks + 1), fdctrl->data_len);
+ if (((direction == FD_DIR_SCANE || direction == FD_DIR_SCANL ||
+ direction == FD_DIR_SCANH) && dma_mode == 0) ||
+ (direction == FD_DIR_WRITE && dma_mode == 2) ||
+ (direction == FD_DIR_READ && (dma_mode == 1 || dma_mode == 0))) {
+ /* No access is allowed until DMA transfer has completed */
+ fdctrl->msr &= ~FD_MSR_RQM;
+ /* Now, we just have to wait for the DMA controller to
+ * recall us...
+ */
+#ifndef VBOX
+ DMA_hold_DREQ(fdctrl->dma_chann);
+ DMA_schedule(fdctrl->dma_chann);
+#else
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 1);
+ PDMDevHlpDMASchedule (fdctrl->pDevIns);
+#endif
+ return;
+ } else {
+ FLOPPY_ERROR("dma_mode=%d direction=%d\n", dma_mode, direction);
+ }
+ }
+ FLOPPY_DPRINTF("start non-DMA transfer\n");
+ fdctrl->msr |= FD_MSR_NONDMA;
+ if (direction != FD_DIR_WRITE)
+ fdctrl->msr |= FD_MSR_DIO;
+ /* IO based transfer: calculate len */
+ fdctrl_raise_irq(fdctrl, 0x00);
+
+ return;
+}
+
+/* Prepare a format data transfer (either DMA or FIFO) */
+static void fdctrl_start_format(fdctrl_t *fdctrl)
+{
+ fdrive_t *cur_drv;
+ uint8_t ns, dp, kh, kt, ks;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ kt = cur_drv->track;
+ kh = (fdctrl->fifo[1] & 0x04) >> 2;
+ ns = fdctrl->fifo[3];
+ dp = fdctrl->fifo[5];
+ ks = 1;
+ FLOPPY_DPRINTF("Start format at %d %d %02x, %d sect, pat %02x (%d)\n",
+ GET_CUR_DRV(fdctrl), kh, kt, ns, dp,
+ fd_sector_calc(kh, kt, ks, cur_drv->last_sect, NUM_SIDES(cur_drv)));
+ switch (fd_seek(cur_drv, kh, kt, ks, false)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_EC, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 5:
+ /* No disk in drive */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ break;
+ default:
+ break;
+ }
+ /* It's not clear what should happen if the data rate does not match. */
+#if 0
+ /* Check the data rate. If the programmed data rate does not match
+ * the currently inserted medium, the operation has to fail.
+ */
+ if ((fdctrl->dsr & FD_DSR_DRATEMASK) != cur_drv->media_rate) {
+ FLOPPY_DPRINTF("data rate mismatch (fdc=%d, media=%d)\n",
+ fdctrl->dsr & FD_DSR_DRATEMASK, cur_drv->media_rate);
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, FD_SR2_MD);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ }
+#endif
+ /* Set the FIFO state */
+ fdctrl->data_dir = FD_DIR_FORMAT;
+ fdctrl->data_pos = 0;
+ fdctrl->msr |= FD_MSR_CMDBUSY;
+ fdctrl->data_state &= ~(FD_STATE_MULTI | FD_STATE_SEEK);
+ fdctrl->data_len = ns * 4;
+ fdctrl->eot = ns;
+ if (fdctrl->dor & FD_DOR_DMAEN) {
+ int dma_mode;
+ /* DMA transfer are enabled. Check if DMA channel is well programmed */
+#ifndef VBOX
+ dma_mode = DMA_get_channel_mode(fdctrl->dma_chann);
+#else
+ dma_mode = PDMDevHlpDMAGetChannelMode (fdctrl->pDevIns, fdctrl->dma_chann);
+#endif
+ dma_mode = (dma_mode >> 2) & 3;
+ FLOPPY_DPRINTF("dma_mode=%d direction=%d (%d - %d)\n",
+ dma_mode, fdctrl->data_dir,
+ (128 << fdctrl->fifo[2]) *
+ (cur_drv->last_sect + 1), fdctrl->data_len);
+ if (fdctrl->data_dir == FD_DIR_FORMAT && dma_mode == 2) {
+ /* No access is allowed until DMA transfer has completed */
+ fdctrl->msr &= ~FD_MSR_RQM;
+ /* Now, we just have to wait for the DMA controller to
+ * recall us...
+ */
+#ifndef VBOX
+ DMA_hold_DREQ(fdctrl->dma_chann);
+ DMA_schedule(fdctrl->dma_chann);
+#else
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 1);
+ PDMDevHlpDMASchedule (fdctrl->pDevIns);
+#endif
+ return;
+ } else {
+ FLOPPY_ERROR("dma_mode=%d direction=%d\n", dma_mode, fdctrl->data_dir);
+ }
+ }
+ FLOPPY_DPRINTF("start non-DMA format\n");
+ fdctrl->msr |= FD_MSR_NONDMA;
+ /* IO based transfer: calculate len */
+ fdctrl_raise_irq(fdctrl, 0x00);
+
+ return;
+}
+
+/* Prepare a transfer of deleted data */
+static void fdctrl_start_transfer_del(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ FLOPPY_ERROR("fdctrl_start_transfer_del() unimplemented\n");
+
+ /* We don't handle deleted data,
+ * so we don't return *ANYTHING*
+ */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+}
+
+#ifdef VBOX
+/* Block driver read/write wrappers. */
+
+static int blk_write(fdrive_t *drv, int64_t sector_num, const uint8_t *buf, int nb_sectors)
+{
+ int rc;
+
+ drv->Led.Asserted.s.fWriting = drv->Led.Actual.s.fWriting = 1;
+
+ rc = drv->pDrvMedia->pfnWrite(drv->pDrvMedia, sector_num * FD_SECTOR_LEN,
+ buf, nb_sectors * FD_SECTOR_LEN);
+
+ drv->Led.Actual.s.fWriting = 0;
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("Floppy: Failure to read sector %d. rc=%Rrc", sector_num, rc));
+
+ return rc;
+}
+
+static int blk_read(fdrive_t *drv, int64_t sector_num, uint8_t *buf, int nb_sectors)
+{
+ int rc;
+
+ drv->Led.Asserted.s.fReading = drv->Led.Actual.s.fReading = 1;
+
+ rc = drv->pDrvMedia->pfnRead(drv->pDrvMedia, sector_num * FD_SECTOR_LEN,
+ buf, nb_sectors * FD_SECTOR_LEN);
+
+ drv->Led.Actual.s.fReading = 0;
+
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("Floppy: Failure to read sector %d. rc=%Rrc", sector_num, rc));
+
+ return rc;
+}
+
+#endif
+
+/* handlers for DMA transfers */
+#ifdef VBOX
+static DECLCALLBACK(uint32_t) fdctrl_transfer_handler (PPDMDEVINS pDevIns,
+ void *opaque,
+ unsigned nchan,
+ uint32_t dma_pos,
+ uint32_t dma_len)
+#else
+static int fdctrl_transfer_handler (void *opaque, int nchan,
+ int dma_pos, int dma_len)
+#endif
+{
+ RT_NOREF(pDevIns, dma_pos);
+ fdctrl_t *fdctrl;
+ fdrive_t *cur_drv;
+#ifdef VBOX
+ int rc;
+ uint32_t len = 0;
+ uint32_t start_pos, rel_pos;
+#else
+ int len, start_pos, rel_pos;
+#endif
+ uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+
+ fdctrl = (fdctrl_t *)opaque;
+ if (fdctrl->msr & FD_MSR_RQM) {
+ FLOPPY_DPRINTF("Not in DMA transfer mode !\n");
+ return 0;
+ }
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->data_dir == FD_DIR_SCANE || fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = FD_SR2_SNS;
+ if (dma_len > fdctrl->data_len)
+ dma_len = fdctrl->data_len;
+#ifndef VBOX
+ if (cur_drv->bs == NULL)
+#else /* !VBOX */
+ if (cur_drv->pDrvMedia == NULL)
+#endif
+ {
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ Assert(len == 0);
+ goto transfer_error;
+ }
+
+#ifdef VBOX
+ if (cur_drv->ro)
+ {
+ if (fdctrl->data_dir == FD_DIR_WRITE || fdctrl->data_dir == FD_DIR_FORMAT)
+ {
+ /* Handle readonly medium early, no need to do DMA, touch the
+ * LED or attempt any writes. A real floppy doesn't attempt
+ * to write to readonly media either. */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, FD_SR1_NW,
+ 0x00);
+ Assert(len == 0);
+ goto transfer_error;
+ }
+ }
+#endif
+
+
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ for (start_pos = fdctrl->data_pos; fdctrl->data_pos < dma_len;) {
+ len = dma_len - fdctrl->data_pos;
+ if (len + rel_pos > FD_SECTOR_LEN)
+ len = FD_SECTOR_LEN - rel_pos;
+ FLOPPY_DPRINTF("copy %d bytes (%d %d %d) %d pos %d %02x "
+ "(%d-0x%08x 0x%08x)\n", len, dma_len, fdctrl->data_pos,
+ fdctrl->data_len, GET_CUR_DRV(fdctrl), cur_drv->head,
+ cur_drv->track, cur_drv->sect, fd_sector(cur_drv),
+ fd_sector(cur_drv) * FD_SECTOR_LEN);
+ if (fdctrl->data_dir != FD_DIR_FORMAT &&
+ (fdctrl->data_dir != FD_DIR_WRITE ||
+ len < FD_SECTOR_LEN || rel_pos != 0)) {
+ /* READ & SCAN commands and realign to a sector for WRITE */
+#ifdef VBOX
+ rc = blk_read(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+#else
+ if (bdrv_read(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0)
+#endif
+ {
+ FLOPPY_DPRINTF("Floppy: error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+ }
+ switch (fdctrl->data_dir) {
+ case FD_DIR_READ:
+ /* READ commands */
+#ifdef VBOX
+ {
+ uint32_t read;
+ int rc2 = PDMDevHlpDMAWriteMemory(fdctrl->pDevIns, nchan,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &read);
+ AssertMsgRC (rc2, ("DMAWriteMemory -> %Rrc\n", rc2));
+ }
+#else
+ DMA_write_memory (nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+#endif
+/* cpu_physical_memory_write(addr + fdctrl->data_pos, */
+/* fdctrl->fifo + rel_pos, len); */
+ break;
+ case FD_DIR_WRITE:
+ /* WRITE commands */
+#ifdef VBOX
+ {
+ uint32_t written;
+ int rc2 = PDMDevHlpDMAReadMemory(fdctrl->pDevIns, nchan,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &written);
+ AssertMsgRC (rc2, ("DMAReadMemory -> %Rrc\n", rc2));
+ }
+
+ rc = blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+#else
+ DMA_read_memory (nchan, fdctrl->fifo + rel_pos,
+ fdctrl->data_pos, len);
+ if (bdrv_write(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1) < 0)
+#endif
+ {
+ FLOPPY_ERROR("writing sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ goto transfer_error;
+ }
+ break;
+#ifdef VBOX
+ case FD_DIR_FORMAT:
+ /* FORMAT command */
+ {
+ uint8_t eot = fdctrl->fifo[3];
+ uint8_t filler = fdctrl->fifo[5];
+ uint32_t written;
+ int sct;
+ int rc2 = PDMDevHlpDMAReadMemory(fdctrl->pDevIns, nchan,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &written);
+ AssertMsgRC (rc2, ("DMAReadMemory -> %Rrc\n", rc2));
+
+ /* Fill the entire track with desired data pattern. */
+ FLOPPY_DPRINTF("formatting track: %d sectors, pattern %02x\n",
+ eot, filler);
+ memset(fdctrl->fifo, filler, FD_SECTOR_LEN);
+ for (sct = 0; sct < eot; ++sct)
+ {
+ rc = blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+ {
+ FLOPPY_ERROR("formatting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ goto transfer_error;
+ }
+ fdctrl_seek_to_next_sect(fdctrl, cur_drv);
+ }
+ }
+ break;
+#endif
+ default:
+ /* SCAN commands */
+ {
+ uint8_t tmpbuf[FD_SECTOR_LEN];
+ int ret;
+#ifdef VBOX
+ uint32_t read;
+ int rc2 = PDMDevHlpDMAReadMemory (fdctrl->pDevIns, nchan, tmpbuf,
+ fdctrl->data_pos, len, &read);
+ AssertMsg(RT_SUCCESS(rc2), ("DMAReadMemory -> %Rrc2\n", rc2)); NOREF(rc2);
+#else
+ DMA_read_memory (nchan, tmpbuf, fdctrl->data_pos, len);
+#endif
+ ret = memcmp(tmpbuf, fdctrl->fifo + rel_pos, len);
+ if (ret == 0) {
+ status2 = FD_SR2_SEH;
+ goto end_transfer;
+ }
+ if ((ret < 0 && fdctrl->data_dir == FD_DIR_SCANL) ||
+ (ret > 0 && fdctrl->data_dir == FD_DIR_SCANH)) {
+ status2 = 0x00;
+ goto end_transfer;
+ }
+ }
+ break;
+ }
+ fdctrl->data_pos += len;
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ if (rel_pos == 0) {
+ /* Seek to next sector */
+ if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv))
+ break;
+ }
+ }
+end_transfer:
+ len = fdctrl->data_pos - start_pos;
+ FLOPPY_DPRINTF("end transfer %d %d %d\n",
+ fdctrl->data_pos, len, fdctrl->data_len);
+ if (fdctrl->data_dir == FD_DIR_SCANE ||
+ fdctrl->data_dir == FD_DIR_SCANL ||
+ fdctrl->data_dir == FD_DIR_SCANH)
+ status2 = FD_SR2_SEH;
+ if (FD_DID_SEEK(fdctrl->data_state))
+ status0 |= FD_SR0_SEEK;
+ fdctrl->data_len -= len;
+ fdctrl_stop_transfer(fdctrl, status0, status1, status2);
+transfer_error:
+
+ return len;
+}
+
+/* Data register : 0x05 */
+static uint32_t fdctrl_read_data(fdctrl_t *fdctrl)
+{
+ fdrive_t *cur_drv;
+ uint32_t retval = 0;
+ unsigned pos;
+#ifdef VBOX
+ int rc;
+#endif
+
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ if (!(fdctrl->msr & FD_MSR_RQM) || !(fdctrl->msr & FD_MSR_DIO)) {
+ FLOPPY_ERROR("controller not ready for reading\n");
+ return 0;
+ }
+ pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ if (fdctrl->msr & FD_MSR_NONDMA) {
+ if (pos == 0) {
+ if (fdctrl->data_pos != 0)
+ if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv)) {
+ FLOPPY_DPRINTF("error seeking to next sector %d\n",
+ fd_sector(cur_drv));
+ return 0;
+ }
+#ifdef VBOX
+ rc = blk_read(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+#else
+ if (bdrv_read(cur_drv->bs, fd_sector(cur_drv), fdctrl->fifo, 1) < 0)
+#endif
+ {
+ FLOPPY_DPRINTF("error getting sector %d\n",
+ fd_sector(cur_drv));
+ /* Sure, image size is too small... */
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+ }
+ }
+ }
+ retval = fdctrl->fifo[pos];
+ if (++fdctrl->data_pos == fdctrl->data_len) {
+ fdctrl->data_pos = 0;
+ /* Switch from transfer mode to status mode
+ * then from status mode to command mode
+ */
+ if (fdctrl->msr & FD_MSR_NONDMA) {
+ fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00);
+ } else {
+ fdctrl_reset_fifo(fdctrl);
+ fdctrl_reset_irq(fdctrl);
+ }
+ }
+ FLOPPY_DPRINTF("data register: 0x%02x\n", retval);
+
+ return retval;
+}
+
+static void fdctrl_format_sector(fdctrl_t *fdctrl)
+{
+ fdrive_t *cur_drv;
+ uint8_t kh, kt, ks;
+#ifdef VBOX
+ int ok = 0, rc;
+#endif
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ kt = fdctrl->fifo[6];
+ kh = fdctrl->fifo[7];
+ ks = fdctrl->fifo[8];
+ FLOPPY_DPRINTF("format sector at %d %d %02x %02x (%d)\n",
+ GET_CUR_DRV(fdctrl), kh, kt, ks,
+ fd_sector_calc(kh, kt, ks, cur_drv->last_sect, NUM_SIDES(cur_drv)));
+ switch (fd_seek(cur_drv, kh, kt, ks, fdctrl->config & FD_CONFIG_EIS)) {
+ case 2:
+ /* sect too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 3:
+ /* track too big */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_EC, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 4:
+ /* No seek enabled */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 5:
+ /* No disk in drive */
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA, 0x00);
+ fdctrl->fifo[3] = kt;
+ fdctrl->fifo[4] = kh;
+ fdctrl->fifo[5] = ks;
+ return;
+ case 1:
+ fdctrl->data_state |= FD_STATE_SEEK;
+ break;
+ default:
+ break;
+ }
+ memset(fdctrl->fifo, 0, FD_SECTOR_LEN);
+#ifdef VBOX
+ if (cur_drv->pDrvMedia) {
+ rc = blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE (rc)) {
+ FLOPPY_ERROR("formatting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ } else {
+ ok = 1;
+ }
+ }
+ if (ok) {
+#else
+ if (cur_drv->bs == NULL ||
+ bdrv_write(cur_drv->bs, fd_sector(cur_drv), fdctrl->fifo, 1) < 0) {
+ FLOPPY_ERROR("formatting sector %d\n", fd_sector(cur_drv));
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ } else {
+#endif
+ if (cur_drv->sect == cur_drv->last_sect) {
+ fdctrl->data_state &= ~FD_STATE_FORMAT;
+ /* Last sector done */
+ if (FD_DID_SEEK(fdctrl->data_state))
+ fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+ } else {
+ /* More to do */
+ fdctrl->data_pos = 0;
+ fdctrl->data_len = 4;
+ }
+ }
+}
+
+static void fdctrl_handle_lock(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdctrl->lock = (fdctrl->fifo[0] & 0x80) ? 1 : 0;
+ fdctrl->fifo[0] = fdctrl->lock << 4;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+static void fdctrl_handle_dumpreg(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ /* Drives position */
+ fdctrl->fifo[0] = drv0(fdctrl)->track;
+ fdctrl->fifo[1] = drv1(fdctrl)->track;
+#if MAX_FD == 4
+ fdctrl->fifo[2] = drv2(fdctrl)->track;
+ fdctrl->fifo[3] = drv3(fdctrl)->track;
+#else
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+#endif
+ /* timers */
+ fdctrl->fifo[4] = fdctrl->timer0;
+ fdctrl->fifo[5] = (fdctrl->timer1 << 1) | (fdctrl->dor & FD_DOR_DMAEN ? 1 : 0);
+ fdctrl->fifo[6] = cur_drv->last_sect;
+ fdctrl->fifo[7] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[8] = fdctrl->config;
+ fdctrl->fifo[9] = fdctrl->precomp_trk;
+ fdctrl_set_fifo(fdctrl, 10, 0);
+}
+
+static void fdctrl_handle_version(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ /* Controller's version */
+ fdctrl->fifo[0] = fdctrl->version;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+static void fdctrl_handle_partid(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdctrl->fifo[0] = 0x01; /* Stepping 1 */
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+static void fdctrl_handle_restore(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ /* Drives position */
+ drv0(fdctrl)->track = fdctrl->fifo[3];
+ drv1(fdctrl)->track = fdctrl->fifo[4];
+#if MAX_FD == 4
+ drv2(fdctrl)->track = fdctrl->fifo[5];
+ drv3(fdctrl)->track = fdctrl->fifo[6];
+#endif
+ /* timers */
+ fdctrl->timer0 = fdctrl->fifo[7];
+ fdctrl->timer1 = fdctrl->fifo[8];
+ cur_drv->last_sect = fdctrl->fifo[9];
+ fdctrl->lock = fdctrl->fifo[10] >> 7;
+ cur_drv->perpendicular = (fdctrl->fifo[10] >> 2) & 0xF;
+ fdctrl->config = fdctrl->fifo[11];
+ fdctrl->precomp_trk = fdctrl->fifo[12];
+ fdctrl->pwrd = fdctrl->fifo[13];
+ fdctrl_reset_fifo(fdctrl);
+}
+
+static void fdctrl_handle_save(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ fdctrl->fifo[0] = 0;
+ fdctrl->fifo[1] = 0;
+ /* Drives position */
+ fdctrl->fifo[2] = drv0(fdctrl)->track;
+ fdctrl->fifo[3] = drv1(fdctrl)->track;
+#if MAX_FD == 4
+ fdctrl->fifo[4] = drv2(fdctrl)->track;
+ fdctrl->fifo[5] = drv3(fdctrl)->track;
+#else
+ fdctrl->fifo[4] = 0;
+ fdctrl->fifo[5] = 0;
+#endif
+ /* timers */
+ fdctrl->fifo[6] = fdctrl->timer0;
+ fdctrl->fifo[7] = fdctrl->timer1;
+ fdctrl->fifo[8] = cur_drv->last_sect;
+ fdctrl->fifo[9] = (fdctrl->lock << 7) |
+ (cur_drv->perpendicular << 2);
+ fdctrl->fifo[10] = fdctrl->config;
+ fdctrl->fifo[11] = fdctrl->precomp_trk;
+ fdctrl->fifo[12] = fdctrl->pwrd;
+ fdctrl->fifo[13] = 0;
+ fdctrl->fifo[14] = 0;
+ fdctrl_set_fifo(fdctrl, 15, 0);
+}
+
+static void fdctrl_handle_readid(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ FLOPPY_DPRINTF("CMD:%02x SEL:%02x\n", fdctrl->fifo[0], fdctrl->fifo[1]);
+
+ fdctrl->msr &= ~FD_MSR_RQM;
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+#ifdef VBOX
+ TMTimerSetMillies(fdctrl->result_timer, 1000 / 50);
+#else
+ qemu_mod_timer(fdctrl->result_timer,
+ qemu_get_clock(vm_clock) + (get_ticks_per_sec() / 50));
+#endif
+}
+
+static void fdctrl_handle_format_track(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+ uint8_t ns, dp;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl->data_state &= ~(FD_STATE_MULTI | FD_STATE_SEEK);
+ ns = fdctrl->fifo[3];
+ dp = fdctrl->fifo[5];
+
+ FLOPPY_DPRINTF("Format track %d at %d, %d sectors, filler %02x\n",
+ cur_drv->track, GET_CUR_DRV(fdctrl), ns, dp);
+ FLOPPY_DPRINTF("CMD:%02x SEL:%02x N:%02x SC:%02x GPL:%02x D:%02x\n",
+ fdctrl->fifo[0], fdctrl->fifo[1], fdctrl->fifo[2],
+ fdctrl->fifo[3], fdctrl->fifo[4], fdctrl->fifo[5]);
+
+ /* Since we cannot actually format anything, we have to make sure that
+ * whatever new format the guest is trying to establish matches the
+ * existing format of the medium.
+ */
+ if (cur_drv->last_sect != ns || fdctrl->fifo[2] != 2)
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_NW, 0);
+ else
+ {
+ cur_drv->bps = fdctrl->fifo[2] > 7 ? 16384 : 128 << fdctrl->fifo[2];
+ cur_drv->last_sect = ns;
+
+ fdctrl_start_format(fdctrl);
+ }
+}
+
+static void fdctrl_handle_specify(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdctrl->timer0 = (fdctrl->fifo[1] >> 4) & 0xF;
+ fdctrl->timer1 = fdctrl->fifo[2] >> 1;
+ if (fdctrl->fifo[2] & 1)
+ fdctrl->dor &= ~FD_DOR_DMAEN;
+ else
+ fdctrl->dor |= FD_DOR_DMAEN;
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+}
+
+static void fdctrl_handle_sense_drive_status(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+ /* 1 Byte status back */
+ fdctrl->fifo[0] = (cur_drv->ro << 6) |
+ (cur_drv->track == 0 ? 0x10 : 0x00) |
+ (cur_drv->head << 2) |
+ GET_CUR_DRV(fdctrl) |
+ 0x28;
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+static void fdctrl_handle_recalibrate(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+ uint8_t st0;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fd_recalibrate(cur_drv);
+ fdctrl_reset_fifo(fdctrl);
+ st0 = FD_SR0_SEEK | GET_CUR_DRV(fdctrl);
+ /* No drive means no TRK0 signal. */
+ if (cur_drv->drive == FDRIVE_DRV_NONE)
+ st0 |= FD_SR0_ABNTERM | FD_SR0_EQPMT;
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, st0);
+}
+
+static void fdctrl_handle_sense_interrupt_status(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ FLOPPY_DPRINTF("CMD:%02x\n", fdctrl->fifo[0]);
+ if(fdctrl->reset_sensei > 0) {
+ fdctrl->fifo[0] =
+ FD_SR0_RDYCHG + FD_RESET_SENSEI_COUNT - fdctrl->reset_sensei;
+ fdctrl->reset_sensei--;
+ } else {
+ /* XXX: status0 handling is broken for read/write
+ commands, so we do this hack. It should be suppressed
+ ASAP */
+ fdctrl->fifo[0] =
+ FD_SR0_SEEK | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl);
+ /* Hack to preserve SR0 on equipment check failures (no drive). */
+ if (fdctrl->status0 & FD_SR0_EQPMT)
+ fdctrl->fifo[0] = fdctrl->status0;
+ }
+
+ fdctrl->fifo[1] = cur_drv->track;
+ fdctrl_set_fifo(fdctrl, 2, 0);
+ FLOPPY_DPRINTF("ST0:%02x PCN:%02x\n", fdctrl->fifo[0], fdctrl->fifo[1]);
+ fdctrl->status0 = FD_SR0_RDYCHG;
+}
+
+static void fdctrl_handle_seek(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+
+ FLOPPY_DPRINTF("CMD:%02x SEL:%02x NCN:%02x\n", fdctrl->fifo[0],
+ fdctrl->fifo[1], fdctrl->fifo[2]);
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ fdctrl_reset_fifo(fdctrl);
+#ifdef VBOX
+ /* The seek command just sends step pulses to the drive and doesn't care if
+ * there's a medium inserted or if it's banging the head against the drive.
+ */
+ cur_drv->track = fdctrl->fifo[2];
+ cur_drv->ltrk = cur_drv->track;
+ cur_drv->head = (fdctrl->fifo[1] >> 2) & 1;
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, FD_SR0_SEEK | GET_CUR_DRV(fdctrl));
+#else
+ if (fdctrl->fifo[2] > cur_drv->max_track) {
+ fdctrl_raise_irq(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK);
+ } else {
+ cur_drv->track = fdctrl->fifo[2];
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, FD_SR0_SEEK);
+ }
+#endif
+}
+
+static void fdctrl_handle_perpendicular_mode(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ if (fdctrl->fifo[1] & 0x80)
+ cur_drv->perpendicular = fdctrl->fifo[1] & 0x7;
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+}
+
+static void fdctrl_handle_configure(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdctrl->config = fdctrl->fifo[2];
+ fdctrl->precomp_trk = fdctrl->fifo[3];
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+}
+
+static void fdctrl_handle_powerdown_mode(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdctrl->pwrd = fdctrl->fifo[1];
+ fdctrl->fifo[0] = fdctrl->fifo[1];
+ fdctrl_set_fifo(fdctrl, 1, 0);
+}
+
+static void fdctrl_handle_option(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ /* No result back */
+ fdctrl_reset_fifo(fdctrl);
+}
+
+static void fdctrl_handle_drive_specification_command(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ /* fdrive_t *cur_drv = get_cur_drv(fdctrl); - unused */
+
+ /* This command takes a variable number of parameters. It can be terminated
+ * at any time if the high bit of a parameter is set. Once there are 6 bytes
+ * in the FIFO (command + 5 parameter bytes), data_len/data_pos will be 7.
+ */
+ if (fdctrl->data_len == 7 || (fdctrl->fifo[fdctrl->data_pos - 1] & 0x80)) {
+
+ /* Command parameters done */
+ if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x40) {
+ /* Data is echoed, but not stored! */
+ fdctrl->fifo[0] = fdctrl->data_len > 2 ? fdctrl->fifo[1] : 0;
+ fdctrl->fifo[1] = fdctrl->data_len > 3 ? fdctrl->fifo[2] : 0;
+ fdctrl->fifo[2] = 0;
+ fdctrl->fifo[3] = 0;
+ fdctrl_set_fifo(fdctrl, 4, 0);
+ } else {
+ fdctrl_reset_fifo(fdctrl);
+ }
+ } else
+ fdctrl->data_len++; /* Wait for another byte. */
+}
+
+static void fdctrl_handle_relative_seek_out(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->fifo[2] + cur_drv->track >= cur_drv->max_track) {
+ cur_drv->track = cur_drv->max_track - 1;
+ } else {
+ cur_drv->track += fdctrl->fifo[2];
+ }
+ fdctrl_reset_fifo(fdctrl);
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, FD_SR0_SEEK);
+}
+
+static void fdctrl_handle_relative_seek_in(fdctrl_t *fdctrl, int direction)
+{
+ RT_NOREF(direction);
+ fdrive_t *cur_drv;
+
+ SET_CUR_DRV(fdctrl, fdctrl->fifo[1] & FD_DOR_SELMASK);
+ cur_drv = get_cur_drv(fdctrl);
+ if (fdctrl->fifo[2] > cur_drv->track) {
+ cur_drv->track = 0;
+ } else {
+ cur_drv->track -= fdctrl->fifo[2];
+ }
+ fdctrl_reset_fifo(fdctrl);
+ /* Raise Interrupt */
+ fdctrl_raise_irq(fdctrl, FD_SR0_SEEK);
+}
+
+static const struct {
+ uint8_t value;
+ uint8_t mask;
+ const char* name;
+ int parameters;
+ void (*handler)(fdctrl_t *fdctrl, int direction);
+ int direction;
+} handlers[] = {
+ { FD_CMD_READ, 0x1f, "READ", 8, fdctrl_start_transfer, FD_DIR_READ },
+ { FD_CMD_WRITE, 0x3f, "WRITE", 8, fdctrl_start_transfer, FD_DIR_WRITE },
+ { FD_CMD_SEEK, 0xff, "SEEK", 2, fdctrl_handle_seek },
+ { FD_CMD_SENSE_INTERRUPT_STATUS, 0xff, "SENSE INTERRUPT STATUS", 0, fdctrl_handle_sense_interrupt_status },
+ { FD_CMD_RECALIBRATE, 0xff, "RECALIBRATE", 1, fdctrl_handle_recalibrate },
+ { FD_CMD_FORMAT_TRACK, 0xbf, "FORMAT TRACK", 5, fdctrl_handle_format_track },
+ { FD_CMD_READ_TRACK, 0xbf, "READ TRACK", 8, fdctrl_start_transfer, FD_DIR_READ },
+ { FD_CMD_RESTORE, 0xff, "RESTORE", 17, fdctrl_handle_restore }, /* part of READ DELETED DATA */
+ { FD_CMD_SAVE, 0xff, "SAVE", 0, fdctrl_handle_save }, /* part of READ DELETED DATA */
+ { FD_CMD_READ_DELETED, 0x1f, "READ DELETED DATA", 8, fdctrl_start_transfer_del, FD_DIR_READ },
+ { FD_CMD_SCAN_EQUAL, 0x1f, "SCAN EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANE },
+ { FD_CMD_VERIFY, 0x1f, "VERIFY", 8, fdctrl_unimplemented },
+ { FD_CMD_SCAN_LOW_OR_EQUAL, 0x1f, "SCAN LOW OR EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANL },
+ { FD_CMD_SCAN_HIGH_OR_EQUAL, 0x1f, "SCAN HIGH OR EQUAL", 8, fdctrl_start_transfer, FD_DIR_SCANH },
+ { FD_CMD_WRITE_DELETED, 0x3f, "WRITE DELETED DATA", 8, fdctrl_start_transfer_del, FD_DIR_WRITE },
+ { FD_CMD_READ_ID, 0xbf, "READ ID", 1, fdctrl_handle_readid },
+ { FD_CMD_SPECIFY, 0xff, "SPECIFY", 2, fdctrl_handle_specify },
+ { FD_CMD_SENSE_DRIVE_STATUS, 0xff, "SENSE DRIVE STATUS", 1, fdctrl_handle_sense_drive_status },
+ { FD_CMD_PERPENDICULAR_MODE, 0xff, "PERPENDICULAR MODE", 1, fdctrl_handle_perpendicular_mode },
+ { FD_CMD_CONFIGURE, 0xff, "CONFIGURE", 3, fdctrl_handle_configure },
+ { FD_CMD_POWERDOWN_MODE, 0xff, "POWERDOWN MODE", 2, fdctrl_handle_powerdown_mode },
+ { FD_CMD_OPTION, 0xff, "OPTION", 1, fdctrl_handle_option },
+ { FD_CMD_DRIVE_SPECIFICATION_COMMAND, 0xff, "DRIVE SPECIFICATION COMMAND", 1, fdctrl_handle_drive_specification_command },
+ { FD_CMD_RELATIVE_SEEK_OUT, 0xff, "RELATIVE SEEK OUT", 2, fdctrl_handle_relative_seek_out },
+ { FD_CMD_FORMAT_AND_WRITE, 0xff, "FORMAT AND WRITE", 10, fdctrl_unimplemented },
+ { FD_CMD_RELATIVE_SEEK_IN, 0xff, "RELATIVE SEEK IN", 2, fdctrl_handle_relative_seek_in },
+ { FD_CMD_LOCK, 0x7f, "LOCK", 0, fdctrl_handle_lock },
+ { FD_CMD_DUMPREG, 0xff, "DUMPREG", 0, fdctrl_handle_dumpreg },
+ { FD_CMD_VERSION, 0xff, "VERSION", 0, fdctrl_handle_version },
+ { FD_CMD_PART_ID, 0xff, "PART ID", 0, fdctrl_handle_partid },
+ { FD_CMD_WRITE, 0x1f, "WRITE (BeOS)", 8, fdctrl_start_transfer, FD_DIR_WRITE }, /* not in specification ; BeOS 4.5 bug */
+ { 0, 0, "unknown", 0, fdctrl_unimplemented }, /* default handler */
+};
+/* Associate command to an index in the 'handlers' array */
+static uint8_t command_to_handler[256];
+
+static void fdctrl_write_data(fdctrl_t *fdctrl, uint32_t value)
+{
+ fdrive_t *cur_drv;
+ int pos;
+
+ cur_drv = get_cur_drv(fdctrl);
+ /* Reset mode */
+ if (!(fdctrl->dor & FD_DOR_nRESET)) {
+ FLOPPY_DPRINTF("Floppy controller in RESET state !\n");
+ return;
+ }
+ if (!(fdctrl->msr & FD_MSR_RQM) || (fdctrl->msr & FD_MSR_DIO)) {
+ FLOPPY_ERROR("controller not ready for writing\n");
+ return;
+ }
+ fdctrl->dsr &= ~FD_DSR_PWRDOWN;
+ /* Is it write command time ? */
+ if (fdctrl->msr & FD_MSR_NONDMA) {
+ /* FIFO data write */
+ pos = fdctrl->data_pos++;
+ pos %= FD_SECTOR_LEN;
+ fdctrl->fifo[pos] = value;
+ if (pos == FD_SECTOR_LEN - 1 ||
+ fdctrl->data_pos == fdctrl->data_len) {
+#ifdef VBOX
+ blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+#else
+ bdrv_write(cur_drv->bs, fd_sector(cur_drv),
+ fdctrl->fifo, 1);
+#endif
+ }
+ /* Switch from transfer mode to status mode
+ * then from status mode to command mode
+ */
+ if (fdctrl->data_pos == fdctrl->data_len)
+ fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00);
+ return;
+ }
+ if (fdctrl->data_pos == 0) {
+ /* Command */
+ fdctrl_reset_irq(fdctrl); /* If pending from previous seek/recalibrate. */
+ pos = command_to_handler[value & 0xff];
+ FLOPPY_DPRINTF("%s command\n", handlers[pos].name);
+ fdctrl->data_len = handlers[pos].parameters + 1;
+ fdctrl->msr |= FD_MSR_CMDBUSY;
+ }
+
+ FLOPPY_DPRINTF("%s: %02x\n", __FUNCTION__, value);
+ fdctrl->fifo[fdctrl->data_pos++ % FD_SECTOR_LEN] = value;
+ if (fdctrl->data_pos == fdctrl->data_len) {
+ /* We now have all parameters
+ * and will be able to treat the command
+ */
+ if (fdctrl->data_state & FD_STATE_FORMAT) {
+ fdctrl_format_sector(fdctrl);
+ return;
+ }
+
+ pos = command_to_handler[fdctrl->fifo[0] & 0xff];
+ FLOPPY_DPRINTF("treat %s command\n", handlers[pos].name);
+ (*handlers[pos].handler)(fdctrl, handlers[pos].direction);
+ }
+}
+
+static void fdctrl_result_timer(void *opaque)
+{
+ fdctrl_t *fdctrl = (fdctrl_t *)opaque;
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+
+ /* Pretend we are spinning.
+ * This is needed for Coherent, which uses READ ID to check for
+ * sector interleaving.
+ */
+ if (cur_drv->last_sect != 0) {
+ cur_drv->sect = (cur_drv->sect % cur_drv->last_sect) + 1;
+ }
+ /* READ_ID can't automatically succeed! */
+#ifdef VBOX
+ if (!cur_drv->max_track) {
+ FLOPPY_DPRINTF("read id when no disk in drive\n");
+ /// @todo This is wrong! Command should not complete.
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA | FD_SR1_ND, FD_SR2_MD);
+ } else if ((fdctrl->dsr & FD_DSR_DRATEMASK) != cur_drv->media_rate) {
+ FLOPPY_DPRINTF("read id rate mismatch (fdc=%d, media=%d)\n",
+ fdctrl->dsr & FD_DSR_DRATEMASK, cur_drv->media_rate);
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA | FD_SR1_ND, FD_SR2_MD);
+ } else if (cur_drv->track >= cur_drv->max_track) {
+ FLOPPY_DPRINTF("read id past last track (%d >= %d)\n",
+ cur_drv->track, cur_drv->max_track);
+ cur_drv->ltrk = 0;
+ fdctrl_stop_transfer(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA | FD_SR1_ND, FD_SR2_MD);
+ }
+ else
+#endif
+ fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00);
+}
+
+
+#ifdef VBOX
+
+/* -=-=-=-=-=-=-=-=- Timer Callback -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) fdcTimerCallback(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, pTimer);
+ fdctrl_t *fdctrl = (fdctrl_t *)pvUser;
+ fdctrl_result_timer(fdctrl);
+}
+
+
+/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTOUT}
+ */
+static DECLCALLBACK(int) fdcIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pDevIns);
+ if (cb == 1)
+ fdctrl_write (pvUser, uPort & 7, u32);
+ else
+ AssertMsgFailed(("uPort=%#x cb=%d u32=%#x\n", uPort, cb, u32));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTIN}
+ */
+static DECLCALLBACK(int) fdcIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pDevIns);
+ if (cb == 1)
+ {
+ *pu32 = fdctrl_read (pvUser, uPort & 7);
+ return VINF_SUCCESS;
+ }
+ return VERR_IOM_IOPORT_UNUSED;
+}
+
+
+/* -=-=-=-=-=-=-=-=- Saved state -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) fdcSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ fdctrl_t *pThis = PDMINS_2_DATA(pDevIns, fdctrl_t *);
+ unsigned int i;
+
+ /* Save the FDC I/O registers... */
+ SSMR3PutU8(pSSM, pThis->sra);
+ SSMR3PutU8(pSSM, pThis->srb);
+ SSMR3PutU8(pSSM, pThis->dor);
+ SSMR3PutU8(pSSM, pThis->tdr);
+ SSMR3PutU8(pSSM, pThis->dsr);
+ SSMR3PutU8(pSSM, pThis->msr);
+ /* ...the status registers... */
+ SSMR3PutU8(pSSM, pThis->status0);
+ SSMR3PutU8(pSSM, pThis->status1);
+ SSMR3PutU8(pSSM, pThis->status2);
+ /* ...the command FIFO... */
+ SSMR3PutU32(pSSM, sizeof(pThis->fifo));
+ SSMR3PutMem(pSSM, &pThis->fifo, sizeof(pThis->fifo));
+ SSMR3PutU32(pSSM, pThis->data_pos);
+ SSMR3PutU32(pSSM, pThis->data_len);
+ SSMR3PutU8(pSSM, pThis->data_state);
+ SSMR3PutU8(pSSM, pThis->data_dir);
+ /* ...and miscellaneous internal FDC state. */
+ SSMR3PutU8(pSSM, pThis->reset_sensei);
+ SSMR3PutU8(pSSM, pThis->eot);
+ SSMR3PutU8(pSSM, pThis->timer0);
+ SSMR3PutU8(pSSM, pThis->timer1);
+ SSMR3PutU8(pSSM, pThis->precomp_trk);
+ SSMR3PutU8(pSSM, pThis->config);
+ SSMR3PutU8(pSSM, pThis->lock);
+ SSMR3PutU8(pSSM, pThis->pwrd);
+ SSMR3PutU8(pSSM, pThis->version);
+
+ /* Save the number of drives and per-drive state. Note that the media
+ * states will be updated in fd_revalidate() and need not be saved.
+ */
+ SSMR3PutU8(pSSM, pThis->num_floppies);
+ Assert(RT_ELEMENTS(pThis->drives) == pThis->num_floppies);
+ for (i = 0; i < pThis->num_floppies; ++i)
+ {
+ fdrive_t *d = &pThis->drives[i];
+
+ SSMR3PutMem(pSSM, &d->Led, sizeof(d->Led));
+ SSMR3PutU32(pSSM, d->drive);
+ SSMR3PutU8(pSSM, d->dsk_chg);
+ SSMR3PutU8(pSSM, d->perpendicular);
+ SSMR3PutU8(pSSM, d->head);
+ SSMR3PutU8(pSSM, d->track);
+ SSMR3PutU8(pSSM, d->sect);
+ }
+ return TMR3TimerSave (pThis->result_timer, pSSM);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) fdcLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ fdctrl_t *pThis = PDMINS_2_DATA(pDevIns, fdctrl_t *);
+ unsigned int i;
+ uint32_t val32;
+ uint8_t val8;
+
+ if (uVersion > FDC_SAVESTATE_CURRENT)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ /* The old saved state was significantly different. However, we can get
+ * back most of the controller state and fix the rest by pretending the
+ * disk in the drive (if any) has been replaced. At any rate there should
+ * be no difficulty unless the state was saved during a floppy operation.
+ */
+ if (uVersion == FDC_SAVESTATE_OLD)
+ {
+ /* First verify a few assumptions. */
+ AssertMsgReturn(sizeof(pThis->fifo) == FD_SECTOR_LEN,
+ ("The size of FIFO in saved state doesn't match!\n"),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ AssertMsgReturn(RT_ELEMENTS(pThis->drives) == 2,
+ ("The number of drives in old saved state doesn't match!\n"),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ /* Now load the old state. */
+ SSMR3GetU8(pSSM, &pThis->version);
+ /* Toss IRQ level, DMA channel, I/O base, and state. */
+ SSMR3GetU8(pSSM, &val8);
+ SSMR3GetU8(pSSM, &val8);
+ SSMR3GetU32(pSSM, &val32);
+ SSMR3GetU8(pSSM, &val8);
+ /* Translate dma_en. */
+ SSMR3GetU8(pSSM, &val8);
+ if (val8)
+ pThis->dor |= FD_DOR_DMAEN;
+ SSMR3GetU8(pSSM, &pThis->cur_drv);
+ /* Translate bootsel. */
+ SSMR3GetU8(pSSM, &val8);
+ pThis->tdr |= val8 << 2;
+ SSMR3GetMem(pSSM, &pThis->fifo, FD_SECTOR_LEN);
+ SSMR3GetU32(pSSM, &pThis->data_pos);
+ SSMR3GetU32(pSSM, &pThis->data_len);
+ SSMR3GetU8(pSSM, &pThis->data_state);
+ SSMR3GetU8(pSSM, &pThis->data_dir);
+ SSMR3GetU8(pSSM, &pThis->status0);
+ SSMR3GetU8(pSSM, &pThis->eot);
+ SSMR3GetU8(pSSM, &pThis->timer0);
+ SSMR3GetU8(pSSM, &pThis->timer1);
+ SSMR3GetU8(pSSM, &pThis->precomp_trk);
+ SSMR3GetU8(pSSM, &pThis->config);
+ SSMR3GetU8(pSSM, &pThis->lock);
+ SSMR3GetU8(pSSM, &pThis->pwrd);
+
+ for (i = 0; i < 2; ++i)
+ {
+ fdrive_t *d = &pThis->drives[i];
+
+ SSMR3GetMem (pSSM, &d->Led, sizeof (d->Led));
+ SSMR3GetU32(pSSM, &val32);
+ d->drive = (fdrive_type_t)val32;
+ SSMR3GetU32(pSSM, &val32); /* Toss drflags */
+ SSMR3GetU8(pSSM, &d->perpendicular);
+ SSMR3GetU8(pSSM, &d->head);
+ SSMR3GetU8(pSSM, &d->track);
+ SSMR3GetU8(pSSM, &d->sect);
+ SSMR3GetU8(pSSM, &val8); /* Toss dir, rw */
+ SSMR3GetU8(pSSM, &val8);
+ SSMR3GetU32(pSSM, &val32);
+ d->flags = (fdrive_flags_t)val32;
+ SSMR3GetU8(pSSM, &d->last_sect);
+ SSMR3GetU8(pSSM, &d->max_track);
+ SSMR3GetU16(pSSM, &d->bps);
+ SSMR3GetU8(pSSM, &d->ro);
+ }
+ }
+ else /* New state - straightforward. */
+ {
+ Assert(uVersion == FDC_SAVESTATE_CURRENT);
+ /* Load the FDC I/O registers... */
+ SSMR3GetU8(pSSM, &pThis->sra);
+ SSMR3GetU8(pSSM, &pThis->srb);
+ SSMR3GetU8(pSSM, &pThis->dor);
+ SSMR3GetU8(pSSM, &pThis->tdr);
+ SSMR3GetU8(pSSM, &pThis->dsr);
+ SSMR3GetU8(pSSM, &pThis->msr);
+ /* ...the status registers... */
+ SSMR3GetU8(pSSM, &pThis->status0);
+ SSMR3GetU8(pSSM, &pThis->status1);
+ SSMR3GetU8(pSSM, &pThis->status2);
+ /* ...the command FIFO, if the size matches... */
+ SSMR3GetU32(pSSM, &val32);
+ AssertMsgReturn(sizeof(pThis->fifo) == val32,
+ ("The size of FIFO in saved state doesn't match!\n"),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ SSMR3GetMem(pSSM, &pThis->fifo, sizeof(pThis->fifo));
+ SSMR3GetU32(pSSM, &pThis->data_pos);
+ SSMR3GetU32(pSSM, &pThis->data_len);
+ SSMR3GetU8(pSSM, &pThis->data_state);
+ SSMR3GetU8(pSSM, &pThis->data_dir);
+ /* ...and miscellaneous internal FDC state. */
+ SSMR3GetU8(pSSM, &pThis->reset_sensei);
+ SSMR3GetU8(pSSM, &pThis->eot);
+ SSMR3GetU8(pSSM, &pThis->timer0);
+ SSMR3GetU8(pSSM, &pThis->timer1);
+ SSMR3GetU8(pSSM, &pThis->precomp_trk);
+ SSMR3GetU8(pSSM, &pThis->config);
+ SSMR3GetU8(pSSM, &pThis->lock);
+ SSMR3GetU8(pSSM, &pThis->pwrd);
+ SSMR3GetU8(pSSM, &pThis->version);
+
+ /* Validate the number of drives. */
+ SSMR3GetU8(pSSM, &pThis->num_floppies);
+ AssertMsgReturn(RT_ELEMENTS(pThis->drives) == pThis->num_floppies,
+ ("The number of drives in saved state doesn't match!\n"),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ /* Load the per-drive state. */
+ for (i = 0; i < pThis->num_floppies; ++i)
+ {
+ fdrive_t *d = &pThis->drives[i];
+
+ SSMR3GetMem(pSSM, &d->Led, sizeof(d->Led));
+ SSMR3GetU32(pSSM, &val32);
+ d->drive = (fdrive_type_t)val32;
+ SSMR3GetU8(pSSM, &d->dsk_chg);
+ SSMR3GetU8(pSSM, &d->perpendicular);
+ SSMR3GetU8(pSSM, &d->head);
+ SSMR3GetU8(pSSM, &d->track);
+ SSMR3GetU8(pSSM, &d->sect);
+ }
+ }
+ return TMR3TimerLoad (pThis->result_timer, pSSM);
+}
+
+
+/* -=-=-=-=-=-=-=-=- Drive level interfaces -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnMountNotify}
+ */
+static DECLCALLBACK(void) fdMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ fdrive_t *pDrv = RT_FROM_MEMBER(pInterface, fdrive_t, IMountNotify);
+ LogFlow(("fdMountNotify:\n"));
+ fd_revalidate(pDrv);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnUnmountNotify}
+ */
+static DECLCALLBACK(void) fdUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ fdrive_t *pDrv = RT_FROM_MEMBER(pInterface, fdrive_t, IMountNotify);
+ LogFlow(("fdUnmountNotify:\n"));
+ fd_revalidate(pDrv);
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) fdQueryInterface (PPDMIBASE pInterface, const char *pszIID)
+{
+ fdrive_t *pDrv = RT_FROM_MEMBER(pInterface, fdrive_t, IBase);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrv->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pDrv->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pDrv->IMountNotify);
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) fdQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ fdrive_t *pDrv = RT_FROM_MEMBER(pInterface, fdrive_t, IPort);
+ PPDMDEVINS pDevIns = pDrv->pDevIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pDrv->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=-=-=-=-=- Controller level interfaces -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed}
+ */
+static DECLCALLBACK(int) fdcStatusQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ fdctrl_t *pThis = RT_FROM_MEMBER (pInterface, fdctrl_t, ILeds);
+ if (iLUN < RT_ELEMENTS(pThis->drives)) {
+ *ppLed = &pThis->drives[iLUN].Led;
+ Assert ((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) fdcStatusQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ fdctrl_t *pThis = RT_FROM_MEMBER (pInterface, fdctrl_t, IBaseStatus);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBaseStatus);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->ILeds);
+ return NULL;
+}
+
+
+/**
+ * Configure a drive.
+ *
+ * @returns VBox status code.
+ * @param drv The drive in question.
+ * @param pDevIns The driver instance.
+ * @param fInit Set if we're at init time and can change the drive type.
+ */
+static int fdConfig(fdrive_t *drv, PPDMDEVINS pDevIns, bool fInit)
+{
+ static const char * const s_apszDesc[] = {"Floppy Drive A:", "Floppy Drive B"};
+ int rc;
+
+ /*
+ * Reset the LED just to be on the safe side.
+ */
+ Assert (RT_ELEMENTS(s_apszDesc) > drv->iLUN);
+ Assert (drv->Led.u32Magic == PDMLED_MAGIC);
+ drv->Led.Actual.u32 = 0;
+ drv->Led.Asserted.u32 = 0;
+
+ /*
+ * Try attach the block device and get the interfaces.
+ */
+ rc = PDMDevHlpDriverAttach (pDevIns, drv->iLUN, &drv->IBase, &drv->pDrvBase, s_apszDesc[drv->iLUN]);
+ if (RT_SUCCESS (rc)) {
+ drv->pDrvMedia = PDMIBASE_QUERY_INTERFACE(drv->pDrvBase, PDMIMEDIA);
+ if (drv->pDrvMedia) {
+ drv->pDrvMount = PDMIBASE_QUERY_INTERFACE(drv->pDrvBase, PDMIMOUNT);
+ if (drv->pDrvMount) {
+ fd_init(drv, fInit);
+ } else {
+ AssertMsgFailed (("Configuration error: LUN#%d without mountable interface!\n", drv->iLUN));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ } else {
+ AssertMsgFailed (("Configuration error: LUN#%d hasn't a block interface!\n", drv->iLUN));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ } else {
+ AssertMsg (rc == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Failed to attach LUN#%d. rc=%Rrc\n", drv->iLUN, rc));
+ switch (rc) {
+ case VERR_ACCESS_DENIED:
+ /* Error already cached by DrvHostBase */
+ break;
+ case VERR_PDM_NO_ATTACHED_DRIVER:
+ /* Legal on architectures without a floppy controller */
+ break;
+ default:
+ rc = PDMDevHlpVMSetError (pDevIns, rc, RT_SRC_POS,
+ N_ ("The floppy controller cannot attach to the floppy drive"));
+ break;
+ }
+ }
+
+ if (RT_FAILURE (rc)) {
+ drv->pDrvBase = NULL;
+ drv->pDrvMedia = NULL;
+ drv->pDrvMount = NULL;
+ }
+ LogFlow (("fdConfig: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ *
+ * This is called when we change block driver for a floppy drive.
+ */
+static DECLCALLBACK(int) fdcAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ fdctrl_t *fdctrl = PDMINS_2_DATA(pDevIns, fdctrl_t *);
+ fdrive_t *drv;
+ int rc;
+ LogFlow (("ideDetach: iLUN=%u\n", iLUN));
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("The FDC device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Validate.
+ */
+ if (iLUN >= 2) {
+ AssertMsgFailed (("Configuration error: cannot attach or detach any but the first two LUNs - iLUN=%u\n",
+ iLUN));
+ return VERR_PDM_DEVINS_NO_ATTACH;
+ }
+
+ /*
+ * Locate the drive and stuff.
+ */
+ drv = &fdctrl->drives[iLUN];
+
+ /* the usual paranoia */
+ AssertRelease (!drv->pDrvBase);
+ AssertRelease (!drv->pDrvMedia);
+ AssertRelease (!drv->pDrvMount);
+
+ rc = fdConfig (drv, pDevIns, false /*fInit*/);
+ AssertMsg (rc != VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: failed to configure drive %d, rc=%Rrc\n", iLUN, rc));
+ if (RT_SUCCESS(rc)) {
+ fd_revalidate (drv);
+ }
+
+ LogFlow (("floppyAttach: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ *
+ * The floppy drive has been temporarily 'unplugged'.
+ */
+static DECLCALLBACK(void) fdcDetach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ fdctrl_t *pThis = PDMINS_2_DATA(pDevIns, fdctrl_t *);
+ LogFlow (("ideDetach: iLUN=%u\n", iLUN));
+
+ switch (iLUN)
+ {
+ case 0:
+ case 1:
+ {
+ fdrive_t *drv = &pThis->drives[iLUN];
+ drv->pDrvBase = NULL;
+ drv->pDrvMedia = NULL;
+ drv->pDrvMount = NULL;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Cannot detach LUN#%d!\n", iLUN));
+ break;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ *
+ * I haven't check the specs on what's supposed to happen on reset, but we
+ * should get any 'FATAL: floppy recal:f07 ctrl not ready' when resetting
+ * at wrong time like we do if this was all void.
+ */
+static DECLCALLBACK(void) fdcReset(PPDMDEVINS pDevIns)
+{
+ fdctrl_t *pThis = PDMINS_2_DATA (pDevIns, fdctrl_t *);
+ unsigned i;
+ LogFlow (("fdcReset:\n"));
+
+ fdctrl_reset(pThis, 0);
+
+ for (i = 0; i < RT_ELEMENTS(pThis->drives); i++)
+ fd_revalidate(&pThis->drives[i]);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) fdcConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ RT_NOREF(iInstance);
+ fdctrl_t *pThis = PDMINS_2_DATA(pDevIns, fdctrl_t *);
+ int rc;
+ unsigned i, j;
+ int ii;
+ bool mem_mapped;
+ uint16_t io_base;
+ uint8_t irq_lvl, dma_chann;
+ PPDMIBASE pBase;
+
+ Assert(iInstance == 0);
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "IRQ\0DMA\0MemMapped\0IOBase\0"))
+ return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Read the configuration.
+ */
+ rc = CFGMR3QueryU8Def(pCfg, "IRQ", &irq_lvl, 6);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U8 IRQ, rc=%Rrc\n", rc), rc);
+
+ rc = CFGMR3QueryU8Def(pCfg, "DMA", &dma_chann, 2);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U8 DMA, rc=%Rrc\n", rc), rc);
+
+ rc = CFGMR3QueryU16Def(pCfg, "IOBase", &io_base, 0x3f0);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U16 IOBase, rc=%Rrc\n", rc), rc);
+
+ rc = CFGMR3QueryBoolDef(pCfg, "MemMapped", &mem_mapped, false);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read bool value MemMapped rc=%Rrc\n", rc), rc);
+
+ /*
+ * Initialize data.
+ */
+ LogFlow(("fdcConstruct: irq_lvl=%d dma_chann=%d io_base=%#x\n", irq_lvl, dma_chann, io_base));
+ pThis->pDevIns = pDevIns;
+ pThis->version = 0x90; /* Intel 82078 controller */
+ pThis->irq_lvl = irq_lvl;
+ pThis->dma_chann = dma_chann;
+ pThis->io_base = io_base;
+ pThis->config = FD_CONFIG_EIS | FD_CONFIG_EFIFO; /* Implicit seek, polling & FIFO enabled */
+ pThis->num_floppies = MAX_FD;
+
+ /* Fill 'command_to_handler' lookup table */
+ for (ii = RT_ELEMENTS(handlers) - 1; ii >= 0; ii--)
+ for (j = 0; j < sizeof(command_to_handler); j++)
+ if ((j & handlers[ii].mask) == handlers[ii].value)
+ command_to_handler[j] = ii;
+
+ pThis->IBaseStatus.pfnQueryInterface = fdcStatusQueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = fdcStatusQueryStatusLed;
+
+ for (i = 0; i < RT_ELEMENTS(pThis->drives); ++i)
+ {
+ fdrive_t *pDrv = &pThis->drives[i];
+
+ pDrv->drive = FDRIVE_DRV_NONE;
+ pDrv->iLUN = i;
+ pDrv->pDevIns = pDevIns;
+
+ pDrv->IBase.pfnQueryInterface = fdQueryInterface;
+ pDrv->IMountNotify.pfnMountNotify = fdMountNotify;
+ pDrv->IMountNotify.pfnUnmountNotify = fdUnmountNotify;
+ pDrv->IPort.pfnQueryDeviceLocation = fdQueryDeviceLocation;
+ pDrv->Led.u32Magic = PDMLED_MAGIC;
+ }
+
+ /*
+ * Create the FDC timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, fdcTimerCallback, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "FDC Timer", &pThis->result_timer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register DMA channel.
+ */
+ if (pThis->dma_chann != 0xff)
+ {
+ rc = PDMDevHlpDMARegister(pDevIns, dma_chann, &fdctrl_transfer_handler, pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * IO / MMIO.
+ */
+ if (mem_mapped)
+ {
+ AssertMsgFailed(("Memory mapped floppy not support by now\n"));
+ return VERR_NOT_SUPPORTED;
+#if 0
+ FLOPPY_ERROR("memory mapped floppy not supported by now !\n");
+ io_mem = cpu_register_io_memory(0, fdctrl_mem_read, fdctrl_mem_write);
+ cpu_register_physical_memory(base, 0x08, io_mem);
+#endif
+ }
+ else
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, io_base + 0x1, 5, pThis,
+ fdcIoPortWrite, fdcIoPortRead, NULL, NULL, "FDC#1");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpIOPortRegister(pDevIns, io_base + 0x7, 1, pThis,
+ fdcIoPortWrite, fdcIoPortRead, NULL, NULL, "FDC#2");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegister(pDevIns, FDC_SAVESTATE_CURRENT, sizeof(*pThis), fdcSaveExec, fdcLoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach the status port (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBaseStatus, &pBase, "Status Port");
+ if (RT_SUCCESS (rc))
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Initialize drives.
+ */
+ for (i = 0; i < RT_ELEMENTS(pThis->drives); i++)
+ {
+ fdrive_t *pDrv = &pThis->drives[i];
+ rc = fdConfig(pDrv, pDevIns, true /*fInit*/);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Configuration error: failed to configure drive %d, rc=%Rrc\n", i, rc));
+ return rc;
+ }
+ }
+
+ fdctrl_reset(pThis, 0);
+
+ for (i = 0; i < RT_ELEMENTS(pThis->drives); i++)
+ fd_revalidate(&pThis->drives[i]);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceFloppyController =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "i82078",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Floppy drive controller (Intel 82078)",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(fdctrl_t),
+ /* pfnConstruct */
+ fdcConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ fdcReset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ fdcAttach,
+ /* pfnDetach */
+ fdcDetach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* VBOX */
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "k&r"
+ * indent-tabs-mode: nil
+ * End:
+ */
+
diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp
new file mode 100644
index 00000000..05a06d4f
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp
@@ -0,0 +1,5849 @@
+/* $Id: DevLsiLogicSCSI.cpp $ */
+/** @file
+ * DevLsiLogicSCSI - LsiLogic LSI53c1030 SCSI controller.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_LSILOGICSCSI
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmqueue.h>
+#include <VBox/vmm/pdmthread.h>
+#include <VBox/vmm/pdmcritsect.h>
+#include <VBox/scsi.h>
+#include <VBox/sup.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#ifdef IN_RING3
+# include <iprt/memcache.h>
+# include <iprt/mem.h>
+# include <iprt/param.h>
+# include <iprt/uuid.h>
+# include <iprt/time.h>
+#endif
+
+#include "DevLsiLogicSCSI.h"
+#include "VBoxSCSI.h"
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The current saved state version. */
+#define LSILOGIC_SAVED_STATE_VERSION 5
+/** The saved state version used by VirtualBox before the diagnostic
+ * memory access was implemented. */
+#define LSILOGIC_SAVED_STATE_VERSION_PRE_DIAG_MEM 4
+/** The saved state version used by VirtualBox before the doorbell status flag
+ * was changed from bool to a 32bit enum. */
+#define LSILOGIC_SAVED_STATE_VERSION_BOOL_DOORBELL 3
+/** The saved state version used by VirtualBox before SAS support was added. */
+#define LSILOGIC_SAVED_STATE_VERSION_PRE_SAS 2
+/** The saved state version used by VirtualBox 3.0 and earlier. It does not
+ * include the device config part. */
+#define LSILOGIC_SAVED_STATE_VERSION_VBOX_30 1
+
+/** Maximum number of entries in the release log. */
+#define MAX_REL_LOG_ERRORS 1024
+
+#define LSILOGIC_RTGCPHYS_FROM_U32(Hi, Lo) ( (RTGCPHYS)RT_MAKE_U64(Lo, Hi) )
+
+/** Upper number a buffer is freed if it was too big before. */
+#define LSILOGIC_MAX_ALLOC_TOO_MUCH 20
+
+/** Maximum size of the memory regions (prevents teh guest from DOSing the host by
+ * allocating loadds of memory). */
+#define LSILOGIC_MEMORY_REGIONS_MAX (_1M)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Pointer to the device instance data of the LsiLogic emulation. */
+typedef struct LSILOGICSCSI *PLSILOGICSCSI;
+
+#ifdef IN_RING3
+/**
+ * Memory buffer callback.
+ *
+ * @returns nothing.
+ * @param pThis The LsiLogic controller instance.
+ * @param GCPhys The guest physical address of the memory buffer.
+ * @param pSgBuf The pointer to the host R3 S/G buffer.
+ * @param cbCopy How many bytes to copy between the two buffers.
+ * @param pcbSkip Initially contains the amount of bytes to skip
+ * starting from the guest physical address before
+ * accessing the S/G buffer and start copying data.
+ * On return this contains the remaining amount if
+ * cbCopy < *pcbSkip or 0 otherwise.
+ */
+typedef DECLCALLBACK(void) LSILOGICR3MEMCOPYCALLBACK(PLSILOGICSCSI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf, size_t cbCopy,
+ size_t *pcbSkip);
+/** Pointer to a memory copy buffer callback. */
+typedef LSILOGICR3MEMCOPYCALLBACK *PLSILOGICR3MEMCOPYCALLBACK;
+#endif
+
+/**
+ * Reply data.
+ */
+typedef struct LSILOGICSCSIREPLY
+{
+ /** Lower 32 bits of the reply address in memory. */
+ uint32_t u32HostMFALowAddress;
+ /** Full address of the reply in guest memory. */
+ RTGCPHYS GCPhysReplyAddress;
+ /** Size of the reply. */
+ uint32_t cbReply;
+ /** Different views to the reply depending on the request type. */
+ MptReplyUnion Reply;
+} LSILOGICSCSIREPLY;
+/** Pointer to reply data. */
+typedef LSILOGICSCSIREPLY *PLSILOGICSCSIREPLY;
+
+/**
+ * Memory region of the IOC.
+ */
+typedef struct LSILOGICMEMREGN
+{
+ /** List node. */
+ RTLISTNODE NodeList;
+ /** 32bit address the region starts to describe. */
+ uint32_t u32AddrStart;
+ /** 32bit address the region ends (inclusive). */
+ uint32_t u32AddrEnd;
+ /** Data for this region - variable. */
+ uint32_t au32Data[1];
+} LSILOGICMEMREGN;
+/** Pointer to a memory region. */
+typedef LSILOGICMEMREGN *PLSILOGICMEMREGN;
+
+/**
+ * State of a device attached to the buslogic host adapter.
+ *
+ * @implements PDMIBASE
+ * @implements PDMISCSIPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct LSILOGICDEVICE
+{
+ /** Pointer to the owning lsilogic device instance. - R3 pointer */
+ R3PTRTYPE(PLSILOGICSCSI) pLsiLogicR3;
+
+ /** LUN of the device. */
+ uint32_t iLUN;
+ /** Number of outstanding tasks on the port. */
+ volatile uint32_t cOutstandingRequests;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Our base interface. */
+ PDMIBASE IBase;
+ /** Media port interface. */
+ PDMIMEDIAPORT IMediaPort;
+ /** Extended media port interface. */
+ PDMIMEDIAEXPORT IMediaExPort;
+ /** Led interface. */
+ PDMILEDPORTS ILed;
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's media interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's extended media interface. */
+ R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx;
+ /** The status LED state for this device. */
+ PDMLED Led;
+
+} LSILOGICDEVICE;
+/** Pointer to a device state. */
+typedef LSILOGICDEVICE *PLSILOGICDEVICE;
+
+/** Pointer to a task state. */
+typedef struct LSILOGICREQ *PLSILOGICREQ;
+
+/**
+ * Device instance data for the emulated SCSI controller.
+ */
+typedef struct LSILOGICSCSI
+{
+ /** PCI device structure. */
+ PDMPCIDEV PciDev;
+ /** Pointer to the device instance. - R3 ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance. - R0 ptr. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance. - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+
+ /** Flag whether the GC part of the device is enabled. */
+ bool fGCEnabled;
+ /** Flag whether the R0 part of the device is enabled. */
+ bool fR0Enabled;
+
+ /** The state the controller is currently in. */
+ LSILOGICSTATE enmState;
+ /** Who needs to init the driver to get into operational state. */
+ LSILOGICWHOINIT enmWhoInit;
+ /** Flag whether we are in doorbell function. */
+ LSILOGICDOORBELLSTATE enmDoorbellState;
+ /** Flag whether diagnostic access is enabled. */
+ bool fDiagnosticEnabled;
+ /** Flag whether a notification was send to R3. */
+ bool fNotificationSent;
+ /** Flag whether the guest enabled event notification from the IOC. */
+ bool fEventNotificationEnabled;
+ /** Flag whether the diagnostic address and RW registers are enabled. */
+ bool fDiagRegsEnabled;
+
+ /** Queue to send tasks to R3. - R3 ptr */
+ R3PTRTYPE(PPDMQUEUE) pNotificationQueueR3;
+ /** Queue to send tasks to R3. - R0 ptr */
+ R0PTRTYPE(PPDMQUEUE) pNotificationQueueR0;
+ /** Queue to send tasks to R3. - RC ptr */
+ RCPTRTYPE(PPDMQUEUE) pNotificationQueueRC;
+
+ /** Number of device states allocated. */
+ uint32_t cDeviceStates;
+
+ /** States for attached devices. */
+ R3PTRTYPE(PLSILOGICDEVICE) paDeviceStates;
+#if HC_ARCH_BITS == 32
+ RTR3PTR R3PtrPadding0;
+#endif
+
+ /** Interrupt mask. */
+ volatile uint32_t uInterruptMask;
+ /** Interrupt status register. */
+ volatile uint32_t uInterruptStatus;
+
+ /** Buffer for messages which are passed through the doorbell using the
+ * handshake method. */
+ uint32_t aMessage[sizeof(MptConfigurationRequest)]; /** @todo r=bird: Looks like 4 tims the required size? Please explain in comment if this correct... */
+ /** Actual position in the buffer. */
+ uint32_t iMessage;
+ /** Size of the message which is given in the doorbell message in dwords. */
+ uint32_t cMessage;
+
+ /** Reply buffer.
+ * @note 60 bytes */
+ MptReplyUnion ReplyBuffer;
+ /** Next entry to read. */
+ uint32_t uNextReplyEntryRead;
+ /** Size of the reply in the buffer in 16bit words. */
+ uint32_t cReplySize;
+
+ /** The fault code of the I/O controller if we are in the fault state. */
+ uint16_t u16IOCFaultCode;
+
+ /** I/O port address the device is mapped to. */
+ RTIOPORT IOPortBase;
+ /** MMIO address the device is mapped to. */
+ RTGCPHYS GCPhysMMIOBase;
+
+ /** Upper 32 bits of the message frame address to locate requests in guest memory. */
+ uint32_t u32HostMFAHighAddr;
+ /** Upper 32 bits of the sense buffer address. */
+ uint32_t u32SenseBufferHighAddr;
+ /** Maximum number of devices the driver reported he can handle. */
+ uint8_t cMaxDevices;
+ /** Maximum number of buses the driver reported he can handle. */
+ uint8_t cMaxBuses;
+ /** Current size of reply message frames in the guest. */
+ uint16_t cbReplyFrame;
+
+ /** Next key to write in the sequence to get access
+ * to diagnostic memory. */
+ uint32_t iDiagnosticAccess;
+
+ /** Number entries allocated for the reply queue. */
+ uint32_t cReplyQueueEntries;
+ /** Number entries allocated for the outstanding request queue. */
+ uint32_t cRequestQueueEntries;
+
+
+ /** Critical section protecting the reply post queue. */
+ PDMCRITSECT ReplyPostQueueCritSect;
+ /** Critical section protecting the reply free queue. */
+ PDMCRITSECT ReplyFreeQueueCritSect;
+ /** Critical section protecting the request queue against
+ * concurrent access from the guest. */
+ PDMCRITSECT RequestQueueCritSect;
+ /** Critical section protecting the reply free queue against
+ * concurrent write access from the guest. */
+ PDMCRITSECT ReplyFreeQueueWriteCritSect;
+
+ /** Pointer to the start of the reply free queue - R3. */
+ R3PTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseR3;
+ /** Pointer to the start of the reply post queue - R3. */
+ R3PTRTYPE(volatile uint32_t *) pReplyPostQueueBaseR3;
+ /** Pointer to the start of the request queue - R3. */
+ R3PTRTYPE(volatile uint32_t *) pRequestQueueBaseR3;
+
+ /** Pointer to the start of the reply queue - R0. */
+ R0PTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseR0;
+ /** Pointer to the start of the reply queue - R0. */
+ R0PTRTYPE(volatile uint32_t *) pReplyPostQueueBaseR0;
+ /** Pointer to the start of the request queue - R0. */
+ R0PTRTYPE(volatile uint32_t *) pRequestQueueBaseR0;
+
+ /** Pointer to the start of the reply queue - RC. */
+ RCPTRTYPE(volatile uint32_t *) pReplyFreeQueueBaseRC;
+ /** Pointer to the start of the reply queue - RC. */
+ RCPTRTYPE(volatile uint32_t *) pReplyPostQueueBaseRC;
+ /** Pointer to the start of the request queue - RC. */
+ RCPTRTYPE(volatile uint32_t *) pRequestQueueBaseRC;
+ /** End these RC pointers on a 64-bit boundrary. */
+ RTRCPTR RCPtrPadding1;
+
+ /** Next free entry in the reply queue the guest can write a address to. */
+ volatile uint32_t uReplyFreeQueueNextEntryFreeWrite;
+ /** Next valid entry the controller can read a valid address for reply frames from. */
+ volatile uint32_t uReplyFreeQueueNextAddressRead;
+
+ /** Next free entry in the reply queue the guest can write a address to. */
+ volatile uint32_t uReplyPostQueueNextEntryFreeWrite;
+ /** Next valid entry the controller can read a valid address for reply frames from. */
+ volatile uint32_t uReplyPostQueueNextAddressRead;
+
+ /** Next free entry the guest can write a address to a request frame to. */
+ volatile uint32_t uRequestQueueNextEntryFreeWrite;
+ /** Next valid entry the controller can read a valid address for request frames from. */
+ volatile uint32_t uRequestQueueNextAddressRead;
+
+ /** Emulated controller type */
+ LSILOGICCTRLTYPE enmCtrlType;
+ /** Handle counter */
+ uint16_t u16NextHandle;
+
+ /** Number of ports this controller has. */
+ uint8_t cPorts;
+
+ /** BIOS emulation. */
+ VBOXSCSI VBoxSCSI;
+
+ /** Status LUN: The base interface. */
+ PDMIBASE IBase;
+ /** Status LUN: Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Status LUN: Media Notifys. */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+ /** Pointer to the configuration page area. */
+ R3PTRTYPE(PMptConfigurationPagesSupported) pConfigurationPages;
+
+ /** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when
+ * a port is entering the idle state. */
+ bool volatile fSignalIdle;
+ /** Flag whether we have tasks which need to be processed again- */
+ bool volatile fRedo;
+ /** Flag whether the worker thread is sleeping. */
+ volatile bool fWrkThreadSleeping;
+ /** Flag whether a request from the BIOS is pending which the
+ * worker thread needs to process. */
+ volatile bool fBiosReqPending;
+#if HC_ARCH_BITS == 64
+ /** Alignment padding. */
+ bool afPadding2[4];
+#endif
+ /** List of tasks which can be redone. */
+ R3PTRTYPE(volatile PLSILOGICREQ) pTasksRedoHead;
+
+ /** Current address to read from or write to in the diagnostic memory region. */
+ uint32_t u32DiagMemAddr;
+ /** Current size of the memory regions. */
+ uint32_t cbMemRegns;
+
+#if HC_ARCH_BITS ==32
+ uint32_t u32Padding3;
+#endif
+
+ union
+ {
+ /** List of memory regions - PLSILOGICMEMREGN. */
+ RTLISTANCHOR ListMemRegns;
+ uint8_t u8Padding[2 * sizeof(RTUINTPTR)];
+ };
+
+ /** The support driver session handle. */
+ R3R0PTRTYPE(PSUPDRVSESSION) pSupDrvSession;
+ /** Worker thread. */
+ R3PTRTYPE(PPDMTHREAD) pThreadWrk;
+ /** The event semaphore the processing thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+} LSILOGISCSI;
+
+/**
+ * Task state object which holds all necessary data while
+ * processing the request from the guest.
+ */
+typedef struct LSILOGICREQ
+{
+ /** I/O request handle. */
+ PDMMEDIAEXIOREQ hIoReq;
+ /** Next in the redo list. */
+ PLSILOGICREQ pRedoNext;
+ /** Target device. */
+ PLSILOGICDEVICE pTargetDevice;
+ /** The message request from the guest. */
+ MptRequestUnion GuestRequest;
+ /** Address of the message request frame in guests memory.
+ * Used to read the S/G entries in the second step. */
+ RTGCPHYS GCPhysMessageFrameAddr;
+ /** Physical start address of the S/G list. */
+ RTGCPHYS GCPhysSgStart;
+ /** Chain offset */
+ uint32_t cChainOffset;
+ /** Pointer to the sense buffer. */
+ uint8_t abSenseBuffer[18];
+ /** Flag whether the request was issued from the BIOS. */
+ bool fBIOS;
+ /** SCSI status code. */
+ uint8_t u8ScsiSts;
+} LSILOGICREQ;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+static void lsilogicR3InitializeConfigurationPages(PLSILOGICSCSI pThis);
+static void lsilogicR3ConfigurationPagesFree(PLSILOGICSCSI pThis);
+static int lsilogicR3ProcessConfigurationRequest(PLSILOGICSCSI pThis, PMptConfigurationRequest pConfigurationReq,
+ PMptConfigurationReply pReply);
+#endif
+RT_C_DECLS_END
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Key sequence the guest has to write to enable access
+ * to diagnostic memory. */
+static const uint8_t g_lsilogicDiagnosticAccess[] = {0x04, 0x0b, 0x02, 0x07, 0x0d};
+
+/**
+ * Updates the status of the interrupt pin of the device.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicUpdateInterrupt(PLSILOGICSCSI pThis)
+{
+ uint32_t uIntSts;
+
+ LogFlowFunc(("Updating interrupts\n"));
+
+ /* Mask out doorbell status so that it does not affect interrupt updating. */
+ uIntSts = (ASMAtomicReadU32(&pThis->uInterruptStatus) & ~LSILOGIC_REG_HOST_INTR_STATUS_DOORBELL_STS);
+ /* Check maskable interrupts. */
+ uIntSts &= ~(ASMAtomicReadU32(&pThis->uInterruptMask) & ~LSILOGIC_REG_HOST_INTR_MASK_IRQ_ROUTING);
+
+ if (uIntSts)
+ {
+ LogFlowFunc(("Setting interrupt\n"));
+ PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 1);
+ }
+ else
+ {
+ LogFlowFunc(("Clearing interrupt\n"));
+ PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 0);
+ }
+}
+
+/**
+ * Sets a given interrupt status bit in the status register and
+ * updates the interrupt status.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param uStatus The status bit to set.
+ */
+DECLINLINE(void) lsilogicSetInterrupt(PLSILOGICSCSI pThis, uint32_t uStatus)
+{
+ ASMAtomicOrU32(&pThis->uInterruptStatus, uStatus);
+ lsilogicUpdateInterrupt(pThis);
+}
+
+/**
+ * Clears a given interrupt status bit in the status register and
+ * updates the interrupt status.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param uStatus The status bit to set.
+ */
+DECLINLINE(void) lsilogicClearInterrupt(PLSILOGICSCSI pThis, uint32_t uStatus)
+{
+ ASMAtomicAndU32(&pThis->uInterruptStatus, ~uStatus);
+ lsilogicUpdateInterrupt(pThis);
+}
+
+
+#ifdef IN_RING3
+/**
+ * Sets the I/O controller into fault state and sets the fault code.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param uIOCFaultCode Fault code to set.
+ */
+DECLINLINE(void) lsilogicSetIOCFaultCode(PLSILOGICSCSI pThis, uint16_t uIOCFaultCode)
+{
+ if (pThis->enmState != LSILOGICSTATE_FAULT)
+ {
+ LogFunc(("Setting I/O controller into FAULT state: uIOCFaultCode=%u\n", uIOCFaultCode));
+ pThis->enmState = LSILOGICSTATE_FAULT;
+ pThis->u16IOCFaultCode = uIOCFaultCode;
+ }
+ else
+ LogFunc(("We are already in FAULT state\n"));
+}
+#endif /* IN_RING3 */
+
+
+/**
+ * Returns the number of frames in the reply free queue.
+ *
+ * @returns Number of frames in the reply free queue.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+DECLINLINE(uint32_t) lsilogicReplyFreeQueueGetFrameCount(PLSILOGICSCSI pThis)
+{
+ uint32_t cReplyFrames = 0;
+
+ if (pThis->uReplyFreeQueueNextAddressRead <= pThis->uReplyFreeQueueNextEntryFreeWrite)
+ cReplyFrames = pThis->uReplyFreeQueueNextEntryFreeWrite - pThis->uReplyFreeQueueNextAddressRead;
+ else
+ cReplyFrames = pThis->cReplyQueueEntries - pThis->uReplyFreeQueueNextAddressRead + pThis->uReplyFreeQueueNextEntryFreeWrite;
+
+ return cReplyFrames;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Returns the number of free entries in the reply post queue.
+ *
+ * @returns Number of frames in the reply free queue.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+DECLINLINE(uint32_t) lsilogicReplyPostQueueGetFrameCount(PLSILOGICSCSI pThis)
+{
+ uint32_t cReplyFrames = 0;
+
+ if (pThis->uReplyPostQueueNextAddressRead <= pThis->uReplyPostQueueNextEntryFreeWrite)
+ cReplyFrames = pThis->cReplyQueueEntries - pThis->uReplyPostQueueNextEntryFreeWrite + pThis->uReplyPostQueueNextAddressRead;
+ else
+ cReplyFrames = pThis->uReplyPostQueueNextEntryFreeWrite - pThis->uReplyPostQueueNextAddressRead;
+
+ return cReplyFrames;
+}
+
+
+/**
+ * Performs a hard reset on the controller.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static int lsilogicR3HardReset(PLSILOGICSCSI pThis)
+{
+ pThis->enmState = LSILOGICSTATE_RESET;
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+
+ /* The interrupts are masked out. */
+ pThis->uInterruptMask |= LSILOGIC_REG_HOST_INTR_MASK_DOORBELL
+ | LSILOGIC_REG_HOST_INTR_MASK_REPLY;
+ /* Reset interrupt states. */
+ pThis->uInterruptStatus = 0;
+ lsilogicUpdateInterrupt(pThis);
+
+ /* Reset the queues. */
+ pThis->uReplyFreeQueueNextEntryFreeWrite = 0;
+ pThis->uReplyFreeQueueNextAddressRead = 0;
+ pThis->uReplyPostQueueNextEntryFreeWrite = 0;
+ pThis->uReplyPostQueueNextAddressRead = 0;
+ pThis->uRequestQueueNextEntryFreeWrite = 0;
+ pThis->uRequestQueueNextAddressRead = 0;
+
+ /* Disable diagnostic access. */
+ pThis->iDiagnosticAccess = 0;
+ pThis->fDiagnosticEnabled = false;
+ pThis->fDiagRegsEnabled = false;
+
+ /* Set default values. */
+ pThis->cMaxDevices = pThis->cDeviceStates;
+ pThis->cMaxBuses = 1;
+ pThis->cbReplyFrame = 128; /** @todo Figure out where it is needed. */
+ pThis->u16NextHandle = 1;
+ pThis->u32DiagMemAddr = 0;
+
+ lsilogicR3ConfigurationPagesFree(pThis);
+ lsilogicR3InitializeConfigurationPages(pThis);
+
+ /* Mark that we finished performing the reset. */
+ pThis->enmState = LSILOGICSTATE_READY;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Frees the configuration pages if allocated.
+ *
+ * @returns nothing.
+ * @param pThis The LsiLogic controller instance
+ */
+static void lsilogicR3ConfigurationPagesFree(PLSILOGICSCSI pThis)
+{
+
+ if (pThis->pConfigurationPages)
+ {
+ /* Destroy device list if we emulate a SAS controller. */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PMptConfigurationPagesSas pSasPages = &pThis->pConfigurationPages->u.SasPages;
+ PMptSASDevice pSASDeviceCurr = pSasPages->pSASDeviceHead;
+
+ while (pSASDeviceCurr)
+ {
+ PMptSASDevice pFree = pSASDeviceCurr;
+
+ pSASDeviceCurr = pSASDeviceCurr->pNext;
+ RTMemFree(pFree);
+ }
+ if (pSasPages->paPHYs)
+ RTMemFree(pSasPages->paPHYs);
+ if (pSasPages->pManufacturingPage7)
+ RTMemFree(pSasPages->pManufacturingPage7);
+ if (pSasPages->pSASIOUnitPage0)
+ RTMemFree(pSasPages->pSASIOUnitPage0);
+ if (pSasPages->pSASIOUnitPage1)
+ RTMemFree(pSasPages->pSASIOUnitPage1);
+ }
+
+ RTMemFree(pThis->pConfigurationPages);
+ }
+}
+
+/**
+ * Finishes a context reply.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param u32MessageContext The message context ID to post.
+ */
+static void lsilogicR3FinishContextReply(PLSILOGICSCSI pThis, uint32_t u32MessageContext)
+{
+ int rc;
+
+ LogFlowFunc(("pThis=%#p u32MessageContext=%#x\n", pThis, u32MessageContext));
+
+ AssertMsg(pThis->enmDoorbellState == LSILOGICDOORBELLSTATE_NOT_IN_USE, ("We are in a doorbell function\n"));
+
+ /* Write message context ID into reply post queue. */
+ rc = PDMCritSectEnter(&pThis->ReplyPostQueueCritSect, VINF_SUCCESS);
+ AssertRC(rc);
+
+ /* Check for a entry in the queue. */
+ if (!lsilogicReplyPostQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMCritSectLeave(&pThis->ReplyPostQueueCritSect);
+ return;
+ }
+
+ /* We have a context reply. */
+ ASMAtomicWriteU32(&pThis->CTX_SUFF(pReplyPostQueueBase)[pThis->uReplyPostQueueNextEntryFreeWrite], u32MessageContext);
+ ASMAtomicIncU32(&pThis->uReplyPostQueueNextEntryFreeWrite);
+ pThis->uReplyPostQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+
+ /* Set interrupt. */
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+
+ PDMCritSectLeave(&pThis->ReplyPostQueueCritSect);
+}
+
+
+/**
+ * Takes necessary steps to finish a reply frame.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pReply Pointer to the reply message.
+ * @param fForceReplyFifo Flag whether the use of the reply post fifo is forced.
+ */
+static void lsilogicFinishAddressReply(PLSILOGICSCSI pThis, PMptReplyUnion pReply, bool fForceReplyFifo)
+{
+ /*
+ * If we are in a doorbell function we set the reply size now and
+ * set the system doorbell status interrupt to notify the guest that
+ * we are ready to send the reply.
+ */
+ if (pThis->enmDoorbellState != LSILOGICDOORBELLSTATE_NOT_IN_USE && !fForceReplyFifo)
+ {
+ /* Set size of the reply in 16bit words. The size in the reply is in 32bit dwords. */
+ pThis->cReplySize = pReply->Header.u8MessageLength * 2;
+ Log(("%s: cReplySize=%u\n", __FUNCTION__, pThis->cReplySize));
+ pThis->uNextReplyEntryRead = 0;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+ else
+ {
+ /*
+ * The reply queues are only used if the request was fetched from the request queue.
+ * Requests from the request queue are always transferred to R3. So it is not possible
+ * that this case happens in R0 or GC.
+ */
+# ifdef IN_RING3
+ int rc;
+ /* Grab a free reply message from the queue. */
+ rc = PDMCritSectEnter(&pThis->ReplyFreeQueueCritSect, VINF_SUCCESS);
+ AssertRC(rc);
+
+ /* Check for a free reply frame. */
+ if (!lsilogicReplyFreeQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMCritSectLeave(&pThis->ReplyFreeQueueCritSect);
+ return;
+ }
+
+ uint32_t u32ReplyFrameAddressLow = pThis->CTX_SUFF(pReplyFreeQueueBase)[pThis->uReplyFreeQueueNextAddressRead];
+
+ pThis->uReplyFreeQueueNextAddressRead++;
+ pThis->uReplyFreeQueueNextAddressRead %= pThis->cReplyQueueEntries;
+
+ PDMCritSectLeave(&pThis->ReplyFreeQueueCritSect);
+
+ /* Build 64bit physical address. */
+ RTGCPHYS GCPhysReplyMessage = LSILOGIC_RTGCPHYS_FROM_U32(pThis->u32HostMFAHighAddr, u32ReplyFrameAddressLow);
+ size_t cbReplyCopied = (pThis->cbReplyFrame < sizeof(MptReplyUnion)) ? pThis->cbReplyFrame : sizeof(MptReplyUnion);
+
+ /* Write reply to guest memory. */
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhysReplyMessage, pReply, cbReplyCopied);
+
+ /* Write low 32bits of reply frame into post reply queue. */
+ rc = PDMCritSectEnter(&pThis->ReplyPostQueueCritSect, VINF_SUCCESS);
+ AssertRC(rc);
+
+ /* Check for a entry in the queue. */
+ if (!lsilogicReplyPostQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMCritSectLeave(&pThis->ReplyPostQueueCritSect);
+ return;
+ }
+
+ /* We have a address reply. Set the 31th bit to indicate that. */
+ ASMAtomicWriteU32(&pThis->CTX_SUFF(pReplyPostQueueBase)[pThis->uReplyPostQueueNextEntryFreeWrite],
+ RT_BIT(31) | (u32ReplyFrameAddressLow >> 1));
+ ASMAtomicIncU32(&pThis->uReplyPostQueueNextEntryFreeWrite);
+ pThis->uReplyPostQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+
+ if (fForceReplyFifo)
+ {
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+
+ /* Set interrupt. */
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+
+ PDMCritSectLeave(&pThis->ReplyPostQueueCritSect);
+# else
+ AssertMsgFailed(("This is not allowed to happen.\n"));
+# endif
+ }
+}
+
+
+/**
+ * Tries to find a memory region which covers the given address.
+ *
+ * @returns Pointer to memory region or NULL if not found.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param u32Addr The 32bit address to search for.
+ */
+static PLSILOGICMEMREGN lsilogicR3MemRegionFindByAddr(PLSILOGICSCSI pThis, uint32_t u32Addr)
+{
+ PLSILOGICMEMREGN pRegion = NULL;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThis->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ if ( u32Addr >= pIt->u32AddrStart
+ && u32Addr <= pIt->u32AddrEnd)
+ {
+ pRegion = pIt;
+ break;
+ }
+ }
+
+ return pRegion;
+}
+
+/**
+ * Frees all allocated memory regions.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3MemRegionsFree(PLSILOGICSCSI pThis)
+{
+ PLSILOGICMEMREGN pItNext;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEachSafe(&pThis->ListMemRegns, pIt, pItNext, LSILOGICMEMREGN, NodeList)
+ {
+ RTListNodeRemove(&pIt->NodeList);
+ RTMemFree(pIt);
+ }
+ pThis->cbMemRegns = 0;
+}
+
+/**
+ * Inserts a given memory region into the list.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pRegion The region to insert.
+ */
+static void lsilogicR3MemRegionInsert(PLSILOGICSCSI pThis, PLSILOGICMEMREGN pRegion)
+{
+ bool fInserted = false;
+
+ /* Insert at the right position. */
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThis->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ if (pRegion->u32AddrEnd < pIt->u32AddrStart)
+ {
+ RTListNodeInsertBefore(&pIt->NodeList, &pRegion->NodeList);
+ fInserted = true;
+ break;
+ }
+ }
+ if (!fInserted)
+ RTListAppend(&pThis->ListMemRegns, &pRegion->NodeList);
+}
+
+/**
+ * Count number of memory regions.
+ *
+ * @returns Number of memory regions.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static uint32_t lsilogicR3MemRegionsCount(PLSILOGICSCSI pThis)
+{
+ uint32_t cRegions = 0;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThis->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ cRegions++;
+ }
+
+ return cRegions;
+}
+
+/**
+ * Handles a write to the diagnostic data register.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param u32Data Data to write.
+ */
+static void lsilogicR3DiagRegDataWrite(PLSILOGICSCSI pThis, uint32_t u32Data)
+{
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThis, pThis->u32DiagMemAddr);
+
+ if (pRegion)
+ {
+ uint32_t offRegion = pThis->u32DiagMemAddr - pRegion->u32AddrStart;
+
+ AssertMsg( offRegion % 4 == 0
+ && pThis->u32DiagMemAddr <= pRegion->u32AddrEnd,
+ ("Region offset not on a word boundary or crosses memory region\n"));
+
+ offRegion /= 4;
+ pRegion->au32Data[offRegion] = u32Data;
+ }
+ else
+ {
+ pRegion = NULL;
+
+ /* Create new region, first check whether we can extend another region. */
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThis->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ if (pThis->u32DiagMemAddr == pIt->u32AddrEnd + sizeof(uint32_t))
+ {
+ pRegion = pIt;
+ break;
+ }
+ }
+
+ if (pRegion)
+ {
+ /* Reallocate. */
+ RTListNodeRemove(&pRegion->NodeList);
+
+ uint32_t cRegionSizeOld = (pRegion->u32AddrEnd - pRegion->u32AddrStart) / 4 + 1;
+ uint32_t cRegionSizeNew = cRegionSizeOld + 512;
+
+ if (pThis->cbMemRegns + 512 * sizeof(uint32_t) < LSILOGIC_MEMORY_REGIONS_MAX)
+ {
+ PLSILOGICMEMREGN pRegionNew;
+ pRegionNew = (PLSILOGICMEMREGN)RTMemRealloc(pRegion, RT_UOFFSETOF_DYN(LSILOGICMEMREGN, au32Data[cRegionSizeNew]));
+ if (pRegionNew)
+ {
+ pRegion = pRegionNew;
+ memset(&pRegion->au32Data[cRegionSizeOld], 0, 512 * sizeof(uint32_t));
+ pRegion->au32Data[cRegionSizeOld] = u32Data;
+ pRegion->u32AddrEnd = pRegion->u32AddrStart + (cRegionSizeNew - 1) * sizeof(uint32_t);
+ pThis->cbMemRegns += 512 * sizeof(uint32_t);
+ }
+ /* else: Silently fail, there is nothing we can do here and the guest might work nevertheless. */
+
+ lsilogicR3MemRegionInsert(pThis, pRegion);
+ }
+ }
+ else
+ {
+ if (pThis->cbMemRegns + 512 * sizeof(uint32_t) < LSILOGIC_MEMORY_REGIONS_MAX)
+ {
+ /* Create completely new. */
+ pRegion = (PLSILOGICMEMREGN)RTMemAllocZ(RT_OFFSETOF(LSILOGICMEMREGN, au32Data[512]));
+ if (pRegion)
+ {
+ pRegion->u32AddrStart = pThis->u32DiagMemAddr;
+ pRegion->u32AddrEnd = pRegion->u32AddrStart + (512 - 1) * sizeof(uint32_t);
+ pRegion->au32Data[0] = u32Data;
+ pThis->cbMemRegns += 512 * sizeof(uint32_t);
+
+ lsilogicR3MemRegionInsert(pThis, pRegion);
+ }
+ /* else: Silently fail, there is nothing we can do here and the guest might work nevertheless. */
+ }
+ }
+
+ }
+
+ /* Memory access is always 32bit big. */
+ pThis->u32DiagMemAddr += sizeof(uint32_t);
+}
+
+/**
+ * Handles a read from the diagnostic data register.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pu32Data Where to store the data.
+ */
+static void lsilogicR3DiagRegDataRead(PLSILOGICSCSI pThis, uint32_t *pu32Data)
+{
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThis, pThis->u32DiagMemAddr);
+
+ if (pRegion)
+ {
+ uint32_t offRegion = pThis->u32DiagMemAddr - pRegion->u32AddrStart;
+
+ AssertMsg( offRegion % 4 == 0
+ && pThis->u32DiagMemAddr <= pRegion->u32AddrEnd,
+ ("Region offset not on a word boundary or crosses memory region\n"));
+
+ offRegion /= 4;
+ *pu32Data = pRegion->au32Data[offRegion];
+ }
+ else /* No region, default value 0. */
+ *pu32Data = 0;
+
+ /* Memory access is always 32bit big. */
+ pThis->u32DiagMemAddr += sizeof(uint32_t);
+}
+
+/**
+ * Handles a write to the diagnostic memory address register.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param u32Addr Address to write.
+ */
+static void lsilogicR3DiagRegAddressWrite(PLSILOGICSCSI pThis, uint32_t u32Addr)
+{
+ pThis->u32DiagMemAddr = u32Addr & ~UINT32_C(0x3); /* 32bit alignment. */
+}
+
+/**
+ * Handles a read from the diagnostic memory address register.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pu32Addr Where to store the current address.
+ */
+static void lsilogicR3DiagRegAddressRead(PLSILOGICSCSI pThis, uint32_t *pu32Addr)
+{
+ *pu32Addr = pThis->u32DiagMemAddr;
+}
+
+/**
+ * Processes a given Request from the guest
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pMessageHdr Pointer to the message header of the request.
+ * @param pReply Pointer to the reply.
+ */
+static int lsilogicR3ProcessMessageRequest(PLSILOGICSCSI pThis, PMptMessageHdr pMessageHdr, PMptReplyUnion pReply)
+{
+ int rc = VINF_SUCCESS;
+ bool fForceReplyPostFifo = false;
+
+# ifdef LOG_ENABLED
+ if (pMessageHdr->u8Function < RT_ELEMENTS(g_apszMPTFunctionNames))
+ Log(("Message request function: %s\n", g_apszMPTFunctionNames[pMessageHdr->u8Function]));
+ else
+ Log(("Message request function: <unknown>\n"));
+# endif
+
+ memset(pReply, 0, sizeof(MptReplyUnion));
+
+ switch (pMessageHdr->u8Function)
+ {
+ case MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT:
+ {
+ PMptSCSITaskManagementRequest pTaskMgmtReq = (PMptSCSITaskManagementRequest)pMessageHdr;
+
+ LogFlow(("u8TaskType=%u\n", pTaskMgmtReq->u8TaskType));
+ LogFlow(("u32TaskMessageContext=%#x\n", pTaskMgmtReq->u32TaskMessageContext));
+
+ pReply->SCSITaskManagement.u8MessageLength = 6; /* 6 32bit dwords. */
+ pReply->SCSITaskManagement.u8TaskType = pTaskMgmtReq->u8TaskType;
+ pReply->SCSITaskManagement.u32TerminationCount = 0;
+ fForceReplyPostFifo = true;
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_IOC_INIT:
+ {
+ /*
+ * This request sets the I/O controller to the
+ * operational state.
+ */
+ PMptIOCInitRequest pIOCInitReq = (PMptIOCInitRequest)pMessageHdr;
+
+ /* Update configuration values. */
+ pThis->enmWhoInit = (LSILOGICWHOINIT)pIOCInitReq->u8WhoInit;
+ pThis->cbReplyFrame = pIOCInitReq->u16ReplyFrameSize;
+ pThis->cMaxBuses = pIOCInitReq->u8MaxBuses;
+ pThis->cMaxDevices = pIOCInitReq->u8MaxDevices;
+ pThis->u32HostMFAHighAddr = pIOCInitReq->u32HostMfaHighAddr;
+ pThis->u32SenseBufferHighAddr = pIOCInitReq->u32SenseBufferHighAddr;
+
+ if (pThis->enmState == LSILOGICSTATE_READY)
+ {
+ pThis->enmState = LSILOGICSTATE_OPERATIONAL;
+ }
+
+ /* Return reply. */
+ pReply->IOCInit.u8MessageLength = 5;
+ pReply->IOCInit.u8WhoInit = pThis->enmWhoInit;
+ pReply->IOCInit.u8MaxDevices = pThis->cMaxDevices;
+ pReply->IOCInit.u8MaxBuses = pThis->cMaxBuses;
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS:
+ {
+ pReply->IOCFacts.u8MessageLength = 15; /* 15 32bit dwords. */
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ pReply->IOCFacts.u16MessageVersion = 0x0102; /* Version from the specification. */
+ pReply->IOCFacts.u8NumberOfPorts = pThis->cPorts;
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ pReply->IOCFacts.u16MessageVersion = 0x0105; /* Version from the specification. */
+ pReply->IOCFacts.u8NumberOfPorts = pThis->cPorts;
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+
+ pReply->IOCFacts.u8IOCNumber = 0; /* PCI function number. */
+ pReply->IOCFacts.u16IOCExceptions = 0;
+ pReply->IOCFacts.u8MaxChainDepth = LSILOGICSCSI_MAXIMUM_CHAIN_DEPTH;
+ pReply->IOCFacts.u8WhoInit = pThis->enmWhoInit;
+ pReply->IOCFacts.u8BlockSize = 12; /* Block size in 32bit dwords. This is the largest request we can get (SCSI I/O). */
+ pReply->IOCFacts.u8Flags = 0; /* Bit 0 is set if the guest must upload the FW prior to using the controller. Obviously not needed here. */
+ pReply->IOCFacts.u16ReplyQueueDepth = pThis->cReplyQueueEntries - 1; /* One entry is always free. */
+ pReply->IOCFacts.u16RequestFrameSize = 128; /** @todo Figure out where it is needed. */
+ pReply->IOCFacts.u32CurrentHostMFAHighAddr = pThis->u32HostMFAHighAddr;
+ pReply->IOCFacts.u16GlobalCredits = pThis->cRequestQueueEntries - 1; /* One entry is always free. */
+
+ pReply->IOCFacts.u8EventState = 0; /* Event notifications not enabled. */
+ pReply->IOCFacts.u32CurrentSenseBufferHighAddr = pThis->u32SenseBufferHighAddr;
+ pReply->IOCFacts.u16CurReplyFrameSize = pThis->cbReplyFrame;
+ pReply->IOCFacts.u8MaxDevices = pThis->cMaxDevices;
+ pReply->IOCFacts.u8MaxBuses = pThis->cMaxBuses;
+
+ /* Check for a valid firmware image in the IOC memory which was downlaoded by tzhe guest earlier. */
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThis, LSILOGIC_FWIMGHDR_LOAD_ADDRESS);
+
+ if (pRegion)
+ {
+ uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart) / 4;
+ PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr];
+
+ /* Check for the signature. */
+ /** @todo Checksum validation. */
+ if ( pFwImgHdr->u32Signature1 == LSILOGIC_FWIMGHDR_SIGNATURE1
+ && pFwImgHdr->u32Signature2 == LSILOGIC_FWIMGHDR_SIGNATURE2
+ && pFwImgHdr->u32Signature3 == LSILOGIC_FWIMGHDR_SIGNATURE3)
+ {
+ LogFlowFunc(("IOC Facts: Found valid firmware image header in memory, using version (%#x), size (%d) and product ID (%#x) from there\n",
+ pFwImgHdr->u32FwVersion, pFwImgHdr->u32ImageSize, pFwImgHdr->u16ProductId));
+
+ pReply->IOCFacts.u16ProductID = pFwImgHdr->u16ProductId;
+ pReply->IOCFacts.u32FwImageSize = pFwImgHdr->u32ImageSize;
+ pReply->IOCFacts.u32FWVersion = pFwImgHdr->u32FwVersion;
+ }
+ }
+ else
+ {
+ pReply->IOCFacts.u16ProductID = 0xcafe; /* Our own product ID :) */
+ pReply->IOCFacts.u32FwImageSize = 0; /* No image needed. */
+ pReply->IOCFacts.u32FWVersion = 0;
+ }
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS:
+ {
+ PMptPortFactsRequest pPortFactsReq = (PMptPortFactsRequest)pMessageHdr;
+
+ pReply->PortFacts.u8MessageLength = 10;
+ pReply->PortFacts.u8PortNumber = pPortFactsReq->u8PortNumber;
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ /* This controller only supports one bus with bus number 0. */
+ if (pPortFactsReq->u8PortNumber >= pThis->cPorts)
+ {
+ pReply->PortFacts.u8PortType = 0; /* Not existant. */
+ }
+ else
+ {
+ pReply->PortFacts.u8PortType = 0x01; /* SCSI Port. */
+ pReply->PortFacts.u16MaxDevices = LSILOGICSCSI_PCI_SPI_DEVICES_PER_BUS_MAX;
+ pReply->PortFacts.u16ProtocolFlags = RT_BIT(3) | RT_BIT(0); /* SCSI initiator and LUN supported. */
+ pReply->PortFacts.u16PortSCSIID = 7; /* Default */
+ pReply->PortFacts.u16MaxPersistentIDs = 0;
+ pReply->PortFacts.u16MaxPostedCmdBuffers = 0; /* Only applies for target mode which we dont support. */
+ pReply->PortFacts.u16MaxLANBuckets = 0; /* Only for the LAN controller. */
+ }
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ if (pPortFactsReq->u8PortNumber >= pThis->cPorts)
+ {
+ pReply->PortFacts.u8PortType = 0; /* Not existant. */
+ }
+ else
+ {
+ pReply->PortFacts.u8PortType = 0x30; /* SAS Port. */
+ pReply->PortFacts.u16MaxDevices = pThis->cPorts;
+ pReply->PortFacts.u16ProtocolFlags = RT_BIT(3) | RT_BIT(0); /* SCSI initiator and LUN supported. */
+ pReply->PortFacts.u16PortSCSIID = pThis->cPorts;
+ pReply->PortFacts.u16MaxPersistentIDs = 0;
+ pReply->PortFacts.u16MaxPostedCmdBuffers = 0; /* Only applies for target mode which we dont support. */
+ pReply->PortFacts.u16MaxLANBuckets = 0; /* Only for the LAN controller. */
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE:
+ {
+ /*
+ * The port enable request notifies the IOC to make the port available and perform
+ * appropriate discovery on the associated link.
+ */
+ PMptPortEnableRequest pPortEnableReq = (PMptPortEnableRequest)pMessageHdr;
+
+ pReply->PortEnable.u8MessageLength = 5;
+ pReply->PortEnable.u8PortNumber = pPortEnableReq->u8PortNumber;
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION:
+ {
+ PMptEventNotificationRequest pEventNotificationReq = (PMptEventNotificationRequest)pMessageHdr;
+
+ if (pEventNotificationReq->u8Switch)
+ pThis->fEventNotificationEnabled = true;
+ else
+ pThis->fEventNotificationEnabled = false;
+
+ pReply->EventNotification.u16EventDataLength = 1; /* 1 32bit D-Word. */
+ pReply->EventNotification.u8MessageLength = 8;
+ pReply->EventNotification.u8MessageFlags = (1 << 7);
+ pReply->EventNotification.u8AckRequired = 0;
+ pReply->EventNotification.u32Event = MPT_EVENT_EVENT_CHANGE;
+ pReply->EventNotification.u32EventContext = 0;
+ pReply->EventNotification.u32EventData = pThis->fEventNotificationEnabled ? 1 : 0;
+
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK:
+ {
+ AssertMsgFailed(("todo"));
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_CONFIG:
+ {
+ PMptConfigurationRequest pConfigurationReq = (PMptConfigurationRequest)pMessageHdr;
+
+ rc = lsilogicR3ProcessConfigurationRequest(pThis, pConfigurationReq, &pReply->Configuration);
+ AssertRC(rc);
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_FW_UPLOAD:
+ {
+ PMptFWUploadRequest pFWUploadReq = (PMptFWUploadRequest)pMessageHdr;
+
+ pReply->FWUpload.u8ImageType = pFWUploadReq->u8ImageType;
+ pReply->FWUpload.u8MessageLength = 6;
+ pReply->FWUpload.u32ActualImageSize = 0;
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_FW_DOWNLOAD:
+ {
+ //PMptFWDownloadRequest pFWDownloadReq = (PMptFWDownloadRequest)pMessageHdr;
+
+ pReply->FWDownload.u8MessageLength = 5;
+ LogFlowFunc(("FW Download request issued\n"));
+ break;
+ }
+ case MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST: /* Should be handled already. */
+ default:
+ AssertMsgFailed(("Invalid request function %#x\n", pMessageHdr->u8Function));
+ }
+
+ /* Copy common bits from request message frame to reply. */
+ pReply->Header.u8Function = pMessageHdr->u8Function;
+ pReply->Header.u32MessageContext = pMessageHdr->u32MessageContext;
+
+ lsilogicFinishAddressReply(pThis, pReply, fForceReplyPostFifo);
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Writes a value to a register at a given offset.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param offReg Offset of the register to write.
+ * @param u32 The value being written.
+ */
+static int lsilogicRegisterWrite(PLSILOGICSCSI pThis, uint32_t offReg, uint32_t u32)
+{
+ LogFlowFunc(("pThis=%#p offReg=%#x u32=%#x\n", pThis, offReg, u32));
+ switch (offReg)
+ {
+ case LSILOGIC_REG_REPLY_QUEUE:
+ {
+ int rc = PDMCritSectEnter(&pThis->ReplyFreeQueueWriteCritSect, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ /* Add the entry to the reply free queue. */
+ ASMAtomicWriteU32(&pThis->CTX_SUFF(pReplyFreeQueueBase)[pThis->uReplyFreeQueueNextEntryFreeWrite], u32);
+ pThis->uReplyFreeQueueNextEntryFreeWrite++;
+ pThis->uReplyFreeQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+ PDMCritSectLeave(&pThis->ReplyFreeQueueWriteCritSect);
+ break;
+ }
+ case LSILOGIC_REG_REQUEST_QUEUE:
+ {
+ int rc = PDMCritSectEnter(&pThis->RequestQueueCritSect, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint32_t uNextWrite = ASMAtomicReadU32(&pThis->uRequestQueueNextEntryFreeWrite);
+
+ ASMAtomicWriteU32(&pThis->CTX_SUFF(pRequestQueueBase)[uNextWrite], u32);
+
+ /*
+ * Don't update the value in place. It can happen that we get preempted
+ * after the increment but before the modulo.
+ * Another EMT will read the wrong value when processing the queues
+ * and hang in an endless loop creating thousands of requests.
+ */
+ uNextWrite++;
+ uNextWrite %= pThis->cRequestQueueEntries;
+ ASMAtomicWriteU32(&pThis->uRequestQueueNextEntryFreeWrite, uNextWrite);
+ PDMCritSectLeave(&pThis->RequestQueueCritSect);
+
+ /* Send notification to R3 if there is not one sent already. Do this
+ * only if the worker thread is not sleeping or might go sleeping. */
+ if (!ASMAtomicXchgBool(&pThis->fNotificationSent, true))
+ {
+ if (ASMAtomicReadBool(&pThis->fWrkThreadSleeping))
+ {
+#ifdef IN_RC
+ PPDMQUEUEITEMCORE pNotificationItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotificationQueue));
+ AssertPtr(pNotificationItem);
+ PDMQueueInsert(pThis->CTX_SUFF(pNotificationQueue), pNotificationItem);
+#else
+ LogFlowFunc(("Signal event semaphore\n"));
+ rc = SUPSemEventSignal(pThis->pSupDrvSession, pThis->hEvtProcess);
+ AssertRC(rc);
+#endif
+ }
+ }
+ break;
+ }
+ case LSILOGIC_REG_DOORBELL:
+ {
+ /*
+ * When the guest writes to this register a real device would set the
+ * doorbell status bit in the interrupt status register to indicate that the IOP
+ * has still to process the message.
+ * The guest needs to wait with posting new messages here until the bit is cleared.
+ * Because the guest is not continuing execution while we are here we can skip this.
+ */
+ if (pThis->enmDoorbellState == LSILOGICDOORBELLSTATE_NOT_IN_USE)
+ {
+ uint32_t uFunction = LSILOGIC_REG_DOORBELL_GET_FUNCTION(u32);
+
+ switch (uFunction)
+ {
+ case LSILOGIC_DOORBELL_FUNCTION_IO_UNIT_RESET:
+ case LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET:
+ {
+ /*
+ * The I/O unit reset does much more on real hardware like
+ * reloading the firmware, nothing we need to do here,
+ * so this is like the IOC message unit reset.
+ */
+ pThis->enmState = LSILOGICSTATE_RESET;
+
+ /* Reset interrupt status. */
+ pThis->uInterruptStatus = 0;
+ lsilogicUpdateInterrupt(pThis);
+
+ /* Reset the queues. */
+ pThis->uReplyFreeQueueNextEntryFreeWrite = 0;
+ pThis->uReplyFreeQueueNextAddressRead = 0;
+ pThis->uReplyPostQueueNextEntryFreeWrite = 0;
+ pThis->uReplyPostQueueNextAddressRead = 0;
+ pThis->uRequestQueueNextEntryFreeWrite = 0;
+ pThis->uRequestQueueNextAddressRead = 0;
+
+ /* Only the IOC message unit reset transisionts to the ready state. */
+ if (uFunction == LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET)
+ pThis->enmState = LSILOGICSTATE_READY;
+ break;
+ }
+ case LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE:
+ {
+ pThis->cMessage = LSILOGIC_REG_DOORBELL_GET_SIZE(u32);
+ pThis->iMessage = 0;
+ AssertMsg(pThis->cMessage <= RT_ELEMENTS(pThis->aMessage),
+ ("Message doesn't fit into the buffer, cMessage=%u", pThis->cMessage));
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_FN_HANDSHAKE;
+ /* Update the interrupt status to notify the guest that a doorbell function was started. */
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ }
+ case LSILOGIC_DOORBELL_FUNCTION_REPLY_FRAME_REMOVAL:
+ {
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_LOW;
+ /* Update the interrupt status to notify the guest that a doorbell function was started. */
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Unknown function %u to perform\n", uFunction));
+ }
+ }
+ else if (pThis->enmDoorbellState == LSILOGICDOORBELLSTATE_FN_HANDSHAKE)
+ {
+ /*
+ * We are already performing a doorbell function.
+ * Get the remaining parameters.
+ */
+ AssertMsg(pThis->iMessage < RT_ELEMENTS(pThis->aMessage), ("Message is too big to fit into the buffer\n"));
+ /*
+ * If the last byte of the message is written, force a switch to R3 because some requests might force
+ * a reply through the FIFO which cannot be handled in GC or R0.
+ */
+#ifndef IN_RING3
+ if (pThis->iMessage == pThis->cMessage - 1)
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ pThis->aMessage[pThis->iMessage++] = u32;
+#ifdef IN_RING3
+ if (pThis->iMessage == pThis->cMessage)
+ {
+ int rc = lsilogicR3ProcessMessageRequest(pThis, (PMptMessageHdr)pThis->aMessage, &pThis->ReplyBuffer);
+ AssertRC(rc);
+ }
+#endif
+ }
+ break;
+ }
+ case LSILOGIC_REG_HOST_INTR_STATUS:
+ {
+ /*
+ * Clear the bits the guest wants except the system doorbell interrupt and the IO controller
+ * status bit.
+ * The former bit is always cleared no matter what the guest writes to the register and
+ * the latter one is read only.
+ */
+ ASMAtomicAndU32(&pThis->uInterruptStatus, ~LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+
+ /*
+ * Check if there is still a doorbell function in progress. Set the
+ * system doorbell interrupt bit again if it is.
+ * We do not use lsilogicSetInterrupt here because the interrupt status
+ * is updated afterwards anyway.
+ */
+ if ( (pThis->enmDoorbellState == LSILOGICDOORBELLSTATE_FN_HANDSHAKE)
+ && (pThis->cMessage == pThis->iMessage))
+ {
+ if (pThis->uNextReplyEntryRead == pThis->cReplySize)
+ {
+ /* Reply finished. Reset doorbell in progress status. */
+ Log(("%s: Doorbell function finished\n", __FUNCTION__));
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ }
+ ASMAtomicOrU32(&pThis->uInterruptStatus, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+ else if ( pThis->enmDoorbellState != LSILOGICDOORBELLSTATE_NOT_IN_USE
+ && pThis->enmDoorbellState != LSILOGICDOORBELLSTATE_FN_HANDSHAKE)
+ {
+ /* Reply frame removal, check whether the reply free queue is empty. */
+ if ( pThis->uReplyFreeQueueNextAddressRead == pThis->uReplyFreeQueueNextEntryFreeWrite
+ && pThis->enmDoorbellState == LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW)
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ ASMAtomicOrU32(&pThis->uInterruptStatus, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+
+ lsilogicUpdateInterrupt(pThis);
+ break;
+ }
+ case LSILOGIC_REG_HOST_INTR_MASK:
+ {
+ ASMAtomicWriteU32(&pThis->uInterruptMask, u32 & LSILOGIC_REG_HOST_INTR_MASK_W_MASK);
+ lsilogicUpdateInterrupt(pThis);
+ break;
+ }
+ case LSILOGIC_REG_WRITE_SEQUENCE:
+ {
+ if (pThis->fDiagnosticEnabled)
+ {
+ /* Any value will cause a reset and disabling access. */
+ pThis->fDiagnosticEnabled = false;
+ pThis->iDiagnosticAccess = 0;
+ pThis->fDiagRegsEnabled = false;
+ }
+ else if ((u32 & 0xf) == g_lsilogicDiagnosticAccess[pThis->iDiagnosticAccess])
+ {
+ pThis->iDiagnosticAccess++;
+ if (pThis->iDiagnosticAccess == RT_ELEMENTS(g_lsilogicDiagnosticAccess))
+ {
+ /*
+ * Key sequence successfully written. Enable access to diagnostic
+ * memory and register.
+ */
+ pThis->fDiagnosticEnabled = true;
+ }
+ }
+ else
+ {
+ /* Wrong value written - reset to beginning. */
+ pThis->iDiagnosticAccess = 0;
+ }
+ break;
+ }
+ case LSILOGIC_REG_HOST_DIAGNOSTIC:
+ {
+ if (pThis->fDiagnosticEnabled)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ if (u32 & LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_ADAPTER)
+ lsilogicR3HardReset(pThis);
+ else if (u32 & LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_RW_ENABLE)
+ pThis->fDiagRegsEnabled = true;
+#endif
+ }
+ break;
+ }
+ case LSILOGIC_REG_DIAG_RW_DATA:
+ {
+ if (pThis->fDiagRegsEnabled)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ lsilogicR3DiagRegDataWrite(pThis, u32);
+#endif
+ }
+ break;
+ }
+ case LSILOGIC_REG_DIAG_RW_ADDRESS:
+ {
+ if (pThis->fDiagRegsEnabled)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ lsilogicR3DiagRegAddressWrite(pThis, u32);
+#endif
+ }
+ break;
+ }
+ default: /* Ignore. */
+ {
+ break;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads the content of a register at a given offset.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param offReg Offset of the register to read.
+ * @param pu32 Where to store the content of the register.
+ */
+static int lsilogicRegisterRead(PLSILOGICSCSI pThis, uint32_t offReg, uint32_t *pu32)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t u32 = 0;
+ Assert(!(offReg & 3));
+
+ /* Align to a 4 byte offset. */
+ switch (offReg)
+ {
+ case LSILOGIC_REG_REPLY_QUEUE:
+ {
+ rc = PDMCritSectEnter(&pThis->ReplyPostQueueCritSect, VINF_IOM_R3_MMIO_READ);
+ if (rc != VINF_SUCCESS)
+ break;
+
+ uint32_t idxReplyPostQueueWrite = ASMAtomicUoReadU32(&pThis->uReplyPostQueueNextEntryFreeWrite);
+ uint32_t idxReplyPostQueueRead = ASMAtomicUoReadU32(&pThis->uReplyPostQueueNextAddressRead);
+
+ if (idxReplyPostQueueWrite != idxReplyPostQueueRead)
+ {
+ u32 = pThis->CTX_SUFF(pReplyPostQueueBase)[idxReplyPostQueueRead];
+ idxReplyPostQueueRead++;
+ idxReplyPostQueueRead %= pThis->cReplyQueueEntries;
+ ASMAtomicWriteU32(&pThis->uReplyPostQueueNextAddressRead, idxReplyPostQueueRead);
+ }
+ else
+ {
+ /* The reply post queue is empty. Reset interrupt. */
+ u32 = UINT32_C(0xffffffff);
+ lsilogicClearInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+ }
+ PDMCritSectLeave(&pThis->ReplyPostQueueCritSect);
+
+ Log(("%s: Returning address %#x\n", __FUNCTION__, u32));
+ break;
+ }
+ case LSILOGIC_REG_DOORBELL:
+ {
+ u32 = LSILOGIC_REG_DOORBELL_SET_STATE(pThis->enmState);
+ u32 |= LSILOGIC_REG_DOORBELL_SET_USED(pThis->enmDoorbellState);
+ u32 |= LSILOGIC_REG_DOORBELL_SET_WHOINIT(pThis->enmWhoInit);
+ /*
+ * If there is a doorbell function in progress we pass the return value
+ * instead of the status code. We transfer 16bit of the reply
+ * during one read.
+ */
+ switch (pThis->enmDoorbellState)
+ {
+ case LSILOGICDOORBELLSTATE_NOT_IN_USE:
+ /* We return the status code of the I/O controller. */
+ u32 |= pThis->u16IOCFaultCode;
+ break;
+ case LSILOGICDOORBELLSTATE_FN_HANDSHAKE:
+ /* Return next 16bit value. */
+ if (pThis->uNextReplyEntryRead < pThis->cReplySize)
+ u32 |= pThis->ReplyBuffer.au16Reply[pThis->uNextReplyEntryRead++];
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ case LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_LOW:
+ {
+ uint32_t cReplyFrames = lsilogicReplyFreeQueueGetFrameCount(pThis);
+
+ u32 |= cReplyFrames & UINT32_C(0xffff);
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_HIGH;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ }
+ case LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_HIGH:
+ {
+ uint32_t cReplyFrames = lsilogicReplyFreeQueueGetFrameCount(pThis);
+
+ u32 |= cReplyFrames >> 16;
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ }
+ case LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW:
+ if (pThis->uReplyFreeQueueNextEntryFreeWrite != pThis->uReplyFreeQueueNextAddressRead)
+ {
+ u32 |= pThis->CTX_SUFF(pReplyFreeQueueBase)[pThis->uReplyFreeQueueNextAddressRead] & UINT32_C(0xffff);
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_HIGH;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+ break;
+ case LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_HIGH:
+ u32 |= pThis->CTX_SUFF(pReplyFreeQueueBase)[pThis->uReplyFreeQueueNextAddressRead] >> 16;
+ pThis->uReplyFreeQueueNextAddressRead++;
+ pThis->uReplyFreeQueueNextAddressRead %= pThis->cReplyQueueEntries;
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW;
+ lsilogicSetInterrupt(pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ default:
+ AssertMsgFailed(("Invalid doorbell state %d\n", pThis->enmDoorbellState));
+ }
+
+ break;
+ }
+ case LSILOGIC_REG_HOST_INTR_STATUS:
+ {
+ u32 = ASMAtomicReadU32(&pThis->uInterruptStatus);
+ break;
+ }
+ case LSILOGIC_REG_HOST_INTR_MASK:
+ {
+ u32 = ASMAtomicReadU32(&pThis->uInterruptMask);
+ break;
+ }
+ case LSILOGIC_REG_HOST_DIAGNOSTIC:
+ {
+ if (pThis->fDiagnosticEnabled)
+ u32 |= LSILOGIC_REG_HOST_DIAGNOSTIC_DRWE;
+ if (pThis->fDiagRegsEnabled)
+ u32 |= LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_RW_ENABLE;
+ break;
+ }
+ case LSILOGIC_REG_DIAG_RW_DATA:
+ {
+ if (pThis->fDiagRegsEnabled)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_READ;
+#else
+ lsilogicR3DiagRegDataRead(pThis, &u32);
+#endif
+ }
+ }
+ RT_FALL_THRU();
+ case LSILOGIC_REG_DIAG_RW_ADDRESS:
+ {
+ if (pThis->fDiagRegsEnabled)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_READ;
+#else
+ lsilogicR3DiagRegAddressRead(pThis, &u32);
+#endif
+ }
+ }
+ RT_FALL_THRU();
+ case LSILOGIC_REG_TEST_BASE_ADDRESS: /* The spec doesn't say anything about these registers, so we just ignore them */
+ default: /* Ignore. */
+ {
+ /** @todo LSILOGIC_REG_DIAG_* should return all F's when accessed by MMIO. We
+ * return 0. Likely to apply to undefined offsets as well. */
+ break;
+ }
+ }
+
+ *pu32 = u32;
+ LogFlowFunc(("pThis=%#p offReg=%#x u32=%#x\n", pThis, offReg, u32));
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTOUT}
+ */
+PDMBOTHCBDECL(int) lsilogicIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t offReg = uPort - pThis->IOPortBase;
+ int rc;
+ RT_NOREF2(pvUser, cb);
+
+ if (!(offReg & 3))
+ {
+ rc = lsilogicRegisterWrite(pThis, offReg, u32);
+ if (rc == VINF_IOM_R3_MMIO_WRITE)
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ }
+ else
+ {
+ Log(("lsilogicIOPortWrite: Ignoring misaligned write - offReg=%#x u32=%#x cb=%#x\n", offReg, u32, cb));
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTIN}
+ */
+PDMBOTHCBDECL(int) lsilogicIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t offReg = uPort - pThis->IOPortBase;
+ RT_NOREF_PV(pvUser);
+ RT_NOREF_PV(cb);
+
+ int rc = lsilogicRegisterRead(pThis, offReg & ~(uint32_t)3, pu32);
+ if (rc == VINF_IOM_R3_MMIO_READ)
+ rc = VINF_IOM_R3_IOPORT_READ;
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIOWRITE}
+ */
+PDMBOTHCBDECL(int) lsilogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t offReg = GCPhysAddr - pThis->GCPhysMMIOBase;
+ uint32_t u32;
+ int rc;
+ RT_NOREF_PV(pvUser);
+
+ /* See comments in lsilogicR3Map regarding size and alignment. */
+ if (cb == 4)
+ u32 = *(uint32_t const *)pv;
+ else
+ {
+ if (cb > 4)
+ u32 = *(uint32_t const *)pv;
+ else if (cb >= 2)
+ u32 = *(uint16_t const *)pv;
+ else
+ u32 = *(uint8_t const *)pv;
+ Log(("lsilogicMMIOWrite: Non-DWORD write access - offReg=%#x u32=%#x cb=%#x\n", offReg, u32, cb));
+ }
+
+ if (!(offReg & 3))
+ rc = lsilogicRegisterWrite(pThis, offReg, u32);
+ else
+ {
+ Log(("lsilogicIOPortWrite: Ignoring misaligned write - offReg=%#x u32=%#x cb=%#x\n", offReg, u32, cb));
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIOREAD}
+ */
+PDMBOTHCBDECL(int) lsilogicMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t offReg = GCPhysAddr - pThis->GCPhysMMIOBase;
+ Assert(!(offReg & 3)); Assert(cb == 4);
+ RT_NOREF2(pvUser, cb);
+
+ return lsilogicRegisterRead(pThis, offReg, (uint32_t *)pv);
+}
+
+PDMBOTHCBDECL(int) lsilogicDiagnosticWrite(PPDMDEVINS pDevIns, void *pvUser,
+ RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+#ifdef LOG_ENABLED
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ LogFlowFunc(("pThis=%#p GCPhysAddr=%RGp pv=%#p{%.*Rhxs} cb=%u\n", pThis, GCPhysAddr, pv, cb, pv, cb));
+#endif
+
+ RT_NOREF_PV(pDevIns); RT_NOREF_PV(pvUser); RT_NOREF_PV(GCPhysAddr); RT_NOREF_PV(pv); RT_NOREF_PV(cb);
+ return VINF_SUCCESS;
+}
+
+PDMBOTHCBDECL(int) lsilogicDiagnosticRead(PPDMDEVINS pDevIns, void *pvUser,
+ RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+#ifdef LOG_ENABLED
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ LogFlowFunc(("pThis=%#p GCPhysAddr=%RGp pv=%#p{%.*Rhxs} cb=%u\n", pThis, GCPhysAddr, pv, cb, pv, cb));
+#endif
+
+ RT_NOREF_PV(pDevIns); RT_NOREF_PV(pvUser); RT_NOREF_PV(GCPhysAddr); RT_NOREF_PV(pv); RT_NOREF_PV(cb);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+# ifdef LOG_ENABLED
+/**
+ * Dump an SG entry.
+ *
+ * @returns nothing.
+ * @param pSGEntry Pointer to the SG entry to dump
+ */
+static void lsilogicDumpSGEntry(PMptSGEntryUnion pSGEntry)
+{
+ if (LogIsEnabled())
+ {
+ switch (pSGEntry->Simple32.u2ElementType)
+ {
+ case MPTSGENTRYTYPE_SIMPLE:
+ {
+ Log(("%s: Dumping info for SIMPLE SG entry:\n", __FUNCTION__));
+ Log(("%s: u24Length=%u\n", __FUNCTION__, pSGEntry->Simple32.u24Length));
+ Log(("%s: fEndOfList=%d\n", __FUNCTION__, pSGEntry->Simple32.fEndOfList));
+ Log(("%s: f64BitAddress=%d\n", __FUNCTION__, pSGEntry->Simple32.f64BitAddress));
+ Log(("%s: fBufferContainsData=%d\n", __FUNCTION__, pSGEntry->Simple32.fBufferContainsData));
+ Log(("%s: fLocalAddress=%d\n", __FUNCTION__, pSGEntry->Simple32.fLocalAddress));
+ Log(("%s: fEndOfBuffer=%d\n", __FUNCTION__, pSGEntry->Simple32.fEndOfBuffer));
+ Log(("%s: fLastElement=%d\n", __FUNCTION__, pSGEntry->Simple32.fLastElement));
+ Log(("%s: u32DataBufferAddressLow=%u\n", __FUNCTION__, pSGEntry->Simple32.u32DataBufferAddressLow));
+ if (pSGEntry->Simple32.f64BitAddress)
+ {
+ Log(("%s: u32DataBufferAddressHigh=%u\n", __FUNCTION__, pSGEntry->Simple64.u32DataBufferAddressHigh));
+ Log(("%s: GCDataBufferAddress=%RGp\n", __FUNCTION__,
+ ((uint64_t)pSGEntry->Simple64.u32DataBufferAddressHigh << 32)
+ | pSGEntry->Simple64.u32DataBufferAddressLow));
+ }
+ else
+ Log(("%s: GCDataBufferAddress=%RGp\n", __FUNCTION__, pSGEntry->Simple32.u32DataBufferAddressLow));
+
+ break;
+ }
+ case MPTSGENTRYTYPE_CHAIN:
+ {
+ Log(("%s: Dumping info for CHAIN SG entry:\n", __FUNCTION__));
+ Log(("%s: u16Length=%u\n", __FUNCTION__, pSGEntry->Chain.u16Length));
+ Log(("%s: u8NExtChainOffset=%d\n", __FUNCTION__, pSGEntry->Chain.u8NextChainOffset));
+ Log(("%s: f64BitAddress=%d\n", __FUNCTION__, pSGEntry->Chain.f64BitAddress));
+ Log(("%s: fLocalAddress=%d\n", __FUNCTION__, pSGEntry->Chain.fLocalAddress));
+ Log(("%s: u32SegmentAddressLow=%u\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressLow));
+ Log(("%s: u32SegmentAddressHigh=%u\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressHigh));
+ if (pSGEntry->Chain.f64BitAddress)
+ Log(("%s: GCSegmentAddress=%RGp\n", __FUNCTION__,
+ ((uint64_t)pSGEntry->Chain.u32SegmentAddressHigh << 32) | pSGEntry->Chain.u32SegmentAddressLow));
+ else
+ Log(("%s: GCSegmentAddress=%RGp\n", __FUNCTION__, pSGEntry->Chain.u32SegmentAddressLow));
+ break;
+ }
+ }
+ }
+}
+# endif /* LOG_ENABLED */
+
+/**
+ * Copy from guest to host memory worker.
+ *
+ * @copydoc LSILOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) lsilogicR3CopyBufferFromGuestWorker(PLSILOGICSCSI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc LSILOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) lsilogicR3CopyBufferToGuestWorker(PLSILOGICSCSI pThis, RTGCPHYS GCPhys, PRTSGBUF pSgBuf,
+ size_t cbCopy, size_t *pcbSkip)
+{
+ size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip);
+ cbCopy -= cbSkipped;
+ GCPhys += cbSkipped;
+ *pcbSkip -= cbSkipped;
+
+ while (cbCopy)
+ {
+ size_t cbSeg = cbCopy;
+ void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg);
+
+ AssertPtr(pvSeg);
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Walks the guest S/G buffer calling the given copy worker for every buffer.
+ *
+ * @returns The amout of bytes actually copied.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pLsiReq LSI request state.
+ * @param pfnCopyWorker The copy method to apply for each guest buffer.
+ * @param pSgBuf The host S/G buffer.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t lsilogicSgBufWalker(PLSILOGICSCSI pThis, PLSILOGICREQ pLsiReq,
+ PLSILOGICR3MEMCOPYCALLBACK pfnCopyWorker,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ bool fEndOfList = false;
+ RTGCPHYS GCPhysSgEntryNext = pLsiReq->GCPhysSgStart;
+ RTGCPHYS GCPhysSegmentStart = pLsiReq->GCPhysSgStart;
+ uint32_t cChainOffsetNext = pLsiReq->cChainOffset;
+ PPDMDEVINS pDevIns = pThis->CTX_SUFF(pDevIns);
+ size_t cbCopied = 0;
+
+ /*
+ * Add the amount to skip to the host buffer size to avoid a
+ * few conditionals later on.
+ */
+ cbCopy += cbSkip;
+
+ /* Go through the list until we reach the end. */
+ while ( !fEndOfList
+ && cbCopy)
+ {
+ bool fEndOfSegment = false;
+
+ while ( !fEndOfSegment
+ && cbCopy)
+ {
+ MptSGEntryUnion SGEntry;
+
+ Log(("%s: Reading SG entry from %RGp\n", __FUNCTION__, GCPhysSgEntryNext));
+
+ /* Read the entry. */
+ PDMDevHlpPhysRead(pDevIns, GCPhysSgEntryNext, &SGEntry, sizeof(MptSGEntryUnion));
+
+# ifdef LOG_ENABLED
+ lsilogicDumpSGEntry(&SGEntry);
+# endif
+
+ AssertMsg(SGEntry.Simple32.u2ElementType == MPTSGENTRYTYPE_SIMPLE, ("Invalid SG entry type\n"));
+
+ /* Check if this is a zero element and abort. */
+ if ( !SGEntry.Simple32.u24Length
+ && SGEntry.Simple32.fEndOfList
+ && SGEntry.Simple32.fEndOfBuffer)
+ return cbCopied - RT_MIN(cbSkip, cbCopied);
+
+ uint32_t cbCopyThis = SGEntry.Simple32.u24Length;
+ RTGCPHYS GCPhysAddrDataBuffer = SGEntry.Simple32.u32DataBufferAddressLow;
+
+ if (SGEntry.Simple32.f64BitAddress)
+ {
+ GCPhysAddrDataBuffer |= ((uint64_t)SGEntry.Simple64.u32DataBufferAddressHigh) << 32;
+ GCPhysSgEntryNext += sizeof(MptSGEntrySimple64);
+ }
+ else
+ GCPhysSgEntryNext += sizeof(MptSGEntrySimple32);
+
+ pfnCopyWorker(pThis, GCPhysAddrDataBuffer, pSgBuf, cbCopyThis, &cbSkip);
+ cbCopy -= cbCopyThis;
+ cbCopied += cbCopyThis;
+
+ /* Check if we reached the end of the list. */
+ if (SGEntry.Simple32.fEndOfList)
+ {
+ /* We finished. */
+ fEndOfSegment = true;
+ fEndOfList = true;
+ }
+ else if (SGEntry.Simple32.fLastElement)
+ fEndOfSegment = true;
+ } /* while (!fEndOfSegment) */
+
+ /* Get next chain element. */
+ if (cChainOffsetNext)
+ {
+ MptSGEntryChain SGEntryChain;
+
+ PDMDevHlpPhysRead(pDevIns, GCPhysSegmentStart + cChainOffsetNext, &SGEntryChain, sizeof(MptSGEntryChain));
+
+ AssertMsg(SGEntryChain.u2ElementType == MPTSGENTRYTYPE_CHAIN, ("Invalid SG entry type\n"));
+
+ /* Set the next address now. */
+ GCPhysSgEntryNext = SGEntryChain.u32SegmentAddressLow;
+ if (SGEntryChain.f64BitAddress)
+ GCPhysSgEntryNext |= ((uint64_t)SGEntryChain.u32SegmentAddressHigh) << 32;
+
+ GCPhysSegmentStart = GCPhysSgEntryNext;
+ cChainOffsetNext = SGEntryChain.u8NextChainOffset * sizeof(uint32_t);
+ }
+ } /* while (!fEndOfList) */
+
+ return cbCopied - RT_MIN(cbSkip, cbCopied);
+}
+
+/**
+ * Copies a data buffer into the S/G buffer set up by the guest.
+ *
+ * @returns Amount of bytes copied to the guest.
+ * @param pThis The LsiLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pSgBuf The S/G buffer to copy from.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t lsilogicR3CopySgBufToGuest(PLSILOGICSCSI pThis, PLSILOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return lsilogicSgBufWalker(pThis, pReq, lsilogicR3CopyBufferToGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copies the guest S/G buffer into a host data buffer.
+ *
+ * @returns Amount of bytes copied from the guest.
+ * @param pThis The LsiLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pSgBuf The S/G buffer to copy into.
+ * @param cbSkip How many bytes to skip in advance before starting to copy.
+ * @param cbCopy How many bytes to copy.
+ */
+static size_t lsilogicR3CopySgBufFromGuest(PLSILOGICSCSI pThis, PLSILOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return lsilogicSgBufWalker(pThis, pReq, lsilogicR3CopyBufferFromGuestWorker,
+ pSgBuf, cbSkip, cbCopy);
+}
+
+#if 0 /* unused */
+/**
+ * Copy a simple memory buffer to the guest memory buffer.
+ *
+ * @returns Amount of bytes copied to the guest.
+ * @param pThis The LsiLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pvSrc The buffer to copy from.
+ * @param cbSrc How many bytes to copy.
+ * @param cbSkip How many bytes to skip initially.
+ */
+static size_t lsilogicR3CopyBufferToGuest(PLSILOGICSCSI pThis, PLSILOGICREQ pReq, const void *pvSrc,
+ size_t cbSrc, size_t cbSkip)
+{
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+ Seg.pvSeg = (void *)pvSrc;
+ Seg.cbSeg = cbSrc;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return lsilogicR3CopySgBufToGuest(pThis, pReq, &SgBuf, cbSkip, cbSrc);
+}
+
+/**
+ * Copy a guest memry buffe into simple host memory buffer.
+ *
+ * @returns Amount of bytes copied to the guest.
+ * @param pThis The LsiLogic controller device instance.
+ * @param pReq Request structure.
+ * @param pvSrc The buffer to copy from.
+ * @param cbSrc How many bytes to copy.
+ * @param cbSkip How many bytes to skip initially.
+ */
+static size_t lsilogicR3CopyBufferFromGuest(PLSILOGICSCSI pThis, PLSILOGICREQ pReq, void *pvDst,
+ size_t cbDst, size_t cbSkip)
+{
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+ Seg.pvSeg = (void *)pvDst;
+ Seg.cbSeg = cbDst;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return lsilogicR3CopySgBufFromGuest(pThis, pReq, &SgBuf, cbSkip, cbDst);
+}
+#endif
+
+# ifdef LOG_ENABLED
+static void lsilogicR3DumpSCSIIORequest(PMptSCSIIORequest pSCSIIORequest)
+{
+ if (LogIsEnabled())
+ {
+ Log(("%s: u8TargetID=%d\n", __FUNCTION__, pSCSIIORequest->u8TargetID));
+ Log(("%s: u8Bus=%d\n", __FUNCTION__, pSCSIIORequest->u8Bus));
+ Log(("%s: u8ChainOffset=%d\n", __FUNCTION__, pSCSIIORequest->u8ChainOffset));
+ Log(("%s: u8Function=%d\n", __FUNCTION__, pSCSIIORequest->u8Function));
+ Log(("%s: u8CDBLength=%d\n", __FUNCTION__, pSCSIIORequest->u8CDBLength));
+ Log(("%s: u8SenseBufferLength=%d\n", __FUNCTION__, pSCSIIORequest->u8SenseBufferLength));
+ Log(("%s: u8MessageFlags=%d\n", __FUNCTION__, pSCSIIORequest->u8MessageFlags));
+ Log(("%s: u32MessageContext=%#x\n", __FUNCTION__, pSCSIIORequest->u32MessageContext));
+ for (unsigned i = 0; i < RT_ELEMENTS(pSCSIIORequest->au8LUN); i++)
+ Log(("%s: u8LUN[%d]=%d\n", __FUNCTION__, i, pSCSIIORequest->au8LUN[i]));
+ Log(("%s: u32Control=%#x\n", __FUNCTION__, pSCSIIORequest->u32Control));
+ for (unsigned i = 0; i < RT_ELEMENTS(pSCSIIORequest->au8CDB); i++)
+ Log(("%s: u8CDB[%d]=%d\n", __FUNCTION__, i, pSCSIIORequest->au8CDB[i]));
+ Log(("%s: u32DataLength=%#x\n", __FUNCTION__, pSCSIIORequest->u32DataLength));
+ Log(("%s: u32SenseBufferLowAddress=%#x\n", __FUNCTION__, pSCSIIORequest->u32SenseBufferLowAddress));
+ }
+}
+# endif
+
+/**
+ * Handles the completion of th given request.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pReq The request to complete.
+ * @param rcReq Status code of the request.
+ */
+static void lsilogicR3ReqComplete(PLSILOGICSCSI pThis, PLSILOGICREQ pReq, int rcReq)
+{
+ PLSILOGICDEVICE pTgtDev = pReq->pTargetDevice;
+
+ if (RT_UNLIKELY(pReq->fBIOS))
+ {
+ uint8_t u8ScsiSts = pReq->u8ScsiSts;
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ int rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, u8ScsiSts);
+ AssertMsgRC(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc));
+ }
+ else
+ {
+ RTGCPHYS GCPhysAddrSenseBuffer;
+
+ GCPhysAddrSenseBuffer = pReq->GuestRequest.SCSIIO.u32SenseBufferLowAddress;
+ GCPhysAddrSenseBuffer |= ((uint64_t)pThis->u32SenseBufferHighAddr << 32);
+
+ /* Copy the sense buffer over. */
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhysAddrSenseBuffer, pReq->abSenseBuffer,
+ RT_UNLIKELY( pReq->GuestRequest.SCSIIO.u8SenseBufferLength
+ < sizeof(pReq->abSenseBuffer))
+ ? pReq->GuestRequest.SCSIIO.u8SenseBufferLength
+ : sizeof(pReq->abSenseBuffer));
+
+ if (RT_SUCCESS(rcReq) && RT_LIKELY(pReq->u8ScsiSts == SCSI_STATUS_OK))
+ {
+ uint32_t u32MsgCtx = pReq->GuestRequest.SCSIIO.u32MessageContext;
+
+ /* Free the request before posting completion. */
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ lsilogicR3FinishContextReply(pThis, u32MsgCtx);
+ }
+ else
+ {
+ MptReplyUnion IOCReply;
+ RT_ZERO(IOCReply);
+
+ /* The SCSI target encountered an error during processing post a reply. */
+ IOCReply.SCSIIOError.u8TargetID = pReq->GuestRequest.SCSIIO.u8TargetID;
+ IOCReply.SCSIIOError.u8Bus = pReq->GuestRequest.SCSIIO.u8Bus;
+ IOCReply.SCSIIOError.u8MessageLength = 8;
+ IOCReply.SCSIIOError.u8Function = pReq->GuestRequest.SCSIIO.u8Function;
+ IOCReply.SCSIIOError.u8CDBLength = pReq->GuestRequest.SCSIIO.u8CDBLength;
+ IOCReply.SCSIIOError.u8SenseBufferLength = pReq->GuestRequest.SCSIIO.u8SenseBufferLength;
+ IOCReply.SCSIIOError.u8MessageFlags = pReq->GuestRequest.SCSIIO.u8MessageFlags;
+ IOCReply.SCSIIOError.u32MessageContext = pReq->GuestRequest.SCSIIO.u32MessageContext;
+ IOCReply.SCSIIOError.u8SCSIStatus = pReq->u8ScsiSts;
+ IOCReply.SCSIIOError.u8SCSIState = MPT_SCSI_IO_ERROR_SCSI_STATE_AUTOSENSE_VALID;
+ IOCReply.SCSIIOError.u16IOCStatus = 0;
+ IOCReply.SCSIIOError.u32IOCLogInfo = 0;
+ IOCReply.SCSIIOError.u32TransferCount = 0;
+ IOCReply.SCSIIOError.u32SenseCount = sizeof(pReq->abSenseBuffer);
+ IOCReply.SCSIIOError.u32ResponseInfo = 0;
+
+ /* Free the request before posting completion. */
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ lsilogicFinishAddressReply(pThis, &IOCReply, false);
+ }
+ }
+
+ ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+
+ if (pTgtDev->cOutstandingRequests == 0 && pThis->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pThis->pDevInsR3);
+}
+
+/**
+ * Processes a SCSI I/O request by setting up the request
+ * and sending it to the underlying SCSI driver.
+ * Steps needed to complete request are done in the
+ * callback called by the driver below upon completion of
+ * the request.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param GCPhysMessageFrameAddr Guest physical address where the request is located.
+ * @param pGuestReq The request read fro th guest memory.
+ */
+static int lsilogicR3ProcessSCSIIORequest(PLSILOGICSCSI pThis, RTGCPHYS GCPhysMessageFrameAddr,
+ PMptRequestUnion pGuestReq)
+{
+ MptReplyUnion IOCReply;
+ int rc = VINF_SUCCESS;
+
+# ifdef LOG_ENABLED
+ lsilogicR3DumpSCSIIORequest(&pGuestReq->SCSIIO);
+# endif
+
+ if (RT_LIKELY( (pGuestReq->SCSIIO.u8TargetID < pThis->cDeviceStates)
+ && (pGuestReq->SCSIIO.u8Bus == 0)))
+ {
+ PLSILOGICDEVICE pTgtDev = &pThis->paDeviceStates[pGuestReq->SCSIIO.u8TargetID];
+
+ if (pTgtDev->pDrvBase)
+ {
+ /* Allocate and prepare a new request. */
+ PDMMEDIAEXIOREQ hIoReq;
+ PLSILOGICREQ pLsiReq = NULL;
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqAlloc(pTgtDev->pDrvMediaEx, &hIoReq, (void **)&pLsiReq,
+ pGuestReq->SCSIIO.u32MessageContext,
+ PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ if (RT_SUCCESS(rc))
+ {
+ pLsiReq->hIoReq = hIoReq;
+ pLsiReq->pTargetDevice = pTgtDev;
+ pLsiReq->GCPhysMessageFrameAddr = GCPhysMessageFrameAddr;
+ pLsiReq->fBIOS = false;
+ pLsiReq->GCPhysSgStart = GCPhysMessageFrameAddr + sizeof(MptSCSIIORequest);
+ pLsiReq->cChainOffset = pGuestReq->SCSIIO.u8ChainOffset;
+ if (pLsiReq->cChainOffset)
+ pLsiReq->cChainOffset = pLsiReq->cChainOffset * sizeof(uint32_t) - sizeof(MptSCSIIORequest);
+ memcpy(&pLsiReq->GuestRequest, pGuestReq, sizeof(MptRequestUnion));
+ RT_BZERO(&pLsiReq->abSenseBuffer[0], sizeof(pLsiReq->abSenseBuffer));
+
+ PDMMEDIAEXIOREQSCSITXDIR enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN;
+ uint8_t uDataDirection = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_GET(pLsiReq->GuestRequest.SCSIIO.u32Control);
+
+ /*
+ * Keep the direction to unknown if there is a mismatch between the data length
+ * and the transfer direction bit.
+ * The Solaris 9 driver is buggy and sets it to none for INQUIRY requests.
+ */
+ if ( uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE
+ && pLsiReq->GuestRequest.SCSIIO.u32DataLength == 0)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_NONE;
+ else if (uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE;
+ else if (uDataDirection == MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqSendScsiCmd(pTgtDev->pDrvMediaEx, pLsiReq->hIoReq, pLsiReq->GuestRequest.SCSIIO.au8LUN[1],
+ &pLsiReq->GuestRequest.SCSIIO.au8CDB[0], pLsiReq->GuestRequest.SCSIIO.u8CDBLength,
+ enmXferDir, pLsiReq->GuestRequest.SCSIIO.u32DataLength,
+ &pLsiReq->abSenseBuffer[0], sizeof(pLsiReq->abSenseBuffer), &pLsiReq->u8ScsiSts,
+ 30 * RT_MS_1SEC);
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ lsilogicR3ReqComplete(pThis, pLsiReq, rc);
+
+ return VINF_SUCCESS;
+ }
+ else
+ IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE;
+ }
+ else
+ {
+ /* Device is not present report SCSI selection timeout. */
+ IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE;
+ }
+ }
+ else
+ {
+ /* Report out of bounds target ID or bus. */
+ if (pGuestReq->SCSIIO.u8Bus != 0)
+ IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_BUS;
+ else
+ IOCReply.SCSIIOError.u16IOCStatus = MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_TARGETID;
+ }
+
+ static int g_cLogged = 0;
+
+ if (g_cLogged++ < MAX_REL_LOG_ERRORS)
+ {
+ LogRel(("LsiLogic#%d: %d/%d (Bus/Target) doesn't exist\n", pThis->CTX_SUFF(pDevIns)->iInstance,
+ pGuestReq->SCSIIO.u8TargetID, pGuestReq->SCSIIO.u8Bus));
+ /* Log the CDB too */
+ LogRel(("LsiLogic#%d: Guest issued CDB {%#x",
+ pThis->CTX_SUFF(pDevIns)->iInstance, pGuestReq->SCSIIO.au8CDB[0]));
+ for (unsigned i = 1; i < pGuestReq->SCSIIO.u8CDBLength; i++)
+ LogRel((", %#x", pGuestReq->SCSIIO.au8CDB[i]));
+ LogRel(("}\n"));
+ }
+
+ /* The rest is equal to both errors. */
+ IOCReply.SCSIIOError.u8TargetID = pGuestReq->SCSIIO.u8TargetID;
+ IOCReply.SCSIIOError.u8Bus = pGuestReq->SCSIIO.u8Bus;
+ IOCReply.SCSIIOError.u8MessageLength = sizeof(MptSCSIIOErrorReply) / 4;
+ IOCReply.SCSIIOError.u8Function = pGuestReq->SCSIIO.u8Function;
+ IOCReply.SCSIIOError.u8CDBLength = pGuestReq->SCSIIO.u8CDBLength;
+ IOCReply.SCSIIOError.u8SenseBufferLength = pGuestReq->SCSIIO.u8SenseBufferLength;
+ IOCReply.SCSIIOError.u32MessageContext = pGuestReq->SCSIIO.u32MessageContext;
+ IOCReply.SCSIIOError.u8SCSIStatus = SCSI_STATUS_OK;
+ IOCReply.SCSIIOError.u8SCSIState = MPT_SCSI_IO_ERROR_SCSI_STATE_TERMINATED;
+ IOCReply.SCSIIOError.u32IOCLogInfo = 0;
+ IOCReply.SCSIIOError.u32TransferCount = 0;
+ IOCReply.SCSIIOError.u32SenseCount = 0;
+ IOCReply.SCSIIOError.u32ResponseInfo = 0;
+
+ lsilogicFinishAddressReply(pThis, &IOCReply, false);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) lsilogicR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaPort);
+ PPDMDEVINS pDevIns = pTgtDev->CTX_SUFF(pLsiLogic)->CTX_SUFF(pDevIns);
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pTgtDev->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) lsilogicR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaExPort);
+ PLSILOGICREQ pReq = (PLSILOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = 0;
+ if (RT_UNLIKELY(pReq->fBIOS))
+ cbCopied = vboxscsiCopyToBuf(&pTgtDev->CTX_SUFF(pLsiLogic)->VBoxSCSI, pSgBuf, offDst, cbCopy);
+ else
+ cbCopied = lsilogicR3CopySgBufToGuest(pTgtDev->CTX_SUFF(pLsiLogic), pReq, pSgBuf, offDst, cbCopy);
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) lsilogicR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF1(hIoReq);
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaExPort);
+ PLSILOGICREQ pReq = (PLSILOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = 0;
+ if (RT_UNLIKELY(pReq->fBIOS))
+ cbCopied = vboxscsiCopyFromBuf(&pTgtDev->CTX_SUFF(pLsiLogic)->VBoxSCSI, pSgBuf, offSrc, cbCopy);
+ else
+ cbCopied = lsilogicR3CopySgBufFromGuest(pTgtDev->CTX_SUFF(pLsiLogic), pReq, pSgBuf, offSrc, cbCopy);
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) lsilogicR3IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF(hIoReq);
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaExPort);
+ lsilogicR3ReqComplete(pTgtDev->CTX_SUFF(pLsiLogic), (PLSILOGICREQ)pvIoReqAlloc, rcReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) lsilogicR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF3(hIoReq, pvIoReqAlloc, enmState);
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaExPort);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ /* Make sure the request is not accounted for so the VM can suspend successfully. */
+ uint32_t cTasksActive = ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+ if (!cTasksActive && pTgtDev->CTX_SUFF(pLsiLogic)->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pTgtDev->CTX_SUFF(pLsiLogic)->pDevInsR3);
+ break;
+ }
+ case PDMMEDIAEXIOREQSTATE_ACTIVE:
+ /* Make sure the request is accounted for so the VM suspends only when the request is complete. */
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+ break;
+ default:
+ AssertMsgFailed(("Invalid request state given %u\n", enmState));
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) lsilogicR3MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ PLSILOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IMediaExPort);
+ PLSILOGICSCSI pThis = pTgtDev->CTX_SUFF(pLsiLogic);
+
+ if (pThis->pMediaNotify)
+ {
+ int rc = VMR3ReqCallNoWait(PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns)), VMCPUID_ANY,
+ (PFNRT)pThis->pMediaNotify->pfnEjected, 2,
+ pThis->pMediaNotify, pTgtDev->iLUN);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationIOUnitPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->IOUnitPage0.u.fields.Header;
+ *ppbPageData = pPages->IOUnitPage0.u.abPageData;
+ *pcbPage = sizeof(pPages->IOUnitPage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPages->IOUnitPage1.u.fields.Header;
+ *ppbPageData = pPages->IOUnitPage1.u.abPageData;
+ *pcbPage = sizeof(pPages->IOUnitPage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->IOUnitPage2.u.fields.Header;
+ *ppbPageData = pPages->IOUnitPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->IOUnitPage2);
+ break;
+ case 3:
+ *ppPageHeader = &pPages->IOUnitPage3.u.fields.Header;
+ *ppbPageData = pPages->IOUnitPage3.u.abPageData;
+ *pcbPage = sizeof(pPages->IOUnitPage3);
+ break;
+ case 4:
+ *ppPageHeader = &pPages->IOUnitPage4.u.fields.Header;
+ *ppbPageData = pPages->IOUnitPage4.u.abPageData;
+ *pcbPage = sizeof(pPages->IOUnitPage4);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationIOCPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->IOCPage0.u.fields.Header;
+ *ppbPageData = pPages->IOCPage0.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPages->IOCPage1.u.fields.Header;
+ *ppbPageData = pPages->IOCPage1.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->IOCPage2.u.fields.Header;
+ *ppbPageData = pPages->IOCPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage2);
+ break;
+ case 3:
+ *ppPageHeader = &pPages->IOCPage3.u.fields.Header;
+ *ppbPageData = pPages->IOCPage3.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage3);
+ break;
+ case 4:
+ *ppPageHeader = &pPages->IOCPage4.u.fields.Header;
+ *ppbPageData = pPages->IOCPage4.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage4);
+ break;
+ case 6:
+ *ppPageHeader = &pPages->IOCPage6.u.fields.Header;
+ *ppbPageData = pPages->IOCPage6.u.abPageData;
+ *pcbPage = sizeof(pPages->IOCPage6);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationManufacturingPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->ManufacturingPage0.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage0.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPages->ManufacturingPage1.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage1.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->ManufacturingPage2.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage2);
+ break;
+ case 3:
+ *ppPageHeader = &pPages->ManufacturingPage3.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage3.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage3);
+ break;
+ case 4:
+ *ppPageHeader = &pPages->ManufacturingPage4.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage4.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage4);
+ break;
+ case 5:
+ *ppPageHeader = &pPages->ManufacturingPage5.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage5.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage5);
+ break;
+ case 6:
+ *ppPageHeader = &pPages->ManufacturingPage6.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage6.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage6);
+ break;
+ case 7:
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ *ppPageHeader = &pPages->u.SasPages.pManufacturingPage7->u.fields.Header;
+ *ppbPageData = pPages->u.SasPages.pManufacturingPage7->u.abPageData;
+ *pcbPage = pPages->u.SasPages.cbManufacturingPage7;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ break;
+ case 8:
+ *ppPageHeader = &pPages->ManufacturingPage8.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage8.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage8);
+ break;
+ case 9:
+ *ppPageHeader = &pPages->ManufacturingPage9.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage9.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage9);
+ break;
+ case 10:
+ *ppPageHeader = &pPages->ManufacturingPage10.u.fields.Header;
+ *ppbPageData = pPages->ManufacturingPage10.u.abPageData;
+ *pcbPage = sizeof(pPages->ManufacturingPage10);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationBiosPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+ switch (u8PageNumber)
+ {
+ case 1:
+ *ppPageHeader = &pPages->BIOSPage1.u.fields.Header;
+ *ppbPageData = pPages->BIOSPage1.u.abPageData;
+ *pcbPage = sizeof(pPages->BIOSPage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->BIOSPage2.u.fields.Header;
+ *ppbPageData = pPages->BIOSPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->BIOSPage2);
+ break;
+ case 4:
+ *ppPageHeader = &pPages->BIOSPage4.u.fields.Header;
+ *ppbPageData = pPages->BIOSPage4.u.abPageData;
+ *pcbPage = sizeof(pPages->BIOSPage4);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8Port The port to retrieve the page for.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationSCSISPIPortPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8Port,
+ uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+
+ if (u8Port >= RT_ELEMENTS(pPages->u.SpiPages.aPortPages))
+ return VERR_NOT_FOUND;
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage0.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage0.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage1.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage1.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage2.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aPortPages[u8Port].SCSISPIPortPage2);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Return the configuration page header and data
+ * which matches the given page type and number.
+ *
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis The LsiLogic controller instance data.
+ * @param pPages The pages supported by the controller.
+ * @param u8Bus The bus the device is on the page should be returned.
+ * @param u8TargetID The target ID of the device to return the page for.
+ * @param u8PageNumber Number of the page to get.
+ * @param ppPageHeader Where to store the pointer to the page header.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page data in bytes on success.
+ */
+static int lsilogicR3ConfigurationSCSISPIDevicePageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8Bus,
+ uint8_t u8TargetID, uint8_t u8PageNumber,
+ PMptConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+ AssertPtr(ppPageHeader); Assert(ppbPageData);
+
+ if (u8Bus >= RT_ELEMENTS(pPages->u.SpiPages.aBuses))
+ return VERR_NOT_FOUND;
+
+ if (u8TargetID >= RT_ELEMENTS(pPages->u.SpiPages.aBuses[u8Bus].aDevicePages))
+ return VERR_NOT_FOUND;
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage1);
+ break;
+ case 2:
+ *ppPageHeader = &pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage2);
+ break;
+ case 3:
+ *ppPageHeader = &pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3.u.fields.Header;
+ *ppbPageData = pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SpiPages.aBuses[u8Bus].aDevicePages[u8TargetID].SCSISPIDevicePage3);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+static int lsilogicR3ConfigurationSASIOUnitPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPages->u.SasPages.pSASIOUnitPage0->u.fields.ExtHeader;
+ *ppbPageData = pPages->u.SasPages.pSASIOUnitPage0->u.abPageData;
+ *pcbPage = pPages->u.SasPages.cbSASIOUnitPage0;
+ break;
+ case 1:
+ *ppPageHeader = &pPages->u.SasPages.pSASIOUnitPage1->u.fields.ExtHeader;
+ *ppbPageData = pPages->u.SasPages.pSASIOUnitPage1->u.abPageData;
+ *pcbPage = pPages->u.SasPages.cbSASIOUnitPage1;
+ break;
+ case 2:
+ *ppPageHeader = &pPages->u.SasPages.SASIOUnitPage2.u.fields.ExtHeader;
+ *ppbPageData = pPages->u.SasPages.SASIOUnitPage2.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SasPages.SASIOUnitPage2);
+ break;
+ case 3:
+ *ppPageHeader = &pPages->u.SasPages.SASIOUnitPage3.u.fields.ExtHeader;
+ *ppbPageData = pPages->u.SasPages.SASIOUnitPage3.u.abPageData;
+ *pcbPage = sizeof(pPages->u.SasPages.SASIOUnitPage3);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+static int lsilogicR3ConfigurationSASPHYPageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ MptConfigurationPageAddress PageAddress,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+ uint8_t uAddressForm = MPT_CONFIGURATION_PAGE_ADDRESS_GET_SAS_FORM(PageAddress);
+ PMptConfigurationPagesSas pPagesSas = &pPages->u.SasPages;
+ PMptPHY pPHYPages = NULL;
+
+ Log(("Address form %d\n", uAddressForm));
+
+ if (uAddressForm == 0) /* PHY number */
+ {
+ uint8_t u8PhyNumber = PageAddress.SASPHY.Form0.u8PhyNumber;
+
+ Log(("PHY number %d\n", u8PhyNumber));
+
+ if (u8PhyNumber >= pPagesSas->cPHYs)
+ return VERR_NOT_FOUND;
+
+ pPHYPages = &pPagesSas->paPHYs[u8PhyNumber];
+ }
+ else if (uAddressForm == 1) /* Index form */
+ {
+ uint16_t u16Index = PageAddress.SASPHY.Form1.u16Index;
+
+ Log(("PHY index %d\n", u16Index));
+
+ if (u16Index >= pPagesSas->cPHYs)
+ return VERR_NOT_FOUND;
+
+ pPHYPages = &pPagesSas->paPHYs[u16Index];
+ }
+ else
+ rc = VERR_NOT_FOUND; /* Correct? */
+
+ if (pPHYPages)
+ {
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pPHYPages->SASPHYPage0.u.fields.ExtHeader;
+ *ppbPageData = pPHYPages->SASPHYPage0.u.abPageData;
+ *pcbPage = sizeof(pPHYPages->SASPHYPage0);
+ break;
+ case 1:
+ *ppPageHeader = &pPHYPages->SASPHYPage1.u.fields.ExtHeader;
+ *ppbPageData = pPHYPages->SASPHYPage1.u.abPageData;
+ *pcbPage = sizeof(pPHYPages->SASPHYPage1);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+static int lsilogicR3ConfigurationSASDevicePageGetFromNumber(PLSILOGICSCSI pThis,
+ PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ MptConfigurationPageAddress PageAddress,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ RT_NOREF(pThis);
+ int rc = VINF_SUCCESS;
+ uint8_t uAddressForm = MPT_CONFIGURATION_PAGE_ADDRESS_GET_SAS_FORM(PageAddress);
+ PMptConfigurationPagesSas pPagesSas = &pPages->u.SasPages;
+ PMptSASDevice pSASDevice = NULL;
+
+ Log(("Address form %d\n", uAddressForm));
+
+ if (uAddressForm == 0)
+ {
+ uint16_t u16Handle = PageAddress.SASDevice.Form0And2.u16Handle;
+
+ Log(("Get next handle %#x\n", u16Handle));
+
+ pSASDevice = pPagesSas->pSASDeviceHead;
+
+ /* Get the first device? */
+ if (u16Handle != 0xffff)
+ {
+ /* No, search for the right one. */
+
+ while ( pSASDevice
+ && pSASDevice->SASDevicePage0.u.fields.u16DevHandle != u16Handle)
+ pSASDevice = pSASDevice->pNext;
+
+ if (pSASDevice)
+ pSASDevice = pSASDevice->pNext;
+ }
+ }
+ else if (uAddressForm == 1)
+ {
+ uint8_t u8TargetID = PageAddress.SASDevice.Form1.u8TargetID;
+ uint8_t u8Bus = PageAddress.SASDevice.Form1.u8Bus;
+
+ Log(("u8TargetID=%d u8Bus=%d\n", u8TargetID, u8Bus));
+
+ pSASDevice = pPagesSas->pSASDeviceHead;
+
+ while ( pSASDevice
+ && ( pSASDevice->SASDevicePage0.u.fields.u8TargetID != u8TargetID
+ || pSASDevice->SASDevicePage0.u.fields.u8Bus != u8Bus))
+ pSASDevice = pSASDevice->pNext;
+ }
+ else if (uAddressForm == 2)
+ {
+ uint16_t u16Handle = PageAddress.SASDevice.Form0And2.u16Handle;
+
+ Log(("Handle %#x\n", u16Handle));
+
+ pSASDevice = pPagesSas->pSASDeviceHead;
+
+ while ( pSASDevice
+ && pSASDevice->SASDevicePage0.u.fields.u16DevHandle != u16Handle)
+ pSASDevice = pSASDevice->pNext;
+ }
+
+ if (pSASDevice)
+ {
+ switch (u8PageNumber)
+ {
+ case 0:
+ *ppPageHeader = &pSASDevice->SASDevicePage0.u.fields.ExtHeader;
+ *ppbPageData = pSASDevice->SASDevicePage0.u.abPageData;
+ *pcbPage = sizeof(pSASDevice->SASDevicePage0);
+ break;
+ case 1:
+ *ppPageHeader = &pSASDevice->SASDevicePage1.u.fields.ExtHeader;
+ *ppbPageData = pSASDevice->SASDevicePage1.u.abPageData;
+ *pcbPage = sizeof(pSASDevice->SASDevicePage1);
+ break;
+ case 2:
+ *ppPageHeader = &pSASDevice->SASDevicePage2.u.fields.ExtHeader;
+ *ppbPageData = pSASDevice->SASDevicePage2.u.abPageData;
+ *pcbPage = sizeof(pSASDevice->SASDevicePage2);
+ break;
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+/**
+ * Returns the extended configuration page header and data.
+ * @returns VINF_SUCCESS if successful
+ * VERR_NOT_FOUND if the requested page could be found.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pConfigurationReq The configuration request.
+ * @param ppPageHeader Where to return the pointer to the page header on success.
+ * @param ppbPageData Where to store the pointer to the page data.
+ * @param pcbPage Where to store the size of the page in bytes.
+ */
+static int lsilogicR3ConfigurationPageGetExtended(PLSILOGICSCSI pThis, PMptConfigurationRequest pConfigurationReq,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ int rc = VINF_SUCCESS;
+
+ Log(("Extended page requested:\n"));
+ Log(("u8ExtPageType=%#x\n", pConfigurationReq->u8ExtPageType));
+ Log(("u8ExtPageLength=%d\n", pConfigurationReq->u16ExtPageLength));
+
+ switch (pConfigurationReq->u8ExtPageType)
+ {
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT:
+ {
+ rc = lsilogicR3ConfigurationSASIOUnitPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ ppPageHeader, ppbPageData, pcbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASPHYS:
+ {
+ rc = lsilogicR3ConfigurationSASPHYPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ pConfigurationReq->PageAddress,
+ ppPageHeader, ppbPageData, pcbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE:
+ {
+ rc = lsilogicR3ConfigurationSASDevicePageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ pConfigurationReq->PageAddress,
+ ppPageHeader, ppbPageData, pcbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASEXPANDER: /* No expanders supported */
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_ENCLOSURE: /* No enclosures supported */
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+/**
+ * Processes a Configuration request.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pConfigurationReq Pointer to the request structure.
+ * @param pReply Pointer to the reply message frame
+ */
+static int lsilogicR3ProcessConfigurationRequest(PLSILOGICSCSI pThis, PMptConfigurationRequest pConfigurationReq,
+ PMptConfigurationReply pReply)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbPageData = NULL;
+ PMptConfigurationPageHeader pPageHeader = NULL;
+ PMptExtendedConfigurationPageHeader pExtPageHeader = NULL;
+ uint8_t u8PageType;
+ uint8_t u8PageAttribute;
+ size_t cbPage = 0;
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ u8PageType = MPT_CONFIGURATION_PAGE_TYPE_GET(pConfigurationReq->u8PageType);
+ u8PageAttribute = MPT_CONFIGURATION_PAGE_ATTRIBUTE_GET(pConfigurationReq->u8PageType);
+
+ Log(("GuestRequest:\n"));
+ Log(("u8Action=%#x\n", pConfigurationReq->u8Action));
+ Log(("u8PageType=%#x\n", u8PageType));
+ Log(("u8PageNumber=%d\n", pConfigurationReq->u8PageNumber));
+ Log(("u8PageLength=%d\n", pConfigurationReq->u8PageLength));
+ Log(("u8PageVersion=%d\n", pConfigurationReq->u8PageVersion));
+
+ /* Copy common bits from the request into the reply. */
+ pReply->u8MessageLength = 6; /* 6 32bit D-Words. */
+ pReply->u8Action = pConfigurationReq->u8Action;
+ pReply->u8Function = pConfigurationReq->u8Function;
+ pReply->u32MessageContext = pConfigurationReq->u32MessageContext;
+
+ switch (u8PageType)
+ {
+ case MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationIOUnitPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_IOC:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationIOCPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationManufacturingPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationSCSISPIPortPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->PageAddress.MPIPortNumber.u8PortNumber,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationSCSISPIDevicePageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->PageAddress.BusAndTargetId.u8Bus,
+ pConfigurationReq->PageAddress.BusAndTargetId.u8TargetID,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_BIOS:
+ {
+ rc = lsilogicR3ConfigurationBiosPageGetFromNumber(pThis,
+ pThis->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED:
+ {
+ rc = lsilogicR3ConfigurationPageGetExtended(pThis,
+ pConfigurationReq,
+ &pExtPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ default:
+ rc = VERR_NOT_FOUND;
+ }
+
+ if (rc == VERR_NOT_FOUND)
+ {
+ Log(("Page not found\n"));
+ pReply->u8PageType = pConfigurationReq->u8PageType;
+ pReply->u8PageNumber = pConfigurationReq->u8PageNumber;
+ pReply->u8PageLength = pConfigurationReq->u8PageLength;
+ pReply->u8PageVersion = pConfigurationReq->u8PageVersion;
+ pReply->u16IOCStatus = MPT_IOCSTATUS_CONFIG_INVALID_PAGE;
+ return VINF_SUCCESS;
+ }
+
+ if (u8PageType == MPT_CONFIGURATION_PAGE_TYPE_EXTENDED)
+ {
+ pReply->u8PageType = pExtPageHeader->u8PageType;
+ pReply->u8PageNumber = pExtPageHeader->u8PageNumber;
+ pReply->u8PageVersion = pExtPageHeader->u8PageVersion;
+ pReply->u8ExtPageType = pExtPageHeader->u8ExtPageType;
+ pReply->u16ExtPageLength = pExtPageHeader->u16ExtPageLength;
+
+ for (int i = 0; i < pExtPageHeader->u16ExtPageLength; i++)
+ LogFlowFunc(("PageData[%d]=%#x\n", i, ((uint32_t *)pbPageData)[i]));
+ }
+ else
+ {
+ pReply->u8PageType = pPageHeader->u8PageType;
+ pReply->u8PageNumber = pPageHeader->u8PageNumber;
+ pReply->u8PageLength = pPageHeader->u8PageLength;
+ pReply->u8PageVersion = pPageHeader->u8PageVersion;
+
+ for (int i = 0; i < pReply->u8PageLength; i++)
+ LogFlowFunc(("PageData[%d]=%#x\n", i, ((uint32_t *)pbPageData)[i]));
+ }
+
+ /*
+ * Don't use the scatter gather handling code as the configuration request always have only one
+ * simple element.
+ */
+ switch (pConfigurationReq->u8Action)
+ {
+ case MPT_CONFIGURATION_REQUEST_ACTION_DEFAULT: /* Nothing to do. We are always using the defaults. */
+ case MPT_CONFIGURATION_REQUEST_ACTION_HEADER:
+ {
+ /* Already copied above nothing to do. */
+ break;
+ }
+ case MPT_CONFIGURATION_REQUEST_ACTION_READ_NVRAM:
+ case MPT_CONFIGURATION_REQUEST_ACTION_READ_CURRENT:
+ case MPT_CONFIGURATION_REQUEST_ACTION_READ_DEFAULT:
+ {
+ uint32_t cbBuffer = pConfigurationReq->SimpleSGElement.u24Length;
+ if (cbBuffer != 0)
+ {
+ RTGCPHYS GCPhysAddrPageBuffer = pConfigurationReq->SimpleSGElement.u32DataBufferAddressLow;
+ if (pConfigurationReq->SimpleSGElement.f64BitAddress)
+ GCPhysAddrPageBuffer |= (uint64_t)pConfigurationReq->SimpleSGElement.u32DataBufferAddressHigh << 32;
+
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), GCPhysAddrPageBuffer, pbPageData, RT_MIN(cbBuffer, cbPage));
+ }
+ break;
+ }
+ case MPT_CONFIGURATION_REQUEST_ACTION_WRITE_CURRENT:
+ case MPT_CONFIGURATION_REQUEST_ACTION_WRITE_NVRAM:
+ {
+ uint32_t cbBuffer = pConfigurationReq->SimpleSGElement.u24Length;
+ if (cbBuffer != 0)
+ {
+ RTGCPHYS GCPhysAddrPageBuffer = pConfigurationReq->SimpleSGElement.u32DataBufferAddressLow;
+ if (pConfigurationReq->SimpleSGElement.f64BitAddress)
+ GCPhysAddrPageBuffer |= (uint64_t)pConfigurationReq->SimpleSGElement.u32DataBufferAddressHigh << 32;
+
+ LogFlow(("cbBuffer=%u cbPage=%u\n", cbBuffer, cbPage));
+
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), GCPhysAddrPageBuffer, pbPageData,
+ RT_MIN(cbBuffer, cbPage));
+ }
+ break;
+ }
+ default:
+ AssertMsgFailed(("todo\n"));
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the configuration pages for the SPI SCSI controller.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPagesSpi(PLSILOGICSCSI pThis)
+{
+ PMptConfigurationPagesSpi pPages = &pThis->pConfigurationPages->u.SpiPages;
+
+ AssertMsg(pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI, ("Controller is not the SPI SCSI one\n"));
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /* Clear everything first. */
+ memset(pPages, 0, sizeof(MptConfigurationPagesSpi));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pPages->aPortPages); i++)
+ {
+ /* SCSI-SPI port page 0. */
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageNumber = 0;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort0) / 4;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fInformationUnitTransfersCapable = true;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fDTCapable = true;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fQASCapable = true;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u8MinimumSynchronousTransferPeriod = 0;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u8MaximumSynchronousOffset = 0xff;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fWide = true;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.fAIPCapable = true;
+ pPages->aPortPages[i].SCSISPIPortPage0.u.fields.u2SignalingType = 0x3; /* Single Ended. */
+
+ /* SCSI-SPI port page 1. */
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageNumber = 1;
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort1) / 4;
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u8SCSIID = 7;
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u16PortResponseIDsBitmask = (1 << 7);
+ pPages->aPortPages[i].SCSISPIPortPage1.u.fields.u32OnBusTimerValue = 0;
+
+ /* SCSI-SPI port page 2. */
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT;
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageNumber = 2;
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIPort2) / 4;
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.u4HostSCSIID = 7;
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.u2InitializeHBA = 0x3;
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.fTerminationDisabled = true;
+ for (unsigned iDevice = 0; iDevice < RT_ELEMENTS(pPages->aPortPages[i].SCSISPIPortPage2.u.fields.aDeviceSettings); iDevice++)
+ {
+ pPages->aPortPages[i].SCSISPIPortPage2.u.fields.aDeviceSettings[iDevice].fBootChoice = true;
+ }
+ /* Everything else 0 for now. */
+ }
+
+ for (unsigned uBusCurr = 0; uBusCurr < RT_ELEMENTS(pPages->aBuses); uBusCurr++)
+ {
+ for (unsigned uDeviceCurr = 0; uDeviceCurr < RT_ELEMENTS(pPages->aBuses[uBusCurr].aDevicePages); uDeviceCurr++)
+ {
+ /* SCSI-SPI device page 0. */
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageNumber = 0;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage0.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice0) / 4;
+ /* Everything else 0 for now. */
+
+ /* SCSI-SPI device page 1. */
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageNumber = 1;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage1.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice1) / 4;
+ /* Everything else 0 for now. */
+
+ /* SCSI-SPI device page 2. */
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageNumber = 2;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage2.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice2) / 4;
+ /* Everything else 0 for now. */
+
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageNumber = 3;
+ pPages->aBuses[uBusCurr].aDevicePages[uDeviceCurr].SCSISPIDevicePage3.u.fields.Header.u8PageLength = sizeof(MptConfigurationPageSCSISPIDevice3) / 4;
+ /* Everything else 0 for now. */
+ }
+ }
+}
+
+/**
+ * Generates a handle.
+ *
+ * @returns the handle.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+DECLINLINE(uint16_t) lsilogicGetHandle(PLSILOGICSCSI pThis)
+{
+ uint16_t u16Handle = pThis->u16NextHandle++;
+ return u16Handle;
+}
+
+/**
+ * Generates a SAS address (WWID)
+ *
+ * @returns nothing.
+ * @param pSASAddress Pointer to an unitialised SAS address.
+ * @param iId iId which will go into the address.
+ *
+ * @todo Generate better SAS addresses. (Request a block from SUN probably)
+ */
+void lsilogicSASAddressGenerate(PSASADDRESS pSASAddress, unsigned iId)
+{
+ pSASAddress->u8Address[0] = (0x5 << 5);
+ pSASAddress->u8Address[1] = 0x01;
+ pSASAddress->u8Address[2] = 0x02;
+ pSASAddress->u8Address[3] = 0x03;
+ pSASAddress->u8Address[4] = 0x04;
+ pSASAddress->u8Address[5] = 0x05;
+ pSASAddress->u8Address[6] = 0x06;
+ pSASAddress->u8Address[7] = iId;
+}
+
+/**
+ * Initializes the configuration pages for the SAS SCSI controller.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPagesSas(PLSILOGICSCSI pThis)
+{
+ PMptConfigurationPagesSas pPages = &pThis->pConfigurationPages->u.SasPages;
+
+ AssertMsg(pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS, ("Controller is not the SAS SCSI one\n"));
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /* Manufacturing Page 7 - Connector settings. */
+ pPages->cbManufacturingPage7 = LSILOGICSCSI_MANUFACTURING7_GET_SIZE(pThis->cPorts);
+ PMptConfigurationPageManufacturing7 pManufacturingPage7 = (PMptConfigurationPageManufacturing7)RTMemAllocZ(pPages->cbManufacturingPage7);
+ AssertPtr(pManufacturingPage7);
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(pManufacturingPage7,
+ 0, 7,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+ /* Set size manually. */
+ if (pPages->cbManufacturingPage7 / 4 > 255)
+ pManufacturingPage7->u.fields.Header.u8PageLength = 255;
+ else
+ pManufacturingPage7->u.fields.Header.u8PageLength = pPages->cbManufacturingPage7 / 4;
+ pManufacturingPage7->u.fields.u8NumPhys = pThis->cPorts;
+ pPages->pManufacturingPage7 = pManufacturingPage7;
+
+ /* SAS I/O unit page 0 - Port specific information. */
+ pPages->cbSASIOUnitPage0 = LSILOGICSCSI_SASIOUNIT0_GET_SIZE(pThis->cPorts);
+ PMptConfigurationPageSASIOUnit0 pSASPage0 = (PMptConfigurationPageSASIOUnit0)RTMemAllocZ(pPages->cbSASIOUnitPage0);
+ AssertPtr(pSASPage0);
+
+ MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pSASPage0, pPages->cbSASIOUnitPage0,
+ 0, MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY,
+ MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT);
+ pSASPage0->u.fields.u8NumPhys = pThis->cPorts;
+ pPages->pSASIOUnitPage0 = pSASPage0;
+
+ /* SAS I/O unit page 1 - Port specific settings. */
+ pPages->cbSASIOUnitPage1 = LSILOGICSCSI_SASIOUNIT1_GET_SIZE(pThis->cPorts);
+ PMptConfigurationPageSASIOUnit1 pSASPage1 = (PMptConfigurationPageSASIOUnit1)RTMemAllocZ(pPages->cbSASIOUnitPage1);
+ AssertPtr(pSASPage1);
+
+ MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pSASPage1, pPages->cbSASIOUnitPage1,
+ 1, MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE,
+ MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT);
+ pSASPage1->u.fields.u8NumPhys = pSASPage0->u.fields.u8NumPhys;
+ pSASPage1->u.fields.u16ControlFlags = 0;
+ pSASPage1->u.fields.u16AdditionalControlFlags = 0;
+ pPages->pSASIOUnitPage1 = pSASPage1;
+
+ /* SAS I/O unit page 2 - Port specific information. */
+ pPages->SASIOUnitPage2.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pPages->SASIOUnitPage2.u.fields.ExtHeader.u8PageNumber = 2;
+ pPages->SASIOUnitPage2.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT;
+ pPages->SASIOUnitPage2.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASIOUnit2) / 4;
+
+ /* SAS I/O unit page 3 - Port specific information. */
+ pPages->SASIOUnitPage3.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pPages->SASIOUnitPage3.u.fields.ExtHeader.u8PageNumber = 3;
+ pPages->SASIOUnitPage3.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT;
+ pPages->SASIOUnitPage3.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASIOUnit3) / 4;
+
+ pPages->cPHYs = pThis->cPorts;
+ pPages->paPHYs = (PMptPHY)RTMemAllocZ(pPages->cPHYs * sizeof(MptPHY));
+ AssertPtr(pPages->paPHYs);
+
+ /* Initialize the PHY configuration */
+ for (unsigned i = 0; i < pThis->cPorts; i++)
+ {
+ PMptPHY pPHYPages = &pPages->paPHYs[i];
+ uint16_t u16ControllerHandle = lsilogicGetHandle(pThis);
+
+ pManufacturingPage7->u.fields.aPHY[i].u8Location = LSILOGICSCSI_MANUFACTURING7_LOCATION_AUTO;
+
+ pSASPage0->u.fields.aPHY[i].u8Port = i;
+ pSASPage0->u.fields.aPHY[i].u8PortFlags = 0;
+ pSASPage0->u.fields.aPHY[i].u8PhyFlags = 0;
+ pSASPage0->u.fields.aPHY[i].u8NegotiatedLinkRate = LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_FAILED;
+ pSASPage0->u.fields.aPHY[i].u32ControllerPhyDeviceInfo = LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_SET(LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_NO);
+ pSASPage0->u.fields.aPHY[i].u16ControllerDevHandle = u16ControllerHandle;
+ pSASPage0->u.fields.aPHY[i].u16AttachedDevHandle = 0; /* No device attached. */
+ pSASPage0->u.fields.aPHY[i].u32DiscoveryStatus = 0; /* No errors */
+
+ pSASPage1->u.fields.aPHY[i].u8Port = i;
+ pSASPage1->u.fields.aPHY[i].u8PortFlags = 0;
+ pSASPage1->u.fields.aPHY[i].u8PhyFlags = 0;
+ pSASPage1->u.fields.aPHY[i].u8MaxMinLinkRate = LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MIN_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_15GB)
+ | LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MAX_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_30GB);
+ pSASPage1->u.fields.aPHY[i].u32ControllerPhyDeviceInfo = LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_SET(LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_NO);
+
+ /* SAS PHY page 0. */
+ pPHYPages->SASPHYPage0.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pPHYPages->SASPHYPage0.u.fields.ExtHeader.u8PageNumber = 0;
+ pPHYPages->SASPHYPage0.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASPHYS;
+ pPHYPages->SASPHYPage0.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASPHY0) / 4;
+ pPHYPages->SASPHYPage0.u.fields.u8AttachedPhyIdentifier = i;
+ pPHYPages->SASPHYPage0.u.fields.u32AttachedDeviceInfo = LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_SET(LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_NO);
+ pPHYPages->SASPHYPage0.u.fields.u8ProgrammedLinkRate = LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MIN_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_15GB)
+ | LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MAX_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_30GB);
+ pPHYPages->SASPHYPage0.u.fields.u8HwLinkRate = LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MIN_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_15GB)
+ | LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MAX_SET(LSILOGICSCSI_SASIOUNIT1_LINK_RATE_30GB);
+
+ /* SAS PHY page 1. */
+ pPHYPages->SASPHYPage1.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pPHYPages->SASPHYPage1.u.fields.ExtHeader.u8PageNumber = 1;
+ pPHYPages->SASPHYPage1.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASPHYS;
+ pPHYPages->SASPHYPage1.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASPHY1) / 4;
+
+ /* Settings for present devices. */
+ if (pThis->paDeviceStates[i].pDrvBase)
+ {
+ uint16_t u16DeviceHandle = lsilogicGetHandle(pThis);
+ SASADDRESS SASAddress;
+ PMptSASDevice pSASDevice = (PMptSASDevice)RTMemAllocZ(sizeof(MptSASDevice));
+ AssertPtr(pSASDevice);
+
+ memset(&SASAddress, 0, sizeof(SASADDRESS));
+ lsilogicSASAddressGenerate(&SASAddress, i);
+
+ pSASPage0->u.fields.aPHY[i].u8NegotiatedLinkRate = LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_SET(LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_30GB);
+ pSASPage0->u.fields.aPHY[i].u32ControllerPhyDeviceInfo = LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_SET(LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_END)
+ | LSILOGICSCSI_SASIOUNIT0_DEVICE_SSP_TARGET;
+ pSASPage0->u.fields.aPHY[i].u16AttachedDevHandle = u16DeviceHandle;
+ pSASPage1->u.fields.aPHY[i].u32ControllerPhyDeviceInfo = LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_SET(LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_END)
+ | LSILOGICSCSI_SASIOUNIT0_DEVICE_SSP_TARGET;
+ pSASPage0->u.fields.aPHY[i].u16ControllerDevHandle = u16DeviceHandle;
+
+ pPHYPages->SASPHYPage0.u.fields.u32AttachedDeviceInfo = LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_SET(LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_END);
+ pPHYPages->SASPHYPage0.u.fields.SASAddress = SASAddress;
+ pPHYPages->SASPHYPage0.u.fields.u16OwnerDevHandle = u16DeviceHandle;
+ pPHYPages->SASPHYPage0.u.fields.u16AttachedDevHandle = u16DeviceHandle;
+
+ /* SAS device page 0. */
+ pSASDevice->SASDevicePage0.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pSASDevice->SASDevicePage0.u.fields.ExtHeader.u8PageNumber = 0;
+ pSASDevice->SASDevicePage0.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE;
+ pSASDevice->SASDevicePage0.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASDevice0) / 4;
+ pSASDevice->SASDevicePage0.u.fields.SASAddress = SASAddress;
+ pSASDevice->SASDevicePage0.u.fields.u16ParentDevHandle = u16ControllerHandle;
+ pSASDevice->SASDevicePage0.u.fields.u8PhyNum = i;
+ pSASDevice->SASDevicePage0.u.fields.u8AccessStatus = LSILOGICSCSI_SASDEVICE0_STATUS_NO_ERRORS;
+ pSASDevice->SASDevicePage0.u.fields.u16DevHandle = u16DeviceHandle;
+ pSASDevice->SASDevicePage0.u.fields.u8TargetID = i;
+ pSASDevice->SASDevicePage0.u.fields.u8Bus = 0;
+ pSASDevice->SASDevicePage0.u.fields.u32DeviceInfo = LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_SET(LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_END)
+ | LSILOGICSCSI_SASIOUNIT0_DEVICE_SSP_TARGET;
+ pSASDevice->SASDevicePage0.u.fields.u16Flags = LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_PRESENT
+ | LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_MAPPED_TO_BUS_AND_TARGET_ID
+ | LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_MAPPING_PERSISTENT;
+ pSASDevice->SASDevicePage0.u.fields.u8PhysicalPort = i;
+
+ /* SAS device page 1. */
+ pSASDevice->SASDevicePage1.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pSASDevice->SASDevicePage1.u.fields.ExtHeader.u8PageNumber = 1;
+ pSASDevice->SASDevicePage1.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE;
+ pSASDevice->SASDevicePage1.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASDevice1) / 4;
+ pSASDevice->SASDevicePage1.u.fields.SASAddress = SASAddress;
+ pSASDevice->SASDevicePage1.u.fields.u16DevHandle = u16DeviceHandle;
+ pSASDevice->SASDevicePage1.u.fields.u8TargetID = i;
+ pSASDevice->SASDevicePage1.u.fields.u8Bus = 0;
+
+ /* SAS device page 2. */
+ pSASDevice->SASDevicePage2.u.fields.ExtHeader.u8PageType = MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY
+ | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED;
+ pSASDevice->SASDevicePage2.u.fields.ExtHeader.u8PageNumber = 2;
+ pSASDevice->SASDevicePage2.u.fields.ExtHeader.u8ExtPageType = MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE;
+ pSASDevice->SASDevicePage2.u.fields.ExtHeader.u16ExtPageLength = sizeof(MptConfigurationPageSASDevice2) / 4;
+ pSASDevice->SASDevicePage2.u.fields.SASAddress = SASAddress;
+
+ /* Link into device list. */
+ if (!pPages->cDevices)
+ {
+ pPages->pSASDeviceHead = pSASDevice;
+ pPages->pSASDeviceTail = pSASDevice;
+ pPages->cDevices = 1;
+ }
+ else
+ {
+ pSASDevice->pPrev = pPages->pSASDeviceTail;
+ pPages->pSASDeviceTail->pNext = pSASDevice;
+ pPages->pSASDeviceTail = pSASDevice;
+ pPages->cDevices++;
+ }
+ }
+ }
+}
+
+/**
+ * Initializes the configuration pages.
+ *
+ * @returns nothing
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPages(PLSILOGICSCSI pThis)
+{
+ /* Initialize the common pages. */
+ PMptConfigurationPagesSupported pPages = (PMptConfigurationPagesSupported)RTMemAllocZ(sizeof(MptConfigurationPagesSupported));
+
+ pThis->pConfigurationPages = pPages;
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /* Clear everything first. */
+ memset(pPages, 0, sizeof(MptConfigurationPagesSupported));
+
+ /* Manufacturing Page 0. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage0,
+ MptConfigurationPageManufacturing0, 0,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+ strncpy((char *)pPages->ManufacturingPage0.u.fields.abChipName, "VBox MPT Fusion", 16);
+ strncpy((char *)pPages->ManufacturingPage0.u.fields.abChipRevision, "1.0", 8);
+ strncpy((char *)pPages->ManufacturingPage0.u.fields.abBoardName, "VBox MPT Fusion", 16);
+ strncpy((char *)pPages->ManufacturingPage0.u.fields.abBoardAssembly, "SUN", 8);
+ memcpy(pPages->ManufacturingPage0.u.fields.abBoardTracerNumber, "CAFECAFECAFECAFE", 16);
+
+ /* Manufacturing Page 1 - I don't know what this contains so we leave it 0 for now. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage1,
+ MptConfigurationPageManufacturing1, 1,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+
+ /* Manufacturing Page 2. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage2,
+ MptConfigurationPageManufacturing2, 2,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ pPages->ManufacturingPage2.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_SPI_DEVICE_ID;
+ pPages->ManufacturingPage2.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_SPI_REVISION_ID;
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ pPages->ManufacturingPage2.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_SAS_DEVICE_ID;
+ pPages->ManufacturingPage2.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_SAS_REVISION_ID;
+ }
+
+ /* Manufacturing Page 3. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage3,
+ MptConfigurationPageManufacturing3, 3,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ pPages->ManufacturingPage3.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_SPI_DEVICE_ID;
+ pPages->ManufacturingPage3.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_SPI_REVISION_ID;
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ pPages->ManufacturingPage3.u.fields.u16PCIDeviceID = LSILOGICSCSI_PCI_SAS_DEVICE_ID;
+ pPages->ManufacturingPage3.u.fields.u8PCIRevisionID = LSILOGICSCSI_PCI_SAS_REVISION_ID;
+ }
+
+ /* Manufacturing Page 4 - I don't know what this contains so we leave it 0 for now. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage4,
+ MptConfigurationPageManufacturing4, 4,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+
+ /* Manufacturing Page 5 - WWID settings. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage5,
+ MptConfigurationPageManufacturing5, 5,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY);
+
+ /* Manufacturing Page 6 - Product specific settings. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage6,
+ MptConfigurationPageManufacturing6, 6,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* Manufacturing Page 8 - Product specific settings. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage8,
+ MptConfigurationPageManufacturing8, 8,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* Manufacturing Page 9 - Product specific settings. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage9,
+ MptConfigurationPageManufacturing9, 9,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* Manufacturing Page 10 - Product specific settings. */
+ MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(&pPages->ManufacturingPage10,
+ MptConfigurationPageManufacturing10, 10,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* I/O Unit page 0. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(&pPages->IOUnitPage0,
+ MptConfigurationPageIOUnit0, 0,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ pPages->IOUnitPage0.u.fields.u64UniqueIdentifier = 0xcafe;
+
+ /* I/O Unit page 1. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(&pPages->IOUnitPage1,
+ MptConfigurationPageIOUnit1, 1,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ pPages->IOUnitPage1.u.fields.fSingleFunction = true;
+ pPages->IOUnitPage1.u.fields.fAllPathsMapped = false;
+ pPages->IOUnitPage1.u.fields.fIntegratedRAIDDisabled = true;
+ pPages->IOUnitPage1.u.fields.f32BitAccessForced = false;
+
+ /* I/O Unit page 2. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(&pPages->IOUnitPage2,
+ MptConfigurationPageIOUnit2, 2,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT);
+ pPages->IOUnitPage2.u.fields.fPauseOnError = false;
+ pPages->IOUnitPage2.u.fields.fVerboseModeEnabled = false;
+ pPages->IOUnitPage2.u.fields.fDisableColorVideo = false;
+ pPages->IOUnitPage2.u.fields.fNotHookInt40h = false;
+ pPages->IOUnitPage2.u.fields.u32BIOSVersion = 0xcafecafe;
+ pPages->IOUnitPage2.u.fields.aAdapterOrder[0].fAdapterEnabled = true;
+ pPages->IOUnitPage2.u.fields.aAdapterOrder[0].fAdapterEmbedded = true;
+ pPages->IOUnitPage2.u.fields.aAdapterOrder[0].u8PCIBusNumber = 0;
+ pPages->IOUnitPage2.u.fields.aAdapterOrder[0].u8PCIDevFn = pThis->PciDev.uDevFn;
+
+ /* I/O Unit page 3. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(&pPages->IOUnitPage3,
+ MptConfigurationPageIOUnit3, 3,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+ pPages->IOUnitPage3.u.fields.u8GPIOCount = 0;
+
+ /* I/O Unit page 4. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(&pPages->IOUnitPage4,
+ MptConfigurationPageIOUnit4, 4,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* IOC page 0. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage0,
+ MptConfigurationPageIOC0, 0,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ pPages->IOCPage0.u.fields.u32TotalNVStore = 0;
+ pPages->IOCPage0.u.fields.u32FreeNVStore = 0;
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ pPages->IOCPage0.u.fields.u16VendorId = LSILOGICSCSI_PCI_VENDOR_ID;
+ pPages->IOCPage0.u.fields.u16DeviceId = LSILOGICSCSI_PCI_SPI_DEVICE_ID;
+ pPages->IOCPage0.u.fields.u8RevisionId = LSILOGICSCSI_PCI_SPI_REVISION_ID;
+ pPages->IOCPage0.u.fields.u32ClassCode = LSILOGICSCSI_PCI_SPI_CLASS_CODE;
+ pPages->IOCPage0.u.fields.u16SubsystemVendorId = LSILOGICSCSI_PCI_SPI_SUBSYSTEM_VENDOR_ID;
+ pPages->IOCPage0.u.fields.u16SubsystemId = LSILOGICSCSI_PCI_SPI_SUBSYSTEM_ID;
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ pPages->IOCPage0.u.fields.u16VendorId = LSILOGICSCSI_PCI_VENDOR_ID;
+ pPages->IOCPage0.u.fields.u16DeviceId = LSILOGICSCSI_PCI_SAS_DEVICE_ID;
+ pPages->IOCPage0.u.fields.u8RevisionId = LSILOGICSCSI_PCI_SAS_REVISION_ID;
+ pPages->IOCPage0.u.fields.u32ClassCode = LSILOGICSCSI_PCI_SAS_CLASS_CODE;
+ pPages->IOCPage0.u.fields.u16SubsystemVendorId = LSILOGICSCSI_PCI_SAS_SUBSYSTEM_VENDOR_ID;
+ pPages->IOCPage0.u.fields.u16SubsystemId = LSILOGICSCSI_PCI_SAS_SUBSYSTEM_ID;
+ }
+
+ /* IOC page 1. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage1,
+ MptConfigurationPageIOC1, 1,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+ pPages->IOCPage1.u.fields.fReplyCoalescingEnabled = false;
+ pPages->IOCPage1.u.fields.u32CoalescingTimeout = 0;
+ pPages->IOCPage1.u.fields.u8CoalescingDepth = 0;
+
+ /* IOC page 2. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage2,
+ MptConfigurationPageIOC2, 2,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ /* Everything else here is 0. */
+
+ /* IOC page 3. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage3,
+ MptConfigurationPageIOC3, 3,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ /* Everything else here is 0. */
+
+ /* IOC page 4. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage4,
+ MptConfigurationPageIOC4, 4,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ /* Everything else here is 0. */
+
+ /* IOC page 6. */
+ MPT_CONFIG_PAGE_HEADER_INIT_IOC(&pPages->IOCPage6,
+ MptConfigurationPageIOC6, 6,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY);
+ /* Everything else here is 0. */
+
+ /* BIOS page 1. */
+ MPT_CONFIG_PAGE_HEADER_INIT_BIOS(&pPages->BIOSPage1,
+ MptConfigurationPageBIOS1, 1,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* BIOS page 2. */
+ MPT_CONFIG_PAGE_HEADER_INIT_BIOS(&pPages->BIOSPage2,
+ MptConfigurationPageBIOS2, 2,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ /* BIOS page 4. */
+ MPT_CONFIG_PAGE_HEADER_INIT_BIOS(&pPages->BIOSPage4,
+ MptConfigurationPageBIOS4, 4,
+ MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE);
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ lsilogicR3InitializeConfigurationPagesSpi(pThis);
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ lsilogicR3InitializeConfigurationPagesSas(pThis);
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+}
+
+/**
+ * @callback_method_impl{FNPDMQUEUEDEV, Transmit queue consumer.}
+ */
+static DECLCALLBACK(bool) lsilogicR3NotifyQueueConsumer(PPDMDEVINS pDevIns, PPDMQUEUEITEMCORE pItem)
+{
+ RT_NOREF(pItem);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pDevIns=%#p pItem=%#p\n", pDevIns, pItem));
+
+ rc = SUPSemEventSignal(pThis->pSupDrvSession, pThis->hEvtProcess);
+ AssertRC(rc);
+
+ return true;
+}
+
+/**
+ * Sets the emulated controller type from a given string.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the LsiLogic device state.
+ * @param pcszCtrlType The string to use.
+ */
+static int lsilogicR3GetCtrlTypeFromString(PLSILOGICSCSI pThis, const char *pcszCtrlType)
+{
+ int rc = VERR_INVALID_PARAMETER;
+
+ if (!RTStrCmp(pcszCtrlType, LSILOGICSCSI_PCI_SPI_CTRLNAME))
+ {
+ pThis->enmCtrlType = LSILOGICCTRLTYPE_SCSI_SPI;
+ rc = VINF_SUCCESS;
+ }
+ else if (!RTStrCmp(pcszCtrlType, LSILOGICSCSI_PCI_SAS_CTRLNAME))
+ {
+ pThis->enmCtrlType = LSILOGICCTRLTYPE_SCSI_SAS;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTIN, Legacy ISA port.}
+ */
+static DECLCALLBACK(int) lsilogicR3IsaIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser, cb);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ Assert(cb == 1);
+
+ uint8_t iRegister = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? Port - LSILOGIC_BIOS_IO_PORT
+ : Port - LSILOGIC_SAS_BIOS_IO_PORT;
+ int rc = vboxscsiReadRegister(&pThis->VBoxSCSI, iRegister, pu32);
+
+ Log2(("%s: pu32=%p:{%.*Rhxs} iRegister=%d rc=%Rrc\n",
+ __FUNCTION__, pu32, 1, pu32, iRegister, rc));
+
+ return rc;
+}
+
+/**
+ * Prepares a request from the BIOS.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static int lsilogicR3PrepareBiosScsiRequest(PLSILOGICSCSI pThis)
+{
+ int rc;
+ uint32_t uTargetDevice;
+ uint32_t uLun;
+ uint8_t *pbCdb;
+ size_t cbCdb;
+ size_t cbBuf;
+
+ rc = vboxscsiSetupRequest(&pThis->VBoxSCSI, &uLun, &pbCdb, &cbCdb, &cbBuf, &uTargetDevice);
+ AssertMsgRCReturn(rc, ("Setting up SCSI request failed rc=%Rrc\n", rc), rc);
+
+ if ( uTargetDevice < pThis->cDeviceStates
+ && pThis->paDeviceStates[uTargetDevice].pDrvBase)
+ {
+ PLSILOGICDEVICE pTgtDev = &pThis->paDeviceStates[uTargetDevice];
+ PDMMEDIAEXIOREQ hIoReq;
+ PLSILOGICREQ pReq;
+
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqAlloc(pTgtDev->pDrvMediaEx, &hIoReq, (void **)&pReq,
+ 0, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ AssertMsgRCReturn(rc, ("Getting task from cache failed rc=%Rrc\n", rc), rc);
+
+ pReq->fBIOS = true;
+ pReq->hIoReq = hIoReq;
+ pReq->pTargetDevice = pTgtDev;
+
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqSendScsiCmd(pTgtDev->pDrvMediaEx, pReq->hIoReq, uLun,
+ pbCdb, cbCdb, PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN,
+ cbBuf, NULL, 0, &pReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc == VINF_SUCCESS || rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ {
+ uint8_t u8ScsiSts = pReq->u8ScsiSts;
+ pTgtDev->pDrvMediaEx->pfnIoReqFree(pTgtDev->pDrvMediaEx, pReq->hIoReq);
+ rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, u8ScsiSts);
+ }
+ else if (rc == VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+
+ return rc;
+ }
+
+ /* Device is not present. */
+ AssertMsg(pbCdb[0] == SCSI_INQUIRY,
+ ("Device is not present but command is not inquiry\n"));
+
+ SCSIINQUIRYDATA ScsiInquiryData;
+
+ memset(&ScsiInquiryData, 0, sizeof(SCSIINQUIRYDATA));
+ ScsiInquiryData.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+ ScsiInquiryData.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+
+ memcpy(pThis->VBoxSCSI.pbBuf, &ScsiInquiryData, 5);
+
+ rc = vboxscsiRequestFinished(&pThis->VBoxSCSI, SCSI_STATUS_OK);
+ AssertMsgRCReturn(rc, ("Finishing BIOS SCSI request failed rc=%Rrc\n", rc), rc);
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTOUT, Legacy ISA port.}
+ */
+static DECLCALLBACK(int) lsilogicR3IsaIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pvUser, cb);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x Port=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, Port));
+
+ Assert(cb == 1);
+
+ /*
+ * If there is already a request form the BIOS pending ignore this write
+ * because it should not happen.
+ */
+ if (ASMAtomicReadBool(&pThis->fBiosReqPending))
+ return VINF_SUCCESS;
+
+ uint8_t iRegister = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? Port - LSILOGIC_BIOS_IO_PORT
+ : Port - LSILOGIC_SAS_BIOS_IO_PORT;
+ int rc = vboxscsiWriteRegister(&pThis->VBoxSCSI, iRegister, (uint8_t)u32);
+ if (rc == VERR_MORE_DATA)
+ {
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, true);
+ /* Send a notifier to the PDM queue that there are pending requests. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotificationQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pThis->CTX_SUFF(pNotificationQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+ else if (RT_FAILURE(rc))
+ AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTOUTSTRING,
+ * Port I/O Handler for primary port range OUT string operations.}
+ */
+static DECLCALLBACK(int) lsilogicR3IsaIOPortWriteStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port,
+ uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pvUser);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ Log2(("#%d %s: pvUser=%#p cb=%d Port=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+ uint8_t iRegister = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? Port - LSILOGIC_BIOS_IO_PORT
+ : Port - LSILOGIC_SAS_BIOS_IO_PORT;
+ int rc = vboxscsiWriteString(pDevIns, &pThis->VBoxSCSI, iRegister, pbSrc, pcTransfers, cb);
+ if (rc == VERR_MORE_DATA)
+ {
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, true);
+ /* Send a notifier to the PDM queue that there are pending requests. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotificationQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pThis->CTX_SUFF(pNotificationQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+ else if (RT_FAILURE(rc))
+ AssertMsgFailed(("Writing BIOS register failed %Rrc\n", rc));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTINSTRING,
+ * Port I/O Handler for primary port range IN string operations.}
+ */
+static DECLCALLBACK(int) lsilogicR3IsaIOPortReadStr(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port,
+ uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pvUser);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ LogFlowFunc(("#%d %s: pvUser=%#p cb=%d Port=%#x\n", pDevIns->iInstance, __FUNCTION__, pvUser, cb, Port));
+
+ uint8_t iRegister = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? Port - LSILOGIC_BIOS_IO_PORT
+ : Port - LSILOGIC_SAS_BIOS_IO_PORT;
+ return vboxscsiReadString(pDevIns, &pThis->VBoxSCSI, iRegister, pbDst, pcTransfers, cb);
+}
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) lsilogicR3Map(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb,
+ PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(pPciDev);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ int rc = VINF_SUCCESS;
+ const char *pcszCtrl = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? "LsiLogic"
+ : "LsiLogicSas";
+ const char *pcszDiag = pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? "LsiLogicDiag"
+ : "LsiLogicSasDiag";
+
+ Log2(("%s: registering area at GCPhysAddr=%RGp cb=%RGp\n", __FUNCTION__, GCPhysAddress, cb));
+
+ AssertMsg( (enmType == PCI_ADDRESS_SPACE_MEM && cb >= LSILOGIC_PCI_SPACE_MEM_SIZE)
+ || (enmType == PCI_ADDRESS_SPACE_IO && cb >= LSILOGIC_PCI_SPACE_IO_SIZE),
+ ("PCI region type and size do not match\n"));
+
+ if (enmType == PCI_ADDRESS_SPACE_MEM && iRegion == 1)
+ {
+ /*
+ * Non-4-byte read access to LSILOGIC_REG_REPLY_QUEUE may cause real strange behavior
+ * because the data is part of a physical guest address. But some drivers use 1-byte
+ * access to scan for SCSI controllers. So, we simplify our code by telling IOM to
+ * read DWORDs.
+ *
+ * Regarding writes, we couldn't find anything specific in the specs about what should
+ * happen. So far we've ignored unaligned writes and assumed the missing bytes of
+ * byte and word access to be zero. We suspect that IOMMMIO_FLAGS_WRITE_ONLY_DWORD
+ * or IOMMMIO_FLAGS_WRITE_DWORD_ZEROED would be the most appropriate here, but since we
+ * don't have real hw to test one, the old behavior is kept exactly like it used to be.
+ */
+ /** @todo Check out unaligned writes and non-dword writes on real LsiLogic
+ * hardware. */
+ rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ lsilogicMMIOWrite, lsilogicMMIORead, pcszCtrl);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/,
+ "lsilogicMMIOWrite", "lsilogicMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/,
+ "lsilogicMMIOWrite", "lsilogicMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->GCPhysMMIOBase = GCPhysAddress;
+ }
+ else if (enmType == PCI_ADDRESS_SPACE_MEM && iRegion == 2)
+ {
+ /* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
+ rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ lsilogicDiagnosticWrite, lsilogicDiagnosticRead, pcszDiag);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/,
+ "lsilogicDiagnosticWrite", "lsilogicDiagnosticRead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/,
+ "lsilogicDiagnosticWrite", "lsilogicDiagnosticRead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ else if (enmType == PCI_ADDRESS_SPACE_IO)
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+ NULL, lsilogicIOPortWrite, lsilogicIOPortRead, NULL, NULL, pcszCtrl);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+ 0, "lsilogicIOPortWrite", "lsilogicIOPortRead", NULL, NULL, pcszCtrl);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, (RTIOPORT)GCPhysAddress, LSILOGIC_PCI_SPACE_IO_SIZE,
+ 0, "lsilogicIOPortWrite", "lsilogicIOPortRead", NULL, NULL, pcszCtrl);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->IOPortBase = (RTIOPORT)GCPhysAddress;
+ }
+ else
+ AssertMsgFailed(("Invalid enmType=%d iRegion=%d\n", enmType, iRegion));
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{PFNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) lsilogicR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ bool fVerbose = false;
+
+ /*
+ * Parse args.
+ */
+ if (pszArgs)
+ fVerbose = strstr(pszArgs, "verbose") != NULL;
+
+ /*
+ * Show info.
+ */
+ pHlp->pfnPrintf(pHlp,
+ "%s#%d: port=%RTiop mmio=%RGp max-devices=%u GC=%RTbool R0=%RTbool\n",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance,
+ pThis->IOPortBase, pThis->GCPhysMMIOBase,
+ pThis->cDeviceStates,
+ pThis->fGCEnabled ? true : false,
+ pThis->fR0Enabled ? true : false);
+
+ /*
+ * Show general state.
+ */
+ pHlp->pfnPrintf(pHlp, "enmState=%u\n", pThis->enmState);
+ pHlp->pfnPrintf(pHlp, "enmWhoInit=%u\n", pThis->enmWhoInit);
+ pHlp->pfnPrintf(pHlp, "enmDoorbellState=%d\n", pThis->enmDoorbellState);
+ pHlp->pfnPrintf(pHlp, "fDiagnosticEnabled=%RTbool\n", pThis->fDiagnosticEnabled);
+ pHlp->pfnPrintf(pHlp, "fNotificationSent=%RTbool\n", pThis->fNotificationSent);
+ pHlp->pfnPrintf(pHlp, "fEventNotificationEnabled=%RTbool\n", pThis->fEventNotificationEnabled);
+ pHlp->pfnPrintf(pHlp, "uInterruptMask=%#x\n", pThis->uInterruptMask);
+ pHlp->pfnPrintf(pHlp, "uInterruptStatus=%#x\n", pThis->uInterruptStatus);
+ pHlp->pfnPrintf(pHlp, "u16IOCFaultCode=%#06x\n", pThis->u16IOCFaultCode);
+ pHlp->pfnPrintf(pHlp, "u32HostMFAHighAddr=%#x\n", pThis->u32HostMFAHighAddr);
+ pHlp->pfnPrintf(pHlp, "u32SenseBufferHighAddr=%#x\n", pThis->u32SenseBufferHighAddr);
+ pHlp->pfnPrintf(pHlp, "cMaxDevices=%u\n", pThis->cMaxDevices);
+ pHlp->pfnPrintf(pHlp, "cMaxBuses=%u\n", pThis->cMaxBuses);
+ pHlp->pfnPrintf(pHlp, "cbReplyFrame=%u\n", pThis->cbReplyFrame);
+ pHlp->pfnPrintf(pHlp, "cReplyQueueEntries=%u\n", pThis->cReplyQueueEntries);
+ pHlp->pfnPrintf(pHlp, "cRequestQueueEntries=%u\n", pThis->cRequestQueueEntries);
+ pHlp->pfnPrintf(pHlp, "cPorts=%u\n", pThis->cPorts);
+
+ /*
+ * Show queue status.
+ */
+ pHlp->pfnPrintf(pHlp, "uReplyFreeQueueNextEntryFreeWrite=%u\n", pThis->uReplyFreeQueueNextEntryFreeWrite);
+ pHlp->pfnPrintf(pHlp, "uReplyFreeQueueNextAddressRead=%u\n", pThis->uReplyFreeQueueNextAddressRead);
+ pHlp->pfnPrintf(pHlp, "uReplyPostQueueNextEntryFreeWrite=%u\n", pThis->uReplyPostQueueNextEntryFreeWrite);
+ pHlp->pfnPrintf(pHlp, "uReplyPostQueueNextAddressRead=%u\n", pThis->uReplyPostQueueNextAddressRead);
+ pHlp->pfnPrintf(pHlp, "uRequestQueueNextEntryFreeWrite=%u\n", pThis->uRequestQueueNextEntryFreeWrite);
+ pHlp->pfnPrintf(pHlp, "uRequestQueueNextAddressRead=%u\n", pThis->uRequestQueueNextAddressRead);
+
+ /*
+ * Show queue content if verbose
+ */
+ if (fVerbose)
+ {
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnPrintf(pHlp, "RFQ[%u]=%#x\n", i, pThis->pReplyFreeQueueBaseR3[i]);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnPrintf(pHlp, "RPQ[%u]=%#x\n", i, pThis->pReplyPostQueueBaseR3[i]);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ pHlp->pfnPrintf(pHlp, "ReqQ[%u]=%#x\n", i, pThis->pRequestQueueBaseR3[i]);
+ }
+
+ /*
+ * Print the device status.
+ */
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[i];
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ pHlp->pfnPrintf(pHlp, "Device[%u]: device-attached=%RTbool cOutstandingRequests=%u\n",
+ i, pDevice->pDrvBase != NULL, pDevice->cOutstandingRequests);
+ }
+}
+
+/**
+ * Allocate the queues.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static int lsilogicR3QueuesAlloc(PLSILOGICSCSI pThis)
+{
+ PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3);
+ uint32_t cbQueues;
+
+ Assert(!pThis->pReplyFreeQueueBaseR3);
+
+ cbQueues = 2*pThis->cReplyQueueEntries * sizeof(uint32_t);
+ cbQueues += pThis->cRequestQueueEntries * sizeof(uint32_t);
+ int rc = MMHyperAlloc(pVM, cbQueues, 1, MM_TAG_PDM_DEVICE_USER,
+ (void **)&pThis->pReplyFreeQueueBaseR3);
+ if (RT_FAILURE(rc))
+ return VERR_NO_MEMORY;
+ pThis->pReplyFreeQueueBaseR0 = MMHyperR3ToR0(pVM, (void *)pThis->pReplyFreeQueueBaseR3);
+ pThis->pReplyFreeQueueBaseRC = MMHyperR3ToRC(pVM, (void *)pThis->pReplyFreeQueueBaseR3);
+
+ pThis->pReplyPostQueueBaseR3 = pThis->pReplyFreeQueueBaseR3 + pThis->cReplyQueueEntries;
+ pThis->pReplyPostQueueBaseR0 = MMHyperR3ToR0(pVM, (void *)pThis->pReplyPostQueueBaseR3);
+ pThis->pReplyPostQueueBaseRC = MMHyperR3ToRC(pVM, (void *)pThis->pReplyPostQueueBaseR3);
+
+ pThis->pRequestQueueBaseR3 = pThis->pReplyPostQueueBaseR3 + pThis->cReplyQueueEntries;
+ pThis->pRequestQueueBaseR0 = MMHyperR3ToR0(pVM, (void *)pThis->pRequestQueueBaseR3);
+ pThis->pRequestQueueBaseRC = MMHyperR3ToRC(pVM, (void *)pThis->pRequestQueueBaseR3);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Free the hyper memory used or the queues.
+ *
+ * @returns nothing.
+ *
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3QueuesFree(PLSILOGICSCSI pThis)
+{
+ PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3);
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pReplyFreeQueueBaseR3);
+
+ rc = MMHyperFree(pVM, (void *)pThis->pReplyFreeQueueBaseR3);
+ AssertRC(rc);
+
+ pThis->pReplyFreeQueueBaseR3 = NULL;
+ pThis->pReplyPostQueueBaseR3 = NULL;
+ pThis->pRequestQueueBaseR3 = NULL;
+}
+
+
+/* The worker thread. */
+static DECLCALLBACK(int) lsilogicR3Worker(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PLSILOGICSCSI pThis = (PLSILOGICSCSI)pThread->pvUser;
+ int rc = VINF_SUCCESS;
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, true);
+ bool fNotificationSent = ASMAtomicXchgBool(&pThis->fNotificationSent, false);
+ if (!fNotificationSent)
+ {
+ Assert(ASMAtomicReadBool(&pThis->fWrkThreadSleeping));
+ rc = SUPSemEventWaitNoResume(pThis->pSupDrvSession, pThis->hEvtProcess, RT_INDEFINITE_WAIT);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+ LogFlowFunc(("Woken up with rc=%Rrc\n", rc));
+ ASMAtomicWriteBool(&pThis->fNotificationSent, false);
+ }
+
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, false);
+
+ /* Check whether there is a BIOS request pending and process it first. */
+ if (ASMAtomicReadBool(&pThis->fBiosReqPending))
+ {
+ rc = lsilogicR3PrepareBiosScsiRequest(pThis);
+ AssertRC(rc);
+ ASMAtomicXchgBool(&pThis->fBiosReqPending, false);
+ }
+
+ /* Only process request which arrived before we received the notification. */
+ uint32_t uRequestQueueNextEntryWrite = ASMAtomicReadU32(&pThis->uRequestQueueNextEntryFreeWrite);
+
+ /* Go through the messages now and process them. */
+ while ( RT_LIKELY(pThis->enmState == LSILOGICSTATE_OPERATIONAL)
+ && (pThis->uRequestQueueNextAddressRead != uRequestQueueNextEntryWrite))
+ {
+ MptRequestUnion GuestRequest;
+ uint32_t u32RequestMessageFrameDesc = pThis->CTX_SUFF(pRequestQueueBase)[pThis->uRequestQueueNextAddressRead];
+ RTGCPHYS GCPhysMessageFrameAddr = LSILOGIC_RTGCPHYS_FROM_U32(pThis->u32HostMFAHighAddr,
+ (u32RequestMessageFrameDesc & ~0x07));
+
+ /* Read the message header from the guest first. */
+ PDMDevHlpPhysRead(pDevIns, GCPhysMessageFrameAddr, &GuestRequest, sizeof(MptMessageHdr));
+
+ /* Determine the size of the request. */
+ uint32_t cbRequest = 0;
+ switch (GuestRequest.Header.u8Function)
+ {
+ case MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST:
+ cbRequest = sizeof(MptSCSIIORequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT:
+ cbRequest = sizeof(MptSCSITaskManagementRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_IOC_INIT:
+ cbRequest = sizeof(MptIOCInitRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS:
+ cbRequest = sizeof(MptIOCFactsRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_CONFIG:
+ cbRequest = sizeof(MptConfigurationRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS:
+ cbRequest = sizeof(MptPortFactsRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE:
+ cbRequest = sizeof(MptPortEnableRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION:
+ cbRequest = sizeof(MptEventNotificationRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK:
+ AssertMsgFailed(("todo\n"));
+ //cbRequest = sizeof(MptEventAckRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_FW_DOWNLOAD:
+ cbRequest = sizeof(MptFWDownloadRequest);
+ break;
+ case MPT_MESSAGE_HDR_FUNCTION_FW_UPLOAD:
+ cbRequest = sizeof(MptFWUploadRequest);
+ break;
+ default:
+ AssertMsgFailed(("Unknown function issued %u\n", GuestRequest.Header.u8Function));
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INVALID_FUNCTION);
+ }
+
+ if (cbRequest != 0)
+ {
+ /* Read the complete message frame from guest memory now. */
+ PDMDevHlpPhysRead(pDevIns, GCPhysMessageFrameAddr, &GuestRequest, cbRequest);
+
+ /* Handle SCSI I/O requests now. */
+ if (GuestRequest.Header.u8Function == MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST)
+ {
+ rc = lsilogicR3ProcessSCSIIORequest(pThis, GCPhysMessageFrameAddr, &GuestRequest);
+ AssertRC(rc);
+ }
+ else
+ {
+ MptReplyUnion Reply;
+ rc = lsilogicR3ProcessMessageRequest(pThis, &GuestRequest.Header, &Reply);
+ AssertRC(rc);
+ }
+
+ pThis->uRequestQueueNextAddressRead++;
+ pThis->uRequestQueueNextAddressRead %= pThis->cRequestQueueEntries;
+ }
+ } /* While request frames available. */
+ } /* While running */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the worker thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The pcnet device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) lsilogicR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ return SUPSemEventSignal(pThis->pSupDrvSession, pThis->hEvtProcess);
+}
+
+
+/**
+ * Kicks the controller to process pending tasks after the VM was resumed
+ * or loaded from a saved state.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the LsiLogic device state.
+ */
+static void lsilogicR3Kick(PLSILOGICSCSI pThis)
+{
+ if (pThis->fNotificationSent)
+ {
+ /* Send a notifier to the PDM queue that there are pending requests. */
+ PPDMQUEUEITEMCORE pItem = PDMQueueAlloc(pThis->CTX_SUFF(pNotificationQueue));
+ AssertMsg(pItem, ("Allocating item for queue failed\n"));
+ PDMQueueInsert(pThis->CTX_SUFF(pNotificationQueue), (PPDMQUEUEITEMCORE)pItem);
+ }
+}
+
+
+/*
+ * Saved state.
+ */
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ SSMR3PutU32(pSSM, pThis->enmCtrlType);
+ SSMR3PutU32(pSSM, pThis->cDeviceStates);
+ SSMR3PutU32(pSSM, pThis->cPorts);
+
+ /* Save the device config. */
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ SSMR3PutBool(pSSM, pThis->paDeviceStates[i].pDrvBase != NULL);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ /* Every device first. */
+ lsilogicR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ SSMR3PutU32(pSSM, pDevice->cOutstandingRequests);
+
+ /* Query all suspended requests and store them in the request queue. */
+ if (pDevice->pDrvMediaEx)
+ {
+ uint32_t cReqsRedo = pDevice->pDrvMediaEx->pfnIoReqGetSuspendedCount(pDevice->pDrvMediaEx);
+ if (cReqsRedo)
+ {
+ PDMMEDIAEXIOREQ hIoReq;
+ PLSILOGICREQ pReq;
+ int rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedStart(pDevice->pDrvMediaEx, &hIoReq,
+ (void **)&pReq);
+ AssertRCBreak(rc);
+
+ for (;;)
+ {
+ if (!pReq->fBIOS)
+ {
+ /* Write only the lower 32bit part of the address. */
+ ASMAtomicWriteU32(&pThis->CTX_SUFF(pRequestQueueBase)[pThis->uRequestQueueNextEntryFreeWrite],
+ pReq->GCPhysMessageFrameAddr & UINT32_C(0xffffffff));
+
+ pThis->uRequestQueueNextEntryFreeWrite++;
+ pThis->uRequestQueueNextEntryFreeWrite %= pThis->cRequestQueueEntries;
+ }
+ else
+ {
+ AssertMsg(!pReq->pRedoNext, ("Only one BIOS task can be active!\n"));
+ vboxscsiSetRequestRedo(&pThis->VBoxSCSI);
+ }
+
+ cReqsRedo--;
+ if (!cReqsRedo)
+ break;
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pDevice->pDrvMediaEx, hIoReq,
+ &hIoReq, (void **)&pReq);
+ AssertRCBreak(rc);
+ }
+ }
+ }
+ }
+
+ /* Now the main device state. */
+ SSMR3PutU32 (pSSM, pThis->enmState);
+ SSMR3PutU32 (pSSM, pThis->enmWhoInit);
+ SSMR3PutU32 (pSSM, pThis->enmDoorbellState);
+ SSMR3PutBool (pSSM, pThis->fDiagnosticEnabled);
+ SSMR3PutBool (pSSM, pThis->fNotificationSent);
+ SSMR3PutBool (pSSM, pThis->fEventNotificationEnabled);
+ SSMR3PutU32 (pSSM, pThis->uInterruptMask);
+ SSMR3PutU32 (pSSM, pThis->uInterruptStatus);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aMessage); i++)
+ SSMR3PutU32 (pSSM, pThis->aMessage[i]);
+ SSMR3PutU32 (pSSM, pThis->iMessage);
+ SSMR3PutU32 (pSSM, pThis->cMessage);
+ SSMR3PutMem (pSSM, &pThis->ReplyBuffer, sizeof(pThis->ReplyBuffer));
+ SSMR3PutU32 (pSSM, pThis->uNextReplyEntryRead);
+ SSMR3PutU32 (pSSM, pThis->cReplySize);
+ SSMR3PutU16 (pSSM, pThis->u16IOCFaultCode);
+ SSMR3PutU32 (pSSM, pThis->u32HostMFAHighAddr);
+ SSMR3PutU32 (pSSM, pThis->u32SenseBufferHighAddr);
+ SSMR3PutU8 (pSSM, pThis->cMaxDevices);
+ SSMR3PutU8 (pSSM, pThis->cMaxBuses);
+ SSMR3PutU16 (pSSM, pThis->cbReplyFrame);
+ SSMR3PutU32 (pSSM, pThis->iDiagnosticAccess);
+ SSMR3PutU32 (pSSM, pThis->cReplyQueueEntries);
+ SSMR3PutU32 (pSSM, pThis->cRequestQueueEntries);
+ SSMR3PutU32 (pSSM, pThis->uReplyFreeQueueNextEntryFreeWrite);
+ SSMR3PutU32 (pSSM, pThis->uReplyFreeQueueNextAddressRead);
+ SSMR3PutU32 (pSSM, pThis->uReplyPostQueueNextEntryFreeWrite);
+ SSMR3PutU32 (pSSM, pThis->uReplyPostQueueNextAddressRead);
+ SSMR3PutU32 (pSSM, pThis->uRequestQueueNextEntryFreeWrite);
+ SSMR3PutU32 (pSSM, pThis->uRequestQueueNextAddressRead);
+
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ SSMR3PutU32(pSSM, pThis->pReplyFreeQueueBaseR3[i]);
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ SSMR3PutU32(pSSM, pThis->pReplyPostQueueBaseR3[i]);
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ SSMR3PutU32(pSSM, pThis->pRequestQueueBaseR3[i]);
+
+ SSMR3PutU16 (pSSM, pThis->u16NextHandle);
+
+ /* Save diagnostic memory register and data regions. */
+ SSMR3PutU32 (pSSM, pThis->u32DiagMemAddr);
+ SSMR3PutU32 (pSSM, lsilogicR3MemRegionsCount(pThis));
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThis->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ SSMR3PutU32(pSSM, pIt->u32AddrStart);
+ SSMR3PutU32(pSSM, pIt->u32AddrEnd);
+ SSMR3PutMem(pSSM, &pIt->au32Data[0], (pIt->u32AddrEnd - pIt->u32AddrStart + 1) * sizeof(uint32_t));
+ }
+
+ PMptConfigurationPagesSupported pPages = pThis->pConfigurationPages;
+
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage0, sizeof(MptConfigurationPageManufacturing0));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage1, sizeof(MptConfigurationPageManufacturing1));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage2, sizeof(MptConfigurationPageManufacturing2));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage3, sizeof(MptConfigurationPageManufacturing3));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage4, sizeof(MptConfigurationPageManufacturing4));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage5, sizeof(MptConfigurationPageManufacturing5));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage6, sizeof(MptConfigurationPageManufacturing6));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage8, sizeof(MptConfigurationPageManufacturing8));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage9, sizeof(MptConfigurationPageManufacturing9));
+ SSMR3PutMem (pSSM, &pPages->ManufacturingPage10, sizeof(MptConfigurationPageManufacturing10));
+ SSMR3PutMem (pSSM, &pPages->IOUnitPage0, sizeof(MptConfigurationPageIOUnit0));
+ SSMR3PutMem (pSSM, &pPages->IOUnitPage1, sizeof(MptConfigurationPageIOUnit1));
+ SSMR3PutMem (pSSM, &pPages->IOUnitPage2, sizeof(MptConfigurationPageIOUnit2));
+ SSMR3PutMem (pSSM, &pPages->IOUnitPage3, sizeof(MptConfigurationPageIOUnit3));
+ SSMR3PutMem (pSSM, &pPages->IOUnitPage4, sizeof(MptConfigurationPageIOUnit4));
+ SSMR3PutMem (pSSM, &pPages->IOCPage0, sizeof(MptConfigurationPageIOC0));
+ SSMR3PutMem (pSSM, &pPages->IOCPage1, sizeof(MptConfigurationPageIOC1));
+ SSMR3PutMem (pSSM, &pPages->IOCPage2, sizeof(MptConfigurationPageIOC2));
+ SSMR3PutMem (pSSM, &pPages->IOCPage3, sizeof(MptConfigurationPageIOC3));
+ SSMR3PutMem (pSSM, &pPages->IOCPage4, sizeof(MptConfigurationPageIOC4));
+ SSMR3PutMem (pSSM, &pPages->IOCPage6, sizeof(MptConfigurationPageIOC6));
+ SSMR3PutMem (pSSM, &pPages->BIOSPage1, sizeof(MptConfigurationPageBIOS1));
+ SSMR3PutMem (pSSM, &pPages->BIOSPage2, sizeof(MptConfigurationPageBIOS2));
+ SSMR3PutMem (pSSM, &pPages->BIOSPage4, sizeof(MptConfigurationPageBIOS4));
+
+ /* Device dependent pages */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PMptConfigurationPagesSpi pSpiPages = &pPages->u.SpiPages;
+
+ SSMR3PutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage0, sizeof(MptConfigurationPageSCSISPIPort0));
+ SSMR3PutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage1, sizeof(MptConfigurationPageSCSISPIPort1));
+ SSMR3PutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage2, sizeof(MptConfigurationPageSCSISPIPort2));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pSpiPages->aBuses[0].aDevicePages); i++)
+ {
+ SSMR3PutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage0, sizeof(MptConfigurationPageSCSISPIDevice0));
+ SSMR3PutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage1, sizeof(MptConfigurationPageSCSISPIDevice1));
+ SSMR3PutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage2, sizeof(MptConfigurationPageSCSISPIDevice2));
+ SSMR3PutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage3, sizeof(MptConfigurationPageSCSISPIDevice3));
+ }
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PMptConfigurationPagesSas pSasPages = &pPages->u.SasPages;
+
+ SSMR3PutU32(pSSM, pSasPages->cbManufacturingPage7);
+ SSMR3PutU32(pSSM, pSasPages->cbSASIOUnitPage0);
+ SSMR3PutU32(pSSM, pSasPages->cbSASIOUnitPage1);
+
+ SSMR3PutMem(pSSM, pSasPages->pManufacturingPage7, pSasPages->cbManufacturingPage7);
+ SSMR3PutMem(pSSM, pSasPages->pSASIOUnitPage0, pSasPages->cbSASIOUnitPage0);
+ SSMR3PutMem(pSSM, pSasPages->pSASIOUnitPage1, pSasPages->cbSASIOUnitPage1);
+
+ SSMR3PutMem(pSSM, &pSasPages->SASIOUnitPage2, sizeof(MptConfigurationPageSASIOUnit2));
+ SSMR3PutMem(pSSM, &pSasPages->SASIOUnitPage3, sizeof(MptConfigurationPageSASIOUnit3));
+
+ SSMR3PutU32(pSSM, pSasPages->cPHYs);
+ for (unsigned i = 0; i < pSasPages->cPHYs; i++)
+ {
+ SSMR3PutMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage0, sizeof(MptConfigurationPageSASPHY0));
+ SSMR3PutMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage1, sizeof(MptConfigurationPageSASPHY1));
+ }
+
+ /* The number of devices first. */
+ SSMR3PutU32(pSSM, pSasPages->cDevices);
+
+ PMptSASDevice pCurr = pSasPages->pSASDeviceHead;
+
+ while (pCurr)
+ {
+ SSMR3PutMem(pSSM, &pCurr->SASDevicePage0, sizeof(MptConfigurationPageSASDevice0));
+ SSMR3PutMem(pSSM, &pCurr->SASDevicePage1, sizeof(MptConfigurationPageSASDevice1));
+ SSMR3PutMem(pSSM, &pCurr->SASDevicePage2, sizeof(MptConfigurationPageSASDevice2));
+
+ pCurr = pCurr->pNext;
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+
+ vboxscsiR3SaveExec(&pThis->VBoxSCSI, pSSM);
+ return SSMR3PutU32(pSSM, UINT32_MAX);
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE}
+ */
+static DECLCALLBACK(int) lsilogicR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ lsilogicR3Kick(pThis);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ int rc;
+
+ if ( uVersion != LSILOGIC_SAVED_STATE_VERSION
+ && uVersion != LSILOGIC_SAVED_STATE_VERSION_PRE_DIAG_MEM
+ && uVersion != LSILOGIC_SAVED_STATE_VERSION_BOOL_DOORBELL
+ && uVersion != LSILOGIC_SAVED_STATE_VERSION_PRE_SAS
+ && uVersion != LSILOGIC_SAVED_STATE_VERSION_VBOX_30)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* device config */
+ if (uVersion > LSILOGIC_SAVED_STATE_VERSION_PRE_SAS)
+ {
+ LSILOGICCTRLTYPE enmCtrlType;
+ uint32_t cDeviceStates, cPorts;
+
+ rc = SSMR3GetU32(pSSM, (uint32_t *)&enmCtrlType);
+ AssertRCReturn(rc, rc);
+ rc = SSMR3GetU32(pSSM, &cDeviceStates);
+ AssertRCReturn(rc, rc);
+ rc = SSMR3GetU32(pSSM, &cPorts);
+ AssertRCReturn(rc, rc);
+
+ if (enmCtrlType != pThis->enmCtrlType)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Target config mismatch (Controller type): config=%d state=%d"),
+ pThis->enmCtrlType, enmCtrlType);
+ if (cDeviceStates != pThis->cDeviceStates)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Target config mismatch (Device states): config=%u state=%u"),
+ pThis->cDeviceStates, cDeviceStates);
+ if (cPorts != pThis->cPorts)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Target config mismatch (Ports): config=%u state=%u"),
+ pThis->cPorts, cPorts);
+ }
+ if (uVersion > LSILOGIC_SAVED_STATE_VERSION_VBOX_30)
+ {
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ bool fPresent;
+ rc = SSMR3GetBool(pSSM, &fPresent);
+ AssertRCReturn(rc, rc);
+ if (fPresent != (pThis->paDeviceStates[i].pDrvBase != NULL))
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Target %u config mismatch: config=%RTbool state=%RTbool"),
+ i, pThis->paDeviceStates[i].pDrvBase != NULL, fPresent);
+ }
+ }
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ /* Every device first. */
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ SSMR3GetU32(pSSM, (uint32_t *)&pDevice->cOutstandingRequests);
+ }
+ /* Now the main device state. */
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->enmState);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->enmWhoInit);
+ if (uVersion <= LSILOGIC_SAVED_STATE_VERSION_BOOL_DOORBELL)
+ {
+ bool fDoorbellInProgress = false;
+
+ /*
+ * The doorbell status flag distinguishes only between
+ * doorbell not in use or a Function handshake is currently in progress.
+ */
+ SSMR3GetBool (pSSM, &fDoorbellInProgress);
+ if (fDoorbellInProgress)
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_FN_HANDSHAKE;
+ else
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ }
+ else
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->enmDoorbellState);
+ SSMR3GetBool (pSSM, &pThis->fDiagnosticEnabled);
+ SSMR3GetBool (pSSM, &pThis->fNotificationSent);
+ SSMR3GetBool (pSSM, &pThis->fEventNotificationEnabled);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uInterruptMask);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uInterruptStatus);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aMessage); i++)
+ SSMR3GetU32 (pSSM, &pThis->aMessage[i]);
+ SSMR3GetU32 (pSSM, &pThis->iMessage);
+ SSMR3GetU32 (pSSM, &pThis->cMessage);
+ SSMR3GetMem (pSSM, &pThis->ReplyBuffer, sizeof(pThis->ReplyBuffer));
+ SSMR3GetU32 (pSSM, &pThis->uNextReplyEntryRead);
+ SSMR3GetU32 (pSSM, &pThis->cReplySize);
+ SSMR3GetU16 (pSSM, &pThis->u16IOCFaultCode);
+ SSMR3GetU32 (pSSM, &pThis->u32HostMFAHighAddr);
+ SSMR3GetU32 (pSSM, &pThis->u32SenseBufferHighAddr);
+ SSMR3GetU8 (pSSM, &pThis->cMaxDevices);
+ SSMR3GetU8 (pSSM, &pThis->cMaxBuses);
+ SSMR3GetU16 (pSSM, &pThis->cbReplyFrame);
+ SSMR3GetU32 (pSSM, &pThis->iDiagnosticAccess);
+
+ uint32_t cReplyQueueEntries, cRequestQueueEntries;
+ SSMR3GetU32 (pSSM, &cReplyQueueEntries);
+ SSMR3GetU32 (pSSM, &cRequestQueueEntries);
+
+ if ( cReplyQueueEntries != pThis->cReplyQueueEntries
+ || cRequestQueueEntries != pThis->cRequestQueueEntries)
+ {
+ LogFlow(("Reallocating queues cReplyQueueEntries=%u cRequestQueuEntries=%u\n",
+ cReplyQueueEntries, cRequestQueueEntries));
+ lsilogicR3QueuesFree(pThis);
+ pThis->cReplyQueueEntries = cReplyQueueEntries;
+ pThis->cRequestQueueEntries = cRequestQueueEntries;
+ rc = lsilogicR3QueuesAlloc(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uReplyFreeQueueNextEntryFreeWrite);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uReplyFreeQueueNextAddressRead);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uReplyPostQueueNextEntryFreeWrite);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uReplyPostQueueNextAddressRead);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uRequestQueueNextEntryFreeWrite);
+ SSMR3GetU32 (pSSM, (uint32_t *)&pThis->uRequestQueueNextAddressRead);
+
+ PMptConfigurationPagesSupported pPages = pThis->pConfigurationPages;
+
+ if (uVersion <= LSILOGIC_SAVED_STATE_VERSION_PRE_SAS)
+ {
+ PMptConfigurationPagesSpi pSpiPages = &pPages->u.SpiPages;
+ MptConfigurationPagesSupported_SSM_V2 ConfigPagesV2;
+
+ if (pThis->enmCtrlType != LSILOGICCTRLTYPE_SCSI_SPI)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: Expected SPI SCSI controller"));
+
+ SSMR3GetMem(pSSM, &ConfigPagesV2,
+ sizeof(MptConfigurationPagesSupported_SSM_V2));
+
+ pPages->ManufacturingPage0 = ConfigPagesV2.ManufacturingPage0;
+ pPages->ManufacturingPage1 = ConfigPagesV2.ManufacturingPage1;
+ pPages->ManufacturingPage2 = ConfigPagesV2.ManufacturingPage2;
+ pPages->ManufacturingPage3 = ConfigPagesV2.ManufacturingPage3;
+ pPages->ManufacturingPage4 = ConfigPagesV2.ManufacturingPage4;
+ pPages->IOUnitPage0 = ConfigPagesV2.IOUnitPage0;
+ pPages->IOUnitPage1 = ConfigPagesV2.IOUnitPage1;
+ pPages->IOUnitPage2 = ConfigPagesV2.IOUnitPage2;
+ pPages->IOUnitPage3 = ConfigPagesV2.IOUnitPage3;
+ pPages->IOCPage0 = ConfigPagesV2.IOCPage0;
+ pPages->IOCPage1 = ConfigPagesV2.IOCPage1;
+ pPages->IOCPage2 = ConfigPagesV2.IOCPage2;
+ pPages->IOCPage3 = ConfigPagesV2.IOCPage3;
+ pPages->IOCPage4 = ConfigPagesV2.IOCPage4;
+ pPages->IOCPage6 = ConfigPagesV2.IOCPage6;
+
+ pSpiPages->aPortPages[0].SCSISPIPortPage0 = ConfigPagesV2.aPortPages[0].SCSISPIPortPage0;
+ pSpiPages->aPortPages[0].SCSISPIPortPage1 = ConfigPagesV2.aPortPages[0].SCSISPIPortPage1;
+ pSpiPages->aPortPages[0].SCSISPIPortPage2 = ConfigPagesV2.aPortPages[0].SCSISPIPortPage2;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pPages->u.SpiPages.aBuses[0].aDevicePages); i++)
+ {
+ pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage0 = ConfigPagesV2.aBuses[0].aDevicePages[i].SCSISPIDevicePage0;
+ pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage1 = ConfigPagesV2.aBuses[0].aDevicePages[i].SCSISPIDevicePage1;
+ pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage2 = ConfigPagesV2.aBuses[0].aDevicePages[i].SCSISPIDevicePage2;
+ pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage3 = ConfigPagesV2.aBuses[0].aDevicePages[i].SCSISPIDevicePage3;
+ }
+ }
+ else
+ {
+ /* Queue content */
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->pReplyFreeQueueBaseR3[i]);
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->pReplyPostQueueBaseR3[i]);
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->pRequestQueueBaseR3[i]);
+
+ SSMR3GetU16(pSSM, &pThis->u16NextHandle);
+
+ if (uVersion > LSILOGIC_SAVED_STATE_VERSION_PRE_DIAG_MEM)
+ {
+
+ /* Save diagnostic memory register and data regions. */
+ SSMR3GetU32(pSSM, &pThis->u32DiagMemAddr);
+ uint32_t cMemRegions = 0;
+ rc = SSMR3GetU32(pSSM, &cMemRegions);
+ AssertLogRelReturn(rc, rc);
+
+ while (cMemRegions)
+ {
+ uint32_t u32AddrStart = 0;
+ SSMR3GetU32(pSSM, &u32AddrStart);
+ uint32_t u32AddrEnd = 0;
+ rc = SSMR3GetU32(pSSM, &u32AddrEnd);
+ AssertLogRelReturn(rc, rc);
+
+ uint32_t cRegion = u32AddrEnd - u32AddrStart + 1;
+ PLSILOGICMEMREGN pRegion = (PLSILOGICMEMREGN)RTMemAllocZ(RT_UOFFSETOF_DYN(LSILOGICMEMREGN, au32Data[cRegion]));
+ if (pRegion)
+ {
+ pRegion->u32AddrStart = u32AddrStart;
+ pRegion->u32AddrEnd = u32AddrEnd;
+ SSMR3GetMem(pSSM, &pRegion->au32Data[0], cRegion * sizeof(uint32_t));
+ lsilogicR3MemRegionInsert(pThis, pRegion);
+ pThis->cbMemRegns += cRegion * sizeof(uint32_t);
+ }
+ else
+ {
+ /* Leave a log message but continue. */
+ LogRel(("LsiLogic: Out of memory while restoring the state, might not work as expected\n"));
+ SSMR3Skip(pSSM, cRegion * sizeof(uint32_t));
+ }
+ cMemRegions--;
+ }
+ }
+
+ /* Configuration pages */
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage0, sizeof(MptConfigurationPageManufacturing0));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage1, sizeof(MptConfigurationPageManufacturing1));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage2, sizeof(MptConfigurationPageManufacturing2));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage3, sizeof(MptConfigurationPageManufacturing3));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage4, sizeof(MptConfigurationPageManufacturing4));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage5, sizeof(MptConfigurationPageManufacturing5));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage6, sizeof(MptConfigurationPageManufacturing6));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage8, sizeof(MptConfigurationPageManufacturing8));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage9, sizeof(MptConfigurationPageManufacturing9));
+ SSMR3GetMem(pSSM, &pPages->ManufacturingPage10, sizeof(MptConfigurationPageManufacturing10));
+ SSMR3GetMem(pSSM, &pPages->IOUnitPage0, sizeof(MptConfigurationPageIOUnit0));
+ SSMR3GetMem(pSSM, &pPages->IOUnitPage1, sizeof(MptConfigurationPageIOUnit1));
+ SSMR3GetMem(pSSM, &pPages->IOUnitPage2, sizeof(MptConfigurationPageIOUnit2));
+ SSMR3GetMem(pSSM, &pPages->IOUnitPage3, sizeof(MptConfigurationPageIOUnit3));
+ SSMR3GetMem(pSSM, &pPages->IOUnitPage4, sizeof(MptConfigurationPageIOUnit4));
+ SSMR3GetMem(pSSM, &pPages->IOCPage0, sizeof(MptConfigurationPageIOC0));
+ SSMR3GetMem(pSSM, &pPages->IOCPage1, sizeof(MptConfigurationPageIOC1));
+ SSMR3GetMem(pSSM, &pPages->IOCPage2, sizeof(MptConfigurationPageIOC2));
+ SSMR3GetMem(pSSM, &pPages->IOCPage3, sizeof(MptConfigurationPageIOC3));
+ SSMR3GetMem(pSSM, &pPages->IOCPage4, sizeof(MptConfigurationPageIOC4));
+ SSMR3GetMem(pSSM, &pPages->IOCPage6, sizeof(MptConfigurationPageIOC6));
+ SSMR3GetMem(pSSM, &pPages->BIOSPage1, sizeof(MptConfigurationPageBIOS1));
+ SSMR3GetMem(pSSM, &pPages->BIOSPage2, sizeof(MptConfigurationPageBIOS2));
+ SSMR3GetMem(pSSM, &pPages->BIOSPage4, sizeof(MptConfigurationPageBIOS4));
+
+ /* Device dependent pages */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PMptConfigurationPagesSpi pSpiPages = &pPages->u.SpiPages;
+
+ SSMR3GetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage0, sizeof(MptConfigurationPageSCSISPIPort0));
+ SSMR3GetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage1, sizeof(MptConfigurationPageSCSISPIPort1));
+ SSMR3GetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage2, sizeof(MptConfigurationPageSCSISPIPort2));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pSpiPages->aBuses[0].aDevicePages); i++)
+ {
+ SSMR3GetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage0, sizeof(MptConfigurationPageSCSISPIDevice0));
+ SSMR3GetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage1, sizeof(MptConfigurationPageSCSISPIDevice1));
+ SSMR3GetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage2, sizeof(MptConfigurationPageSCSISPIDevice2));
+ SSMR3GetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage3, sizeof(MptConfigurationPageSCSISPIDevice3));
+ }
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ uint32_t cbPage0, cbPage1, cPHYs, cbManufacturingPage7;
+ PMptConfigurationPagesSas pSasPages = &pPages->u.SasPages;
+
+ SSMR3GetU32(pSSM, &cbManufacturingPage7);
+ SSMR3GetU32(pSSM, &cbPage0);
+ SSMR3GetU32(pSSM, &cbPage1);
+
+ if ( (cbPage0 != pSasPages->cbSASIOUnitPage0)
+ || (cbPage1 != pSasPages->cbSASIOUnitPage1)
+ || (cbManufacturingPage7 != pSasPages->cbManufacturingPage7))
+ return VERR_SSM_LOAD_CONFIG_MISMATCH;
+
+ AssertPtr(pSasPages->pManufacturingPage7);
+ AssertPtr(pSasPages->pSASIOUnitPage0);
+ AssertPtr(pSasPages->pSASIOUnitPage1);
+
+ SSMR3GetMem(pSSM, pSasPages->pManufacturingPage7, pSasPages->cbManufacturingPage7);
+ SSMR3GetMem(pSSM, pSasPages->pSASIOUnitPage0, pSasPages->cbSASIOUnitPage0);
+ SSMR3GetMem(pSSM, pSasPages->pSASIOUnitPage1, pSasPages->cbSASIOUnitPage1);
+
+ SSMR3GetMem(pSSM, &pSasPages->SASIOUnitPage2, sizeof(MptConfigurationPageSASIOUnit2));
+ SSMR3GetMem(pSSM, &pSasPages->SASIOUnitPage3, sizeof(MptConfigurationPageSASIOUnit3));
+
+ SSMR3GetU32(pSSM, &cPHYs);
+ if (cPHYs != pSasPages->cPHYs)
+ return VERR_SSM_LOAD_CONFIG_MISMATCH;
+
+ AssertPtr(pSasPages->paPHYs);
+ for (unsigned i = 0; i < pSasPages->cPHYs; i++)
+ {
+ SSMR3GetMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage0, sizeof(MptConfigurationPageSASPHY0));
+ SSMR3GetMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage1, sizeof(MptConfigurationPageSASPHY1));
+ }
+
+ /* The number of devices first. */
+ SSMR3GetU32(pSSM, &pSasPages->cDevices);
+
+ PMptSASDevice pCurr = pSasPages->pSASDeviceHead;
+
+ for (unsigned i = 0; i < pSasPages->cDevices; i++)
+ {
+ SSMR3GetMem(pSSM, &pCurr->SASDevicePage0, sizeof(MptConfigurationPageSASDevice0));
+ SSMR3GetMem(pSSM, &pCurr->SASDevicePage1, sizeof(MptConfigurationPageSASDevice1));
+ SSMR3GetMem(pSSM, &pCurr->SASDevicePage2, sizeof(MptConfigurationPageSASDevice2));
+
+ pCurr = pCurr->pNext;
+ }
+
+ Assert(!pCurr);
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+ }
+
+ rc = vboxscsiR3LoadExec(&pThis->VBoxSCSI, pSSM);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("LsiLogic: Failed to restore BIOS state: %Rrc.\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic: Failed to restore BIOS state\n"));
+ }
+
+ uint32_t u32;
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * The device level IBASE and LED interfaces.
+ */
+
+/**
+ * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed, For a SCSI device.}
+ *
+ * @remarks Called by the scsi driver, proxying the main calls.
+ */
+static DECLCALLBACK(int) lsilogicR3DeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PLSILOGICDEVICE pDevice = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, ILed);
+ if (iLUN == 0)
+ {
+ *ppLed = &pDevice->Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) lsilogicR3DeviceQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PLSILOGICDEVICE pDevice = RT_FROM_MEMBER(pInterface, LSILOGICDEVICE, IBase);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDevice->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pDevice->IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pDevice->IMediaExPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pDevice->ILed);
+ return NULL;
+}
+
+
+/*
+ * The controller level IBASE and LED interfaces.
+ */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) lsilogicR3StatusQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PLSILOGICSCSI pThis = RT_FROM_MEMBER(pInterface, LSILOGICSCSI, ILeds);
+ if (iLUN < pThis->cDeviceStates)
+ {
+ *ppLed = &pThis->paDeviceStates[iLUN].Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) lsilogicR3StatusQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PLSILOGICSCSI pThis = RT_FROM_MEMBER(pInterface, LSILOGICSCSI, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->ILeds);
+ return NULL;
+}
+
+
+/*
+ * The PDM device interface and some helpers.
+ */
+
+/**
+ * Checks if all asynchronous I/O is finished.
+ *
+ * Used by lsilogicR3Reset, lsilogicR3Suspend and lsilogicR3PowerOff.
+ *
+ * @returns true if quiesced, false if busy.
+ * @param pDevIns The device instance.
+ */
+static bool lsilogicR3AllAsyncIOIsFinished(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ for (uint32_t i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pThisDevice = &pThis->paDeviceStates[i];
+ if (pThisDevice->pDrvBase)
+ {
+ if (pThisDevice->cOutstandingRequests != 0)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by lsilogicR3Suspend and lsilogicR3PowerOff.}
+ */
+static DECLCALLBACK(bool) lsilogicR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns)
+{
+ if (!lsilogicR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for ahciR3Suspend and ahciR3PowerOff.
+ */
+static void lsilogicR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!lsilogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, lsilogicR3IsAsyncSuspendOrPowerOffDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ AssertMsg(!pThis->fNotificationSent, ("The PDM Queue should be empty at this point\n"));
+ }
+
+ for (uint32_t i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pThisDevice = &pThis->paDeviceStates[i];
+ if (pThisDevice->pDrvMediaEx)
+ pThisDevice->pDrvMediaEx->pfnNotifySuspend(pThisDevice->pDrvMediaEx);
+ }
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnSuspend}
+ */
+static DECLCALLBACK(void) lsilogicR3Suspend(PPDMDEVINS pDevIns)
+{
+ Log(("lsilogicR3Suspend\n"));
+ lsilogicR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnResume}
+ */
+static DECLCALLBACK(void) lsilogicR3Resume(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ Log(("lsilogicR3Resume\n"));
+
+ lsilogicR3Kick(pThis);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ */
+static DECLCALLBACK(void) lsilogicR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[iLUN];
+
+ if (iLUN >= pThis->cDeviceStates)
+ return;
+
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("LsiLogic: Device does not support hotplugging\n"));
+
+ Log(("%s:\n", __FUNCTION__));
+
+ /*
+ * Zero some important members.
+ */
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) lsilogicR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[iLUN];
+ int rc;
+
+ if (iLUN >= pThis->cDeviceStates)
+ return VERR_PDM_LUN_NOT_FOUND;
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("LsiLogic: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /* the usual paranoia */
+ AssertRelease(!pDevice->pDrvBase);
+ AssertRelease(!pDevice->pDrvMedia);
+ AssertRelease(!pDevice->pDrvMediaEx);
+ Assert(pDevice->iLUN == iLUN);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMedia),
+ ("LsiLogic configuration error: LUN#%d misses the basic media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pDevice->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMediaEx),
+ ("LsiLogic configuration error: LUN#%d misses the extended media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqAllocSizeSet(pDevice->pDrvMediaEx, sizeof(LSILOGICREQ));
+ AssertMsgRCReturn(rc, ("LsiLogic configuration error: LUN#%u: Failed to set I/O request size!", pDevice->iLUN),
+ rc);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pDevice->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+ }
+ return rc;
+}
+
+/**
+ * Common reset worker.
+ *
+ * @param pDevIns The device instance data.
+ */
+static void lsilogicR3ResetCommon(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ int rc;
+
+ rc = lsilogicR3HardReset(pThis);
+ AssertRC(rc);
+
+ vboxscsiInitialize(&pThis->VBoxSCSI);
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by lsilogicR3Reset.}
+ */
+static DECLCALLBACK(bool) lsilogicR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ if (!lsilogicR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ lsilogicR3ResetCommon(pDevIns);
+ return true;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) lsilogicR3Reset(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!lsilogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, lsilogicR3IsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ lsilogicR3ResetCommon(pDevIns);
+ }
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnRelocate}
+ */
+static DECLCALLBACK(void) lsilogicR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->pNotificationQueueRC = PDMQueueRCPtr(pThis->pNotificationQueueR3);
+
+ /* Relocate queues. */
+ pThis->pReplyFreeQueueBaseRC += offDelta;
+ pThis->pReplyPostQueueBaseRC += offDelta;
+ pThis->pRequestQueueBaseRC += offDelta;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnPowerOff}
+ */
+static DECLCALLBACK(void) lsilogicR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("lsilogicR3PowerOff\n"));
+ lsilogicR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) lsilogicR3Destruct(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ PDMR3CritSectDelete(&pThis->ReplyFreeQueueCritSect);
+ PDMR3CritSectDelete(&pThis->ReplyPostQueueCritSect);
+ PDMR3CritSectDelete(&pThis->RequestQueueCritSect);
+ PDMR3CritSectDelete(&pThis->ReplyFreeQueueWriteCritSect);
+
+ RTMemFree(pThis->paDeviceStates);
+ pThis->paDeviceStates = NULL;
+
+ if (pThis->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ SUPSemEventClose(pThis->pSupDrvSession, pThis->hEvtProcess);
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+
+ lsilogicR3ConfigurationPagesFree(pThis);
+ lsilogicR3MemRegionsFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) lsilogicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PLSILOGICSCSI pThis = PDMINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ int rc = VINF_SUCCESS;
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Initialize enought of the state to make the destructure not trip up.
+ */
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ pThis->fBiosReqPending = false;
+ RTListInit(&pThis->ListMemRegns);
+
+ /*
+ * Validate and read configuration.
+ */
+ rc = CFGMR3AreValuesValid(pCfg, "GCEnabled\0"
+ "R0Enabled\0"
+ "ReplyQueueDepth\0"
+ "RequestQueueDepth\0"
+ "ControllerType\0"
+ "NumPorts\0"
+ "Bootable\0");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("LsiLogic configuration error: unknown option specified"));
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &pThis->fGCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read GCEnabled as boolean"));
+ Log(("%s: fGCEnabled=%d\n", __FUNCTION__, pThis->fGCEnabled));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &pThis->fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read R0Enabled as boolean"));
+ Log(("%s: fR0Enabled=%d\n", __FUNCTION__, pThis->fR0Enabled));
+
+ rc = CFGMR3QueryU32Def(pCfg, "ReplyQueueDepth",
+ &pThis->cReplyQueueEntries,
+ LSILOGICSCSI_REPLY_QUEUE_DEPTH_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read ReplyQueue as integer"));
+ Log(("%s: ReplyQueueDepth=%u\n", __FUNCTION__, pThis->cReplyQueueEntries));
+
+ rc = CFGMR3QueryU32Def(pCfg, "RequestQueueDepth",
+ &pThis->cRequestQueueEntries,
+ LSILOGICSCSI_REQUEST_QUEUE_DEPTH_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read RequestQueue as integer"));
+ Log(("%s: RequestQueueDepth=%u\n", __FUNCTION__, pThis->cRequestQueueEntries));
+
+ char *pszCtrlType;
+ rc = CFGMR3QueryStringAllocDef(pCfg, "ControllerType",
+ &pszCtrlType, LSILOGICSCSI_PCI_SPI_CTRLNAME);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read ControllerType as string"));
+ Log(("%s: ControllerType=%s\n", __FUNCTION__, pszCtrlType));
+
+ rc = lsilogicR3GetCtrlTypeFromString(pThis, pszCtrlType);
+ MMR3HeapFree(pszCtrlType);
+
+ char szDevTag[20];
+ RTStrPrintf(szDevTag, sizeof(szDevTag), "LSILOGIC%s-%u",
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI ? "SPI" : "SAS",
+ iInstance);
+
+
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to determine controller type from string"));
+
+ rc = CFGMR3QueryU8(pCfg, "NumPorts",
+ &pThis->cPorts);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ pThis->cPorts = LSILOGICSCSI_PCI_SPI_PORTS_MAX;
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ pThis->cPorts = LSILOGICSCSI_PCI_SAS_PORTS_DEFAULT;
+ else
+ AssertMsgFailed(("Invalid controller type: %d\n", pThis->enmCtrlType));
+ }
+ else if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read NumPorts as integer"));
+
+ bool fBootable;
+ rc = CFGMR3QueryBoolDef(pCfg, "Bootable", &fBootable, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("LsiLogic configuration error: failed to read Bootable as boolean"));
+ Log(("%s: Bootable=%RTbool\n", __FUNCTION__, fBootable));
+
+ /* Init static parts. */
+ PCIDevSetVendorId(&pThis->PciDev, LSILOGICSCSI_PCI_VENDOR_ID); /* LsiLogic */
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PCIDevSetDeviceId (&pThis->PciDev, LSILOGICSCSI_PCI_SPI_DEVICE_ID); /* LSI53C1030 */
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, LSILOGICSCSI_PCI_SPI_SUBSYSTEM_VENDOR_ID);
+ PCIDevSetSubSystemId (&pThis->PciDev, LSILOGICSCSI_PCI_SPI_SUBSYSTEM_ID);
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PCIDevSetDeviceId (&pThis->PciDev, LSILOGICSCSI_PCI_SAS_DEVICE_ID); /* SAS1068 */
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, LSILOGICSCSI_PCI_SAS_SUBSYSTEM_VENDOR_ID);
+ PCIDevSetSubSystemId (&pThis->PciDev, LSILOGICSCSI_PCI_SAS_SUBSYSTEM_ID);
+ }
+ else
+ AssertMsgFailed(("Invalid controller type: %d\n", pThis->enmCtrlType));
+
+ PCIDevSetClassProg (&pThis->PciDev, 0x00); /* SCSI */
+ PCIDevSetClassSub (&pThis->PciDev, 0x00); /* SCSI */
+ PCIDevSetClassBase (&pThis->PciDev, 0x01); /* Mass storage */
+ PCIDevSetInterruptPin(&pThis->PciDev, 0x01); /* Interrupt pin A */
+
+# ifdef VBOX_WITH_MSI_DEVICES
+ PCIDevSetStatus(&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x80);
+# endif
+
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->pSupDrvSession = PDMDevHlpGetSupDrvSession(pDevIns);
+ pThis->IBase.pfnQueryInterface = lsilogicR3StatusQueryInterface;
+ pThis->ILeds.pfnQueryStatusLed = lsilogicR3StatusQueryStatusLed;
+
+ /*
+ * Create critical sections protecting the reply post and free queues.
+ * Note! We do our own syncronization, so NOP the default crit sect for the device.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->ReplyFreeQueueCritSect, RT_SRC_POS, "%sRFQ", szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: cannot create critical section for reply free queue"));
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->ReplyPostQueueCritSect, RT_SRC_POS, "%sRPQ", szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: cannot create critical section for reply post queue"));
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->RequestQueueCritSect, RT_SRC_POS, "%sRQ", szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: cannot create critical section for request queue"));
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->ReplyFreeQueueWriteCritSect, RT_SRC_POS, "%sRFQW", szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: cannot create critical section for reply free queue write access"));
+
+ /*
+ * Register the PCI device, it's I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+# ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ /* use this code for MSI-X support */
+# if 0
+ MsiReg.cMsixVectors = 1;
+ MsiReg.iMsixCapOffset = 0x80;
+ MsiReg.iMsixNextOffset = 0x00;
+ MsiReg.iMsixBar = 3;
+# else
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x80;
+ MsiReg.iMsiNextOffset = 0x00;
+# endif
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE (rc))
+ {
+ /* That's OK, we can work without MSI */
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x0);
+ }
+# endif
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, LSILOGIC_PCI_SPACE_IO_SIZE, PCI_ADDRESS_SPACE_IO, lsilogicR3Map);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM, lsilogicR3Map);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 2, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM, lsilogicR3Map);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Initialize task queue. (Need two items to handle SMP guest concurrency.) */
+ char szTaggedText[64];
+ RTStrPrintf(szTaggedText, sizeof(szTaggedText), "%s-Task", szDevTag);
+ rc = PDMDevHlpQueueCreate(pDevIns, sizeof(PDMQUEUEITEMCORE), 2, 0,
+ lsilogicR3NotifyQueueConsumer, true,
+ szTaggedText,
+ &pThis->pNotificationQueueR3);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->pNotificationQueueR0 = PDMQueueR0Ptr(pThis->pNotificationQueueR3);
+ pThis->pNotificationQueueRC = PDMQueueRCPtr(pThis->pNotificationQueueR3);
+
+ /*
+ * We need one entry free in the queue.
+ */
+ pThis->cReplyQueueEntries++;
+ pThis->cRequestQueueEntries++;
+
+ /*
+ * Allocate memory for the queues.
+ */
+ rc = lsilogicR3QueuesAlloc(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ pThis->cDeviceStates = pThis->cPorts * LSILOGICSCSI_PCI_SPI_DEVICES_PER_BUS_MAX;
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ pThis->cDeviceStates = pThis->cPorts * LSILOGICSCSI_PCI_SAS_DEVICES_PER_PORT_MAX;
+ else
+ AssertMsgFailed(("Invalid controller type: %d\n", pThis->enmCtrlType));
+
+ /*
+ * Create event semaphore and worker thread.
+ */
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThis->pThreadWrk, pThis, lsilogicR3Worker,
+ lsilogicR3WorkerWakeUp, 0, RTTHREADTYPE_IO, szDevTag);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("LsiLogic: Failed to create worker thread %s"), szDevTag);
+
+ rc = SUPSemEventCreate(pThis->pSupDrvSession, &pThis->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("LsiLogic: Failed to create SUP event semaphore"));
+
+ /*
+ * Allocate device states.
+ */
+ pThis->paDeviceStates = (PLSILOGICDEVICE)RTMemAllocZ(sizeof(LSILOGICDEVICE) * pThis->cDeviceStates);
+ if (!pThis->paDeviceStates)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to allocate memory for device states"));
+
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThis->paDeviceStates[i];
+
+ /* Initialize static parts of the device. */
+ pDevice->iLUN = i;
+ pDevice->pLsiLogicR3 = pThis;
+ pDevice->Led.u32Magic = PDMLED_MAGIC;
+ pDevice->IBase.pfnQueryInterface = lsilogicR3DeviceQueryInterface;
+ pDevice->IMediaPort.pfnQueryDeviceLocation = lsilogicR3QueryDeviceLocation;
+ pDevice->IMediaExPort.pfnIoReqCompleteNotify = lsilogicR3IoReqCompleteNotify;
+ pDevice->IMediaExPort.pfnIoReqCopyFromBuf = lsilogicR3IoReqCopyFromBuf;
+ pDevice->IMediaExPort.pfnIoReqCopyToBuf = lsilogicR3IoReqCopyToBuf;
+ pDevice->IMediaExPort.pfnIoReqQueryBuf = NULL;
+ pDevice->IMediaExPort.pfnIoReqQueryDiscardRanges = NULL;
+ pDevice->IMediaExPort.pfnIoReqStateChanged = lsilogicR3IoReqStateChanged;
+ pDevice->IMediaExPort.pfnMediumEjected = lsilogicR3MediumEjected;
+ pDevice->ILed.pfnQueryStatusLed = lsilogicR3DeviceQueryStatusLed;
+
+ char *pszName;
+ if (RTStrAPrintf(&pszName, "Device%u", i) <= 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ /* Attach SCSI driver. */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, pszName);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMedia),
+ ("LsiLogic configuration error: LUN#%d misses the basic media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pDevice->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pDevice->pDrvMediaEx),
+ ("LsiLogic configuration error: LUN#%d misses the extended media interface!\n", pDevice->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqAllocSizeSet(pDevice->pDrvMediaEx, sizeof(LSILOGICREQ));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("LsiLogic configuration error: LUN#%u: Failed to set I/O request size!"),
+ pDevice->iLUN);
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pDevice->pDrvBase = NULL;
+ rc = VINF_SUCCESS;
+ Log(("LsiLogic: no driver attached to device %s\n", pszName));
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("LsiLogic: Failed to attach %s\n", pszName));
+ return rc;
+ }
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThis->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot attach to status driver"));
+ }
+
+ /* Initialize the SCSI emulation for the BIOS. */
+ rc = vboxscsiInitialize(&pThis->VBoxSCSI);
+ AssertRC(rc);
+
+ /*
+ * Register I/O port space in ISA region for BIOS access
+ * if the controller is marked as bootable.
+ */
+ if (fBootable)
+ {
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ rc = PDMDevHlpIOPortRegister(pDevIns, LSILOGIC_BIOS_IO_PORT, 4, NULL,
+ lsilogicR3IsaIOPortWrite, lsilogicR3IsaIOPortRead,
+ lsilogicR3IsaIOPortWriteStr, lsilogicR3IsaIOPortReadStr,
+ "LsiLogic BIOS");
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ rc = PDMDevHlpIOPortRegister(pDevIns, LSILOGIC_SAS_BIOS_IO_PORT, 4, NULL,
+ lsilogicR3IsaIOPortWrite, lsilogicR3IsaIOPortRead,
+ lsilogicR3IsaIOPortWriteStr, lsilogicR3IsaIOPortReadStr,
+ "LsiLogic SAS BIOS");
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot register legacy I/O handlers"));
+ }
+
+ /* Register save state handlers. */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, LSILOGIC_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, lsilogicR3LiveExec, NULL,
+ NULL, lsilogicR3SaveExec, NULL,
+ NULL, lsilogicR3LoadExec, lsilogicR3LoadDone);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot register save state handlers"));
+
+ pThis->enmWhoInit = LSILOGICWHOINIT_SYSTEM_BIOS;
+
+ /*
+ * Register the info item.
+ */
+ char szTmp[128];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%s%u", pDevIns->pReg->szName, pDevIns->iInstance);
+ PDMDevHlpDBGFInfoRegister(pDevIns, szTmp,
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI
+ ? "LsiLogic SPI info."
+ : "LsiLogic SAS info.", lsilogicR3Info);
+
+ /* Perform hard reset. */
+ rc = lsilogicR3HardReset(pThis);
+ AssertRC(rc);
+
+ return rc;
+}
+
+/**
+ * The device registration structure - SPI SCSI controller.
+ */
+const PDMDEVREG g_DeviceLsiLogicSCSI =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "lsilogicscsi",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "LSI Logic 53c1030 SCSI controller.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(LSILOGICSCSI),
+ /* pfnConstruct */
+ lsilogicR3Construct,
+ /* pfnDestruct */
+ lsilogicR3Destruct,
+ /* pfnRelocate */
+ lsilogicR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ lsilogicR3Reset,
+ /* pfnSuspend */
+ lsilogicR3Suspend,
+ /* pfnResume */
+ lsilogicR3Resume,
+ /* pfnAttach */
+ lsilogicR3Attach,
+ /* pfnDetach */
+ lsilogicR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ lsilogicR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+/**
+ * The device registration structure - SAS controller.
+ */
+const PDMDEVREG g_DeviceLsiLogicSAS =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "lsilogicsas",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "LSI Logic SAS1068 controller.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0 |
+ PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION |
+ PDM_DEVREG_FLAGS_FIRST_RESET_NOTIFICATION,
+ /* fClass */
+ PDM_DEVREG_CLASS_STORAGE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(LSILOGICSCSI),
+ /* pfnConstruct */
+ lsilogicR3Construct,
+ /* pfnDestruct */
+ lsilogicR3Destruct,
+ /* pfnRelocate */
+ lsilogicR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ lsilogicR3Reset,
+ /* pfnSuspend */
+ lsilogicR3Suspend,
+ /* pfnResume */
+ lsilogicR3Resume,
+ /* pfnAttach */
+ lsilogicR3Attach,
+ /* pfnDetach */
+ lsilogicR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ lsilogicR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.h b/src/VBox/Devices/Storage/DevLsiLogicSCSI.h
new file mode 100644
index 00000000..2d4d7b25
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.h
@@ -0,0 +1,3648 @@
+/* $Id: DevLsiLogicSCSI.h $ */
+/** @file
+ * VBox storage devices: LsiLogic LSI53c1030 SCSI controller - Defines and structures.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_DevLsiLogicSCSI_h
+#define VBOX_INCLUDED_SRC_Storage_DevLsiLogicSCSI_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/stdint.h>
+
+/*
+ * Custom fixed I/O ports for BIOS controller access. Note that these should
+ * not be in the ISA range (below 400h) to avoid conflicts with ISA device
+ * probing. Addresses in the 300h-340h range should be especially avoided.
+ */
+#define LSILOGIC_BIOS_IO_PORT 0x434
+#define LSILOGIC_SAS_BIOS_IO_PORT 0x438
+
+#define LSILOGICSCSI_REQUEST_QUEUE_DEPTH_DEFAULT 256
+#define LSILOGICSCSI_REPLY_QUEUE_DEPTH_DEFAULT 256
+
+#define LSILOGICSCSI_MAXIMUM_CHAIN_DEPTH 3
+
+#define LSILOGIC_NR_OF_ALLOWED_BIGGER_LISTS 100
+
+/** Equal for all devices */
+#define LSILOGICSCSI_PCI_VENDOR_ID (0x1000)
+
+/** SPI SCSI controller (LSI53C1030) */
+#define LSILOGICSCSI_PCI_SPI_CTRLNAME "LSI53C1030"
+#define LSILOGICSCSI_PCI_SPI_DEVICE_ID (0x0030)
+#define LSILOGICSCSI_PCI_SPI_REVISION_ID (0x00)
+#define LSILOGICSCSI_PCI_SPI_CLASS_CODE (0x01)
+#define LSILOGICSCSI_PCI_SPI_SUBSYSTEM_VENDOR_ID (0x1000)
+#define LSILOGICSCSI_PCI_SPI_SUBSYSTEM_ID (0x8000)
+#define LSILOGICSCSI_PCI_SPI_PORTS_MAX 1
+#define LSILOGICSCSI_PCI_SPI_BUSES_MAX 1
+#define LSILOGICSCSI_PCI_SPI_DEVICES_PER_BUS_MAX 16
+#define LSILOGICSCSI_PCI_SPI_DEVICES_MAX (LSILOGICSCSI_PCI_SPI_BUSES_MAX*LSILOGICSCSI_PCI_SPI_DEVICES_PER_BUS_MAX)
+
+/** SAS SCSI controller (SAS1068 PCI-X Fusion-MPT SAS) */
+#define LSILOGICSCSI_PCI_SAS_CTRLNAME "SAS1068"
+#define LSILOGICSCSI_PCI_SAS_DEVICE_ID (0x0054)
+#define LSILOGICSCSI_PCI_SAS_REVISION_ID (0x00)
+#define LSILOGICSCSI_PCI_SAS_CLASS_CODE (0x00)
+#define LSILOGICSCSI_PCI_SAS_SUBSYSTEM_VENDOR_ID (0x1000)
+#define LSILOGICSCSI_PCI_SAS_SUBSYSTEM_ID (0x8000)
+#define LSILOGICSCSI_PCI_SAS_PORTS_MAX 256
+#define LSILOGICSCSI_PCI_SAS_PORTS_DEFAULT 8
+#define LSILOGICSCSI_PCI_SAS_DEVICES_PER_PORT_MAX 1
+#define LSILOGICSCSI_PCI_SAS_DEVICES_MAX (LSILOGICSCSI_PCI_SAS_PORTS_MAX * LSILOGICSCSI_PCI_SAS_DEVICES_PER_PORT_MAX)
+
+/**
+ * A SAS address.
+ */
+#pragma pack(1)
+typedef union SASADDRESS
+{
+ /** 64bit view. */
+ uint64_t u64Address;
+ /** 32bit view. */
+ uint32_t u32Address[2];
+ /** 16bit view. */
+ uint16_t u16Address[4];
+ /** Byte view. */
+ uint8_t u8Address[8];
+} SASADDRESS, *PSASADDRESS;
+#pragma pack()
+AssertCompileSize(SASADDRESS, 8);
+
+/**
+ * Possible device types we support.
+ */
+typedef enum LSILOGICCTRLTYPE
+{
+ /** SPI SCSI controller (PCI dev id 0x0030) */
+ LSILOGICCTRLTYPE_SCSI_SPI = 0,
+ /** SAS SCSI controller (PCI dev id 0x0054) */
+ LSILOGICCTRLTYPE_SCSI_SAS = 1,
+ /** 32bit hack */
+ LSILOGICCTRLTYPE_32BIT_HACK = 0x7fffffff
+} LSILOGICCTRLTYPE, *PLSILOGICCTRLTYPE;
+
+/**
+ * A simple SG element for a 64bit address.
+ */
+#pragma pack(1)
+typedef struct MptSGEntrySimple64
+{
+ /** Length of the buffer this entry describes. */
+ unsigned u24Length: 24;
+ /** Flag whether this element is the end of the list. */
+ unsigned fEndOfList: 1;
+ /** Flag whether the address is 32bit or 64bits wide. */
+ unsigned f64BitAddress: 1;
+ /** Flag whether this buffer contains data to be transferred or is the destination. */
+ unsigned fBufferContainsData: 1;
+ /** Flag whether this is a local address or a system address. */
+ unsigned fLocalAddress: 1;
+ /** Element type. */
+ unsigned u2ElementType: 2;
+ /** Flag whether this is the last element of the buffer. */
+ unsigned fEndOfBuffer: 1;
+ /** Flag whether this is the last element of the current segment. */
+ unsigned fLastElement: 1;
+ /** Lower 32bits of the address of the data buffer. */
+ unsigned u32DataBufferAddressLow: 32;
+ /** Upper 32bits of the address of the data buffer. */
+ unsigned u32DataBufferAddressHigh: 32;
+} MptSGEntrySimple64, *PMptSGEntrySimple64;
+#pragma pack()
+AssertCompileSize(MptSGEntrySimple64, 12);
+
+/**
+ * A simple SG element for a 32bit address.
+ */
+#pragma pack(1)
+typedef struct MptSGEntrySimple32
+{
+ /** Length of the buffer this entry describes. */
+ unsigned u24Length: 24;
+ /** Flag whether this element is the end of the list. */
+ unsigned fEndOfList: 1;
+ /** Flag whether the address is 32bit or 64bits wide. */
+ unsigned f64BitAddress: 1;
+ /** Flag whether this buffer contains data to be transferred or is the destination. */
+ unsigned fBufferContainsData: 1;
+ /** Flag whether this is a local address or a system address. */
+ unsigned fLocalAddress: 1;
+ /** Element type. */
+ unsigned u2ElementType: 2;
+ /** Flag whether this is the last element of the buffer. */
+ unsigned fEndOfBuffer: 1;
+ /** Flag whether this is the last element of the current segment. */
+ unsigned fLastElement: 1;
+ /** Lower 32bits of the address of the data buffer. */
+ unsigned u32DataBufferAddressLow: 32;
+} MptSGEntrySimple32, *PMptSGEntrySimple32;
+#pragma pack()
+AssertCompileSize(MptSGEntrySimple32, 8);
+
+/**
+ * A chain SG element.
+ */
+#pragma pack(1)
+typedef struct MptSGEntryChain
+{
+ /** Size of the segment. */
+ unsigned u16Length: 16;
+ /** Offset in 32bit words of the next chain element in the segment
+ * identified by this element. */
+ unsigned u8NextChainOffset: 8;
+ /** Reserved. */
+ unsigned fReserved0: 1;
+ /** Flag whether the address is 32bit or 64bits wide. */
+ unsigned f64BitAddress: 1;
+ /** Reserved. */
+ unsigned fReserved1: 1;
+ /** Flag whether this is a local address or a system address. */
+ unsigned fLocalAddress: 1;
+ /** Element type. */
+ unsigned u2ElementType: 2;
+ /** Flag whether this is the last element of the buffer. */
+ unsigned u2Reserved2: 2;
+ /** Lower 32bits of the address of the data buffer. */
+ unsigned u32SegmentAddressLow: 32;
+ /** Upper 32bits of the address of the data buffer. */
+ unsigned u32SegmentAddressHigh: 32;
+} MptSGEntryChain, *PMptSGEntryChain;
+#pragma pack()
+AssertCompileSize(MptSGEntryChain, 12);
+
+typedef union MptSGEntryUnion
+{
+ MptSGEntrySimple64 Simple64;
+ MptSGEntrySimple32 Simple32;
+ MptSGEntryChain Chain;
+} MptSGEntryUnion, *PMptSGEntryUnion;
+
+/**
+ * MPT Fusion message header - Common for all message frames.
+ * This is filled in by the guest.
+ */
+#pragma pack(1)
+typedef struct MptMessageHdr
+{
+ /** Function dependent data. */
+ uint16_t u16FunctionDependent;
+ /** Chain offset. */
+ uint8_t u8ChainOffset;
+ /** The function code. */
+ uint8_t u8Function;
+ /** Function dependent data. */
+ uint8_t au8FunctionDependent[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context - Unique ID from the guest unmodified by the device. */
+ uint32_t u32MessageContext;
+} MptMessageHdr, *PMptMessageHdr;
+#pragma pack()
+AssertCompileSize(MptMessageHdr, 12);
+
+/** Defined function codes found in the message header. */
+#define MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST (0x00)
+#define MPT_MESSAGE_HDR_FUNCTION_SCSI_TASK_MGMT (0x01)
+#define MPT_MESSAGE_HDR_FUNCTION_IOC_INIT (0x02)
+#define MPT_MESSAGE_HDR_FUNCTION_IOC_FACTS (0x03)
+#define MPT_MESSAGE_HDR_FUNCTION_CONFIG (0x04)
+#define MPT_MESSAGE_HDR_FUNCTION_PORT_FACTS (0x05)
+#define MPT_MESSAGE_HDR_FUNCTION_PORT_ENABLE (0x06)
+#define MPT_MESSAGE_HDR_FUNCTION_EVENT_NOTIFICATION (0x07)
+#define MPT_MESSAGE_HDR_FUNCTION_EVENT_ACK (0x08)
+#define MPT_MESSAGE_HDR_FUNCTION_FW_DOWNLOAD (0x09)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_CMD_BUFFER_POST (0x0A)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_ASSIST (0x0B)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_STATUS_SEND (0x0C)
+#define MPT_MESSAGE_HDR_FUNCTION_TARGET_MODE_ABORT (0x0D)
+#define MPT_MESSAGE_HDR_FUNCTION_FW_UPLOAD (0x12)
+
+#ifdef DEBUG
+/**
+ * Function names
+ */
+static const char * const g_apszMPTFunctionNames[] =
+{
+ "SCSI I/O Request",
+ "SCSI Task Management",
+ "IOC Init",
+ "IOC Facts",
+ "Config",
+ "Port Facts",
+ "Port Enable",
+ "Event Notification",
+ "Event Ack",
+ "Firmware Download"
+};
+#endif
+
+/**
+ * Default reply message.
+ * Send from the device to the guest upon completion of a request.
+ */
+ #pragma pack(1)
+typedef struct MptDefaultReplyMessage
+{
+ /** Function dependent data. */
+ uint16_t u16FunctionDependent;
+ /** Length of the message in 32bit DWords. */
+ uint8_t u8MessageLength;
+ /** Function which completed. */
+ uint8_t u8Function;
+ /** Function dependent. */
+ uint8_t au8FunctionDependent[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context given in the request. */
+ uint32_t u32MessageContext;
+ /** Function dependent status code. */
+ uint16_t u16FunctionDependentStatus;
+ /** Status of the IOC. */
+ uint16_t u16IOCStatus;
+ /** Additional log info. */
+ uint32_t u32IOCLogInfo;
+} MptDefaultReplyMessage, *PMptDefaultReplyMessage;
+#pragma pack()
+AssertCompileSize(MptDefaultReplyMessage, 20);
+
+/**
+ * IO controller init request.
+ */
+#pragma pack(1)
+typedef struct MptIOCInitRequest
+{
+ /** Which system send this init request. */
+ uint8_t u8WhoInit;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Chain offset in the SG list. */
+ uint8_t u8ChainOffset;
+ /** Function to execute. */
+ uint8_t u8Function;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Maximum number of devices the driver can handle. */
+ uint8_t u8MaxDevices;
+ /** Maximum number of buses the driver can handle. */
+ uint8_t u8MaxBuses;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reply frame size. */
+ uint16_t u16ReplyFrameSize;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** Upper 32bit part of the 64bit address the message frames are in.
+ * That means all frames must be in the same 4GB segment. */
+ uint32_t u32HostMfaHighAddr;
+ /** Upper 32bit of the sense buffer. */
+ uint32_t u32SenseBufferHighAddr;
+} MptIOCInitRequest, *PMptIOCInitRequest;
+#pragma pack()
+AssertCompileSize(MptIOCInitRequest, 24);
+
+/**
+ * IO controller init reply.
+ */
+#pragma pack(1)
+typedef struct MptIOCInitReply
+{
+ /** Which subsystem send this init request. */
+ uint8_t u8WhoInit;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Message length */
+ uint8_t u8MessageLength;
+ /** Function. */
+ uint8_t u8Function;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Maximum number of devices the driver can handle. */
+ uint8_t u8MaxDevices;
+ /** Maximum number of busses the driver can handle. */
+ uint8_t u8MaxBuses;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID */
+ uint32_t u32MessageContext;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+} MptIOCInitReply, *PMptIOCInitReply;
+#pragma pack()
+AssertCompileSize(MptIOCInitReply, 20);
+
+/**
+ * IO controller facts request.
+ */
+#pragma pack(1)
+typedef struct MptIOCFactsRequest
+{
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** Chain offset in SG list. */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved */
+ uint8_t u8Reserved[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptIOCFactsRequest, *PMptIOCFactsRequest;
+#pragma pack()
+AssertCompileSize(MptIOCFactsRequest, 12);
+
+/**
+ * IO controller facts reply.
+ */
+#pragma pack(1)
+typedef struct MptIOCFactsReply
+{
+ /** Message version. */
+ uint16_t u16MessageVersion;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved */
+ uint16_t u16Reserved1;
+ /** IO controller number */
+ uint8_t u8IOCNumber;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** IO controller exceptions */
+ uint16_t u16IOCExceptions;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+ /** Maximum chain depth. */
+ uint8_t u8MaxChainDepth;
+ /** The current value of the WhoInit field. */
+ uint8_t u8WhoInit;
+ /** Block size. */
+ uint8_t u8BlockSize;
+ /** Flags. */
+ uint8_t u8Flags;
+ /** Depth of the reply queue. */
+ uint16_t u16ReplyQueueDepth;
+ /** Size of a request frame. */
+ uint16_t u16RequestFrameSize;
+ /** Reserved */
+ uint16_t u16Reserved2;
+ /** Product ID. */
+ uint16_t u16ProductID;
+ /** Current value of the high 32bit MFA address. */
+ uint32_t u32CurrentHostMFAHighAddr;
+ /** Global credits - Number of entries allocated to queues */
+ uint16_t u16GlobalCredits;
+ /** Number of ports on the IO controller */
+ uint8_t u8NumberOfPorts;
+ /** Event state. */
+ uint8_t u8EventState;
+ /** Current value of the high 32bit sense buffer address. */
+ uint32_t u32CurrentSenseBufferHighAddr;
+ /** Current reply frame size. */
+ uint16_t u16CurReplyFrameSize;
+ /** Maximum number of devices. */
+ uint8_t u8MaxDevices;
+ /** Maximum number of buses. */
+ uint8_t u8MaxBuses;
+ /** Size of the firmware image. */
+ uint32_t u32FwImageSize;
+ /** Reserved. */
+ uint32_t u32Reserved;
+ /** Firmware version */
+ uint32_t u32FWVersion;
+} MptIOCFactsReply, *PMptIOCFactsReply;
+#pragma pack()
+AssertCompileSize(MptIOCFactsReply, 60);
+
+/**
+ * Port facts request
+ */
+#pragma pack(1)
+typedef struct MptPortFactsRequest
+{
+ /** Reserved */
+ uint16_t u16Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved */
+ uint16_t u16Reserved2;
+ /** Port number to get facts for. */
+ uint8_t u8PortNumber;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptPortFactsRequest, *PMptPortFactsRequest;
+#pragma pack()
+AssertCompileSize(MptPortFactsRequest, 12);
+
+/**
+ * Port facts reply.
+ */
+#pragma pack(1)
+typedef struct MptPortFactsReply
+{
+ /** Reserved. */
+ uint16_t u16Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved */
+ uint16_t u16Reserved2;
+ /** Port number the facts are for. */
+ uint8_t u8PortNumber;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved3;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Port type */
+ uint8_t u8PortType;
+ /** Maximum number of devices on this port. */
+ uint16_t u16MaxDevices;
+ /** SCSI ID of this port on the attached bus. */
+ uint16_t u16PortSCSIID;
+ /** Protocol flags. */
+ uint16_t u16ProtocolFlags;
+ /** Maximum number of target command buffers which can be posted to this port at a time. */
+ uint16_t u16MaxPostedCmdBuffers;
+ /** Maximum number of target IDs that remain persistent between power/reset cycles. */
+ uint16_t u16MaxPersistentIDs;
+ /** Maximum number of LAN buckets. */
+ uint16_t u16MaxLANBuckets;
+ /** Reserved. */
+ uint16_t u16Reserved4;
+ /** Reserved. */
+ uint32_t u32Reserved;
+} MptPortFactsReply, *PMptPortFactsReply;
+#pragma pack()
+AssertCompileSize(MptPortFactsReply, 40);
+
+/**
+ * Port Enable request.
+ */
+#pragma pack(1)
+typedef struct MptPortEnableRequest
+{
+ /** Reserved. */
+ uint16_t u16Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint16_t u16Reserved2;
+ /** Port number to enable. */
+ uint8_t u8PortNumber;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptPortEnableRequest, *PMptPortEnableRequest;
+#pragma pack()
+AssertCompileSize(MptPortEnableRequest, 12);
+
+/**
+ * Port enable reply.
+ */
+#pragma pack(1)
+typedef struct MptPortEnableReply
+{
+ /** Reserved. */
+ uint16_t u16Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved */
+ uint16_t u16Reserved2;
+ /** Port number which was enabled. */
+ uint8_t u8PortNumber;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved3;
+ /** IO controller status */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+} MptPortEnableReply, *PMptPortEnableReply;
+#pragma pack()
+AssertCompileSize(MptPortEnableReply, 20);
+
+/**
+ * Event notification request.
+ */
+#pragma pack(1)
+typedef struct MptEventNotificationRequest
+{
+ /** Switch - Turns event notification on and off. */
+ uint8_t u8Switch;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Chain offset. */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint8_t u8reserved2[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptEventNotificationRequest, *PMptEventNotificationRequest;
+#pragma pack()
+AssertCompileSize(MptEventNotificationRequest, 12);
+
+/**
+ * Event notification reply.
+ */
+#pragma pack(1)
+typedef struct MptEventNotificationReply
+{
+ /** Event data length. */
+ uint16_t u16EventDataLength;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint16_t u16Reserved1;
+ /** Ack required. */
+ uint8_t u8AckRequired;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved2;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+ /** Notification event. */
+ uint32_t u32Event;
+ /** Event context. */
+ uint32_t u32EventContext;
+ /** Event data. */
+ uint32_t u32EventData;
+} MptEventNotificationReply, *PMptEventNotificationReply;
+#pragma pack()
+AssertCompileSize(MptEventNotificationReply, 32);
+
+#define MPT_EVENT_EVENT_CHANGE (0x0000000a)
+
+/**
+ * FW download request.
+ */
+#pragma pack(1)
+typedef struct MptFWDownloadRequest
+{
+ /** Switch - Turns event notification on and off. */
+ uint8_t u8ImageType;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Chain offset. */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint8_t u8Reserved2[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptFWDownloadRequest, *PMptFWDownloadRequest;
+#pragma pack()
+AssertCompileSize(MptFWDownloadRequest, 12);
+
+#define MPT_FW_DOWNLOAD_REQUEST_IMAGE_TYPE_RESERVED 0
+#define MPT_FW_DOWNLOAD_REQUEST_IMAGE_TYPE_FIRMWARE 1
+#define MPT_FW_DOWNLOAD_REQUEST_IMAGE_TYPE_MPI_BIOS 2
+#define MPT_FW_DOWNLOAD_REQUEST_IMAGE_TYPE_NVDATA 3
+
+/**
+ * FW download reply.
+ */
+#pragma pack(1)
+typedef struct MptFWDownloadReply
+{
+ /** Reserved. */
+ uint16_t u16Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint8_t u8Reserved2[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved2;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+} MptFWDownloadReply, *PMptFWDownloadReply;
+#pragma pack()
+AssertCompileSize(MptFWDownloadReply, 20);
+
+/**
+ * FW upload request.
+ */
+#pragma pack(1)
+typedef struct MptFWUploadRequest
+{
+ /** Requested image type. */
+ uint8_t u8ImageType;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Chain offset. */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint8_t u8Reserved2[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+} MptFWUploadRequest, *PMptFWUploadRequest;
+#pragma pack()
+AssertCompileSize(MptFWUploadRequest, 12);
+
+/**
+ * FW upload reply.
+ */
+#pragma pack(1)
+typedef struct MptFWUploadReply
+{
+ /** Image type. */
+ uint8_t u8ImageType;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Reserved. */
+ uint8_t u8Reserved2[3];
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved2;
+ /** IO controller status. */
+ uint16_t u16IOCStatus;
+ /** IO controller log information. */
+ uint32_t u32IOCLogInfo;
+ /** Uploaded image size. */
+ uint32_t u32ActualImageSize;
+} MptFWUploadReply, *PMptFWUploadReply;
+#pragma pack()
+AssertCompileSize(MptFWUploadReply, 24);
+
+/**
+ * SCSI IO Request
+ */
+#pragma pack(1)
+typedef struct MptSCSIIORequest
+{
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus number */
+ uint8_t u8Bus;
+ /** Chain offset */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** CDB length. */
+ uint8_t u8CDBLength;
+ /** Sense buffer length. */
+ uint8_t u8SenseBufferLength;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** LUN */
+ uint8_t au8LUN[8];
+ /** Control values. */
+ uint32_t u32Control;
+ /** The CDB. */
+ uint8_t au8CDB[16];
+ /** Data length. */
+ uint32_t u32DataLength;
+ /** Sense buffer low 32bit address. */
+ uint32_t u32SenseBufferLowAddress;
+} MptSCSIIORequest, *PMptSCSIIORequest;
+#pragma pack()
+AssertCompileSize(MptSCSIIORequest, 48);
+
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_GET(x) (((x) & 0x3000000) >> 24)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE (0x0)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE (0x1)
+#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ (0x2)
+
+/**
+ * SCSI IO error reply.
+ */
+#pragma pack(1)
+typedef struct MptSCSIIOErrorReply
+{
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus number */
+ uint8_t u8Bus;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** CDB length */
+ uint8_t u8CDBLength;
+ /** Sense buffer length */
+ uint8_t u8SenseBufferLength;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Message flags */
+ uint8_t u8MessageFlags;
+ /** Message context ID */
+ uint32_t u32MessageContext;
+ /** SCSI status. */
+ uint8_t u8SCSIStatus;
+ /** SCSI state */
+ uint8_t u8SCSIState;
+ /** IO controller status */
+ uint16_t u16IOCStatus;
+ /** IO controller log information */
+ uint32_t u32IOCLogInfo;
+ /** Transfer count */
+ uint32_t u32TransferCount;
+ /** Sense count */
+ uint32_t u32SenseCount;
+ /** Response information */
+ uint32_t u32ResponseInfo;
+} MptSCSIIOErrorReply, *PMptSCSIIOErrorReply;
+#pragma pack()
+AssertCompileSize(MptSCSIIOErrorReply, 32);
+
+#define MPT_SCSI_IO_ERROR_SCSI_STATE_AUTOSENSE_VALID (0x01)
+#define MPT_SCSI_IO_ERROR_SCSI_STATE_TERMINATED (0x08)
+
+/**
+ * IOC status codes specific to the SCSI I/O error reply.
+ */
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_BUS (0x0041)
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_INVALID_TARGETID (0x0042)
+#define MPT_SCSI_IO_ERROR_IOCSTATUS_DEVICE_NOT_THERE (0x0043)
+
+/**
+ * SCSI task management request.
+ */
+#pragma pack(1)
+typedef struct MptSCSITaskManagementRequest
+{
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus number */
+ uint8_t u8Bus;
+ /** Chain offset */
+ uint8_t u8ChainOffset;
+ /** Function number */
+ uint8_t u8Function;
+ /** Reserved */
+ uint8_t u8Reserved1;
+ /** Task type */
+ uint8_t u8TaskType;
+ /** Reserved */
+ uint8_t u8Reserved2;
+ /** Message flags */
+ uint8_t u8MessageFlags;
+ /** Message context ID */
+ uint32_t u32MessageContext;
+ /** LUN */
+ uint8_t au8LUN[8];
+ /** Reserved */
+ uint8_t auReserved[28];
+ /** Task message context ID. */
+ uint32_t u32TaskMessageContext;
+} MptSCSITaskManagementRequest, *PMptSCSITaskManagementRequest;
+#pragma pack()
+AssertCompileSize(MptSCSITaskManagementRequest, 52);
+
+/**
+ * SCSI task management reply.
+ */
+#pragma pack(1)
+typedef struct MptSCSITaskManagementReply
+{
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus number */
+ uint8_t u8Bus;
+ /** Message length */
+ uint8_t u8MessageLength;
+ /** Function number */
+ uint8_t u8Function;
+ /** Reserved */
+ uint8_t u8Reserved1;
+ /** Task type */
+ uint8_t u8TaskType;
+ /** Reserved */
+ uint8_t u8Reserved2;
+ /** Message flags */
+ uint8_t u8MessageFlags;
+ /** Message context ID */
+ uint32_t u32MessageContext;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** IO controller status */
+ uint16_t u16IOCStatus;
+ /** IO controller log information */
+ uint32_t u32IOCLogInfo;
+ /** Termination count */
+ uint32_t u32TerminationCount;
+} MptSCSITaskManagementReply, *PMptSCSITaskManagementReply;
+#pragma pack()
+AssertCompileSize(MptSCSITaskManagementReply, 24);
+
+/**
+ * Page address for SAS expander page types.
+ */
+#pragma pack(1)
+typedef union MptConfigurationPageAddressSASExpander
+{
+ struct
+ {
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+ } Form0And2;
+ struct
+ {
+ uint16_t u16Handle;
+ uint8_t u8PhyNum;
+ uint8_t u8Reserved;
+ } Form1;
+} MptConfigurationPageAddressSASExpander, *PMptConfigurationPageAddressSASExpander;
+#pragma pack()
+
+/**
+ * Page address for SAS device page types.
+ */
+#pragma pack(1)
+typedef union MptConfigurationPageAddressSASDevice
+{
+ struct
+ {
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+ } Form0And2;
+ struct
+ {
+ uint8_t u8TargetID;
+ uint8_t u8Bus;
+ uint8_t u8Reserved;
+ } Form1;
+} MptConfigurationPageAddressSASDevice, *PMptConfigurationPageAddressSASDevice;
+#pragma pack()
+
+/**
+ * Page address for SAS PHY page types.
+ */
+#pragma pack(1)
+typedef union MptConfigurationPageAddressSASPHY
+{
+ struct
+ {
+ uint8_t u8PhyNumber;
+ uint8_t u8Reserved[3];
+ } Form0;
+ struct
+ {
+ uint16_t u16Index;
+ uint16_t u16Reserved;
+ } Form1;
+} MptConfigurationPageAddressSASPHY, *PMptConfigurationPageAddressSASPHY;
+#pragma pack()
+
+/**
+ * Page address for SAS Enclosure page types.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageAddressSASEnclosure
+{
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+} MptConfigurationPageAddressSASEnclosure, *PMptConfigurationPageAddressSASEnclosure;
+#pragma pack()
+
+/**
+ * Union of all possible address types.
+ */
+#pragma pack(1)
+typedef union MptConfigurationPageAddress
+{
+ /** 32bit view. */
+ uint32_t u32PageAddress;
+ struct
+ {
+ /** Port number to get the configuration page for. */
+ uint8_t u8PortNumber;
+ /** Reserved. */
+ uint8_t u8Reserved[3];
+ } MPIPortNumber;
+ struct
+ {
+ /** Target ID to get the configuration page for. */
+ uint8_t u8TargetID;
+ /** Bus number to get the configuration page for. */
+ uint8_t u8Bus;
+ /** Reserved. */
+ uint8_t u8Reserved[2];
+ } BusAndTargetId;
+ MptConfigurationPageAddressSASExpander SASExpander;
+ MptConfigurationPageAddressSASDevice SASDevice;
+ MptConfigurationPageAddressSASPHY SASPHY;
+ MptConfigurationPageAddressSASEnclosure SASEnclosure;
+} MptConfigurationPageAddress, *PMptConfigurationPageAddress;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageAddress, 4);
+
+#define MPT_CONFIGURATION_PAGE_ADDRESS_GET_SAS_FORM(x) (((x).u32PageAddress >> 28) & 0x0f)
+
+/**
+ * Configuration request
+ */
+#pragma pack(1)
+typedef struct MptConfigurationRequest
+{
+ /** Action code. */
+ uint8_t u8Action;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Chain offset. */
+ uint8_t u8ChainOffset;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Extended page length. */
+ uint16_t u16ExtPageLength;
+ /** Extended page type */
+ uint8_t u8ExtPageType;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint8_t u8Reserved2[8];
+ /** Version number of the page. */
+ uint8_t u8PageVersion;
+ /** Length of the page in 32bit Dwords. */
+ uint8_t u8PageLength;
+ /** Page number to access. */
+ uint8_t u8PageNumber;
+ /** Type of the page being accessed. */
+ uint8_t u8PageType;
+ /** Page type dependent address. */
+ MptConfigurationPageAddress PageAddress;
+ /** Simple SG element describing the buffer. */
+ MptSGEntrySimple64 SimpleSGElement;
+} MptConfigurationRequest, *PMptConfigurationRequest;
+#pragma pack()
+AssertCompileSize(MptConfigurationRequest, 40);
+
+/** Possible action codes. */
+#define MPT_CONFIGURATION_REQUEST_ACTION_HEADER (0x00)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_CURRENT (0x01)
+#define MPT_CONFIGURATION_REQUEST_ACTION_WRITE_CURRENT (0x02)
+#define MPT_CONFIGURATION_REQUEST_ACTION_DEFAULT (0x03)
+#define MPT_CONFIGURATION_REQUEST_ACTION_WRITE_NVRAM (0x04)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_DEFAULT (0x05)
+#define MPT_CONFIGURATION_REQUEST_ACTION_READ_NVRAM (0x06)
+
+/** Page type codes. */
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_IO_UNIT (0x00)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_IOC (0x01)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_BIOS (0x02)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_SCSI_PORT (0x03)
+#define MPT_CONFIGURATION_REQUEST_PAGE_TYPE_EXTENDED (0x0F)
+
+/**
+ * Configuration reply.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationReply
+{
+ /** Action code. */
+ uint8_t u8Action;
+ /** Reserved. */
+ uint8_t u8Reserved;
+ /** Message length. */
+ uint8_t u8MessageLength;
+ /** Function number. */
+ uint8_t u8Function;
+ /** Extended page length. */
+ uint16_t u16ExtPageLength;
+ /** Extended page type */
+ uint8_t u8ExtPageType;
+ /** Message flags. */
+ uint8_t u8MessageFlags;
+ /** Message context ID. */
+ uint32_t u32MessageContext;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** I/O controller status. */
+ uint16_t u16IOCStatus;
+ /** I/O controller log information. */
+ uint32_t u32IOCLogInfo;
+ /** Version number of the page. */
+ uint8_t u8PageVersion;
+ /** Length of the page in 32bit Dwords. */
+ uint8_t u8PageLength;
+ /** Page number to access. */
+ uint8_t u8PageNumber;
+ /** Type of the page being accessed. */
+ uint8_t u8PageType;
+} MptConfigurationReply, *PMptConfigurationReply;
+#pragma pack()
+AssertCompileSize(MptConfigurationReply, 24);
+
+/** Additional I/O controller status codes for the configuration reply. */
+#define MPT_IOCSTATUS_CONFIG_INVALID_ACTION (0x0020)
+#define MPT_IOCSTATUS_CONFIG_INVALID_TYPE (0x0021)
+#define MPT_IOCSTATUS_CONFIG_INVALID_PAGE (0x0022)
+#define MPT_IOCSTATUS_CONFIG_INVALID_DATA (0x0023)
+#define MPT_IOCSTATUS_CONFIG_NO_DEFAULTS (0x0024)
+#define MPT_IOCSTATUS_CONFIG_CANT_COMMIT (0x0025)
+
+/**
+ * Union of all possible request messages.
+ */
+typedef union MptRequestUnion
+{
+ MptMessageHdr Header;
+ MptIOCInitRequest IOCInit;
+ MptIOCFactsRequest IOCFacts;
+ MptPortFactsRequest PortFacts;
+ MptPortEnableRequest PortEnable;
+ MptEventNotificationRequest EventNotification;
+ MptSCSIIORequest SCSIIO;
+ MptSCSITaskManagementRequest SCSITaskManagement;
+ MptConfigurationRequest Configuration;
+ MptFWDownloadRequest FWDownload;
+ MptFWUploadRequest FWUpload;
+} MptRequestUnion, *PMptRequestUnion;
+
+/**
+ * Union of all possible reply messages.
+ */
+typedef union MptReplyUnion
+{
+ /** 16bit view. */
+ uint16_t au16Reply[30];
+ MptDefaultReplyMessage Header;
+ MptIOCInitReply IOCInit;
+ MptIOCFactsReply IOCFacts;
+ MptPortFactsReply PortFacts;
+ MptPortEnableReply PortEnable;
+ MptEventNotificationReply EventNotification;
+ MptSCSIIOErrorReply SCSIIOError;
+ MptSCSITaskManagementReply SCSITaskManagement;
+ MptConfigurationReply Configuration;
+ MptFWDownloadReply FWDownload;
+ MptFWUploadReply FWUpload;
+} MptReplyUnion, *PMptReplyUnion;
+AssertCompileSize(MptReplyUnion, 60);
+
+/**
+ * Firmware image header.
+ */
+#pragma pack(1)
+typedef struct FwImageHdr
+{
+ /** ARM branch instruction. */
+ uint32_t u32ArmBrInsn;
+ /** Signature part 1. */
+ uint32_t u32Signature1;
+ /** Signature part 2. */
+ uint32_t u32Signature2;
+ /** Signature part 3. */
+ uint32_t u32Signature3;
+ /** Another ARM branch instruction. */
+ uint32_t u32ArmBrInsn2;
+ /** Yet another ARM branch instruction. */
+ uint32_t u32ArmBrInsn3;
+ /** Reserved. */
+ uint32_t u32Reserved;
+ /** Checksum of the image. */
+ uint32_t u32Checksum;
+ /** Vendor ID. */
+ uint16_t u16VendorId;
+ /** Product ID. */
+ uint16_t u16ProductId;
+ /** Firmware version. */
+ uint32_t u32FwVersion;
+ /** Firmware sequencer Code version. */
+ uint32_t u32SeqCodeVersion;
+ /** Image size in bytes including the header. */
+ uint32_t u32ImageSize;
+ /** Offset of the first extended image header. */
+ uint32_t u32NextImageHeaderOffset;
+ /** Start address of the image in IOC memory. */
+ uint32_t u32LoadStartAddress;
+ /** Absolute start address of the Iop ARM. */
+ uint32_t u32IopResetVectorValue;
+ /** Address of the IopResetVector register. */
+ uint32_t u32IopResetVectorRegAddr;
+ /** Marker value for what utility. */
+ uint32_t u32VersionNameWhat;
+ /** ASCII string of version. */
+ uint8_t aszVersionName[256];
+ /** Marker value for what utility. */
+ uint32_t u32VendorNameWhat;
+ /** ASCII string of vendor name. */
+ uint8_t aszVendorName[256];
+} FwImageHdr, *PFwImageHdr;
+#pragma pack()
+AssertCompileSize(FwImageHdr, 584);
+
+/** First part of the signature. */
+#define LSILOGIC_FWIMGHDR_SIGNATURE1 UINT32_C(0x5aeaa55a)
+/** Second part of the signature. */
+#define LSILOGIC_FWIMGHDR_SIGNATURE2 UINT32_C(0xa55aeaa5)
+/** Third part of the signature. */
+#define LSILOGIC_FWIMGHDR_SIGNATURE3 UINT32_C(0x5aa55aea)
+/** Load address of the firmware image to watch for,
+ * seen used by Solaris 9. When this value is written to the
+ * diagnostic address register we know a firmware image is downloaded.
+ */
+#define LSILOGIC_FWIMGHDR_LOAD_ADDRESS UINT32_C(0x21ff5e00)
+
+/**
+ * Configuration Page attributes.
+ */
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_READONLY (0x00)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_CHANGEABLE (0x10)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT (0x20)
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_PERSISTENT_READONLY (0x30)
+
+#define MPT_CONFIGURATION_PAGE_ATTRIBUTE_GET(u8PageType) ((u8PageType) & 0xf0)
+
+/**
+ * Configuration Page types.
+ */
+#define MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT (0x00)
+#define MPT_CONFIGURATION_PAGE_TYPE_IOC (0x01)
+#define MPT_CONFIGURATION_PAGE_TYPE_BIOS (0x02)
+#define MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT (0x03)
+#define MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_DEVICE (0x04)
+#define MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING (0x09)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED (0x0F)
+
+#define MPT_CONFIGURATION_PAGE_TYPE_GET(u8PageType) ((u8PageType) & 0x0f)
+
+/**
+ * Extented page types.
+ */
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASIOUNIT (0x10)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASEXPANDER (0x11)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE (0x12)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASPHYS (0x13)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_LOG (0x14)
+#define MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_ENCLOSURE (0x15)
+
+/**
+ * Configuration Page header - Common to all pages.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageHeader
+{
+ /** Version of the page. */
+ uint8_t u8PageVersion;
+ /** The length of the page in 32bit D-Words. */
+ uint8_t u8PageLength;
+ /** Number of the page. */
+ uint8_t u8PageNumber;
+ /** Type of the page. */
+ uint8_t u8PageType;
+} MptConfigurationPageHeader, *PMptConfigurationPageHeader;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageHeader, 4);
+
+/**
+ * Extended configuration page header - Common to all extended pages.
+ */
+#pragma pack(1)
+typedef struct MptExtendedConfigurationPageHeader
+{
+ /** Version of the page. */
+ uint8_t u8PageVersion;
+ /** Reserved. */
+ uint8_t u8Reserved1;
+ /** Number of the page. */
+ uint8_t u8PageNumber;
+ /** Type of the page. */
+ uint8_t u8PageType;
+ /** Extended page length. */
+ uint16_t u16ExtPageLength;
+ /** Extended page type. */
+ uint8_t u8ExtPageType;
+ /** Reserved */
+ uint8_t u8Reserved2;
+} MptExtendedConfigurationPageHeader, *PMptExtendedConfigurationPageHeader;
+#pragma pack()
+AssertCompileSize(MptExtendedConfigurationPageHeader, 8);
+
+/**
+ * Manufacturing page 0. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[76];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Name of the chip. */
+ uint8_t abChipName[16];
+ /** Chip revision. */
+ uint8_t abChipRevision[8];
+ /** Board name. */
+ uint8_t abBoardName[16];
+ /** Board assembly. */
+ uint8_t abBoardAssembly[16];
+ /** Board tracer number. */
+ uint8_t abBoardTracerNumber[16];
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing0, *PMptConfigurationPageManufacturing0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing0, 76);
+
+/**
+ * Manufacturing page 1. - Readonly Persistent.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing1
+{
+ /** Union */
+ union
+ {
+ /** Byte view */
+ uint8_t abPageData[260];
+ /** Field view */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** VPD info - don't know what belongs here so all zero. */
+ uint8_t abVPDInfo[256];
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing1, *PMptConfigurationPageManufacturing1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing1, 260);
+
+/**
+ * Manufacturing page 2. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** PCI Device ID. */
+ uint16_t u16PCIDeviceID;
+ /** PCI Revision ID. */
+ uint8_t u8PCIRevisionID;
+ /** Reserved. */
+ uint8_t u8Reserved;
+ /** Hardware specific settings... */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing2, *PMptConfigurationPageManufacturing2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing2, 8);
+
+/**
+ * Manufacturing page 3. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing3
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** PCI Device ID. */
+ uint16_t u16PCIDeviceID;
+ /** PCI Revision ID. */
+ uint8_t u8PCIRevisionID;
+ /** Reserved. */
+ uint8_t u8Reserved;
+ /** Chip specific settings... */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing3, *PMptConfigurationPageManufacturing3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing3, 8);
+
+/**
+ * Manufacturing page 4. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing4
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[84];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved. */
+ uint32_t u32Reserved;
+ /** InfoOffset0. */
+ uint8_t u8InfoOffset0;
+ /** Info size. */
+ uint8_t u8InfoSize0;
+ /** InfoOffset1. */
+ uint8_t u8InfoOffset1;
+ /** Info size. */
+ uint8_t u8InfoSize1;
+ /** Size of the inquiry data. */
+ uint8_t u8InquirySize;
+ /** Reserved. */
+ uint8_t abReserved[3];
+ /** Inquiry data. */
+ uint8_t abInquiryData[56];
+ /** IS volume settings. */
+ uint32_t u32ISVolumeSettings;
+ /** IME volume settings. */
+ uint32_t u32IMEVolumeSettings;
+ /** IM volume settings. */
+ uint32_t u32IMVolumeSettings;
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing4, *PMptConfigurationPageManufacturing4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing4, 84);
+
+/**
+ * Manufacturing page 5 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing5
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[88];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Base WWID. */
+ uint64_t u64BaseWWID;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Number of ForceWWID fields in this page. */
+ uint8_t u8NumForceWWID;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** Reserved */
+ uint32_t au32Reserved[2];
+ /** ForceWWID entries Maximum of 8 because the SAS controller doesn't has more */
+ uint64_t au64ForceWWID[8];
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing5, *PMptConfigurationPageManufacturing5;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing5, 24+64);
+
+/**
+ * Manufacturing page 6 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing6
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[4];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Product specific data - 0 for now */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing6, *PMptConfigurationPageManufacturing6;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing6, 4);
+
+/**
+ * Manufacutring page 7 - PHY element.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing7PHY
+{
+ /** Pinout */
+ uint32_t u32Pinout;
+ /** Connector name */
+ uint8_t szConnector[16];
+ /** Location */
+ uint8_t u8Location;
+ /** reserved */
+ uint8_t u8Reserved;
+ /** Slot */
+ uint16_t u16Slot;
+} MptConfigurationPageManufacturing7PHY, *PMptConfigurationPageManufacturing7PHY;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing7PHY, 24);
+
+/**
+ * Manufacturing page 7 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing7
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved */
+ uint32_t au32Reserved[2];
+ /** Flags */
+ uint32_t u32Flags;
+ /** Enclosure name */
+ uint8_t szEnclosureName[16];
+ /** Number of PHYs */
+ uint8_t u8NumPhys;
+ /** Reserved */
+ uint8_t au8Reserved[3];
+ /** PHY list for the SAS controller - variable depending on the number of ports */
+ MptConfigurationPageManufacturing7PHY aPHY[1];
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing7, *PMptConfigurationPageManufacturing7;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing7, 36+sizeof(MptConfigurationPageManufacturing7PHY));
+
+#define LSILOGICSCSI_MANUFACTURING7_GET_SIZE(ports) (sizeof(MptConfigurationPageManufacturing7) + ((ports) - 1) * sizeof(MptConfigurationPageManufacturing7PHY))
+
+/** Flags for the flags field */
+#define LSILOGICSCSI_MANUFACTURING7_FLAGS_USE_PROVIDED_INFORMATION RT_BIT(0)
+
+/** Flags for the pinout field */
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_UNKNOWN RT_BIT(0)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8482 RT_BIT(1)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8470_LANE1 RT_BIT(8)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8470_LANE2 RT_BIT(9)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8470_LANE3 RT_BIT(10)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8470_LANE4 RT_BIT(11)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8484_LANE1 RT_BIT(16)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8484_LANE2 RT_BIT(17)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8484_LANE3 RT_BIT(18)
+#define LSILOGICSCSI_MANUFACTURING7_PINOUT_SFF8484_LANE4 RT_BIT(19)
+
+/** Flags for the location field */
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_UNKNOWN 0x01
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_INTERNAL 0x02
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_EXTERNAL 0x04
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_SWITCHABLE 0x08
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_AUTO 0x10
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_NOT_PRESENT 0x20
+#define LSILOGICSCSI_MANUFACTURING7_LOCATION_NOT_CONNECTED 0x80
+
+/**
+ * Manufacturing page 8 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing8
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[4];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Product specific information */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing8, *PMptConfigurationPageManufacturing8;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing8, 4);
+
+/**
+ * Manufacturing page 9 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing9
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[4];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Product specific information */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing9, *PMptConfigurationPageManufacturing9;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing9, 4);
+
+/**
+ * Manufacturing page 10 - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageManufacturing10
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[4];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Product specific information */
+ } fields;
+ } u;
+} MptConfigurationPageManufacturing10, *PMptConfigurationPageManufacturing10;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageManufacturing10, 4);
+
+/**
+ * IO Unit page 0. - Readonly.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** A unique identifier. */
+ uint64_t u64UniqueIdentifier;
+ } fields;
+ } u;
+} MptConfigurationPageIOUnit0, *PMptConfigurationPageIOUnit0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit0, 12);
+
+/**
+ * IO Unit page 1. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Flag whether this is a single function PCI device. */
+ unsigned fSingleFunction: 1;
+ /** Flag whether all possible paths to a device are mapped. */
+ unsigned fAllPathsMapped: 1;
+ /** Reserved. */
+ unsigned u4Reserved: 4;
+ /** Flag whether all RAID functionality is disabled. */
+ unsigned fIntegratedRAIDDisabled: 1;
+ /** Flag whether 32bit PCI accesses are forced. */
+ unsigned f32BitAccessForced: 1;
+ /** Reserved. */
+ unsigned abReserved: 24;
+ } fields;
+ } u;
+} MptConfigurationPageIOUnit1, *PMptConfigurationPageIOUnit1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit1, 8);
+
+/**
+ * Adapter Ordering.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit2AdapterOrdering
+{
+ /** PCI bus number. */
+ unsigned u8PCIBusNumber: 8;
+ /** PCI device and function number. */
+ unsigned u8PCIDevFn: 8;
+ /** Flag whether the adapter is embedded. */
+ unsigned fAdapterEmbedded: 1;
+ /** Flag whether the adapter is enabled. */
+ unsigned fAdapterEnabled: 1;
+ /** Reserved. */
+ unsigned u6Reserved: 6;
+ /** Reserved. */
+ unsigned u8Reserved: 8;
+} MptConfigurationPageIOUnit2AdapterOrdering, *PMptConfigurationPageIOUnit2AdapterOrdering;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit2AdapterOrdering, 4);
+
+/**
+ * IO Unit page 2. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[28];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved. */
+ unsigned fReserved: 1;
+ /** Flag whether Pause on error is enabled. */
+ unsigned fPauseOnError: 1;
+ /** Flag whether verbose mode is enabled. */
+ unsigned fVerboseModeEnabled: 1;
+ /** Set to disable color video. */
+ unsigned fDisableColorVideo: 1;
+ /** Flag whether int 40h is hooked. */
+ unsigned fNotHookInt40h: 1;
+ /** Reserved. */
+ unsigned u3Reserved: 3;
+ /** Reserved. */
+ unsigned abReserved: 24;
+ /** BIOS version. */
+ uint32_t u32BIOSVersion;
+ /** Adapter ordering. */
+ MptConfigurationPageIOUnit2AdapterOrdering aAdapterOrder[4];
+ } fields;
+ } u;
+} MptConfigurationPageIOUnit2, *PMptConfigurationPageIOUnit2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit2, 28);
+
+/*
+ * IO Unit page 3. - Read/Write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit3
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Number of GPIO values. */
+ uint8_t u8GPIOCount;
+ /** Reserved. */
+ uint8_t abReserved[3];
+ } fields;
+ } u;
+} MptConfigurationPageIOUnit3, *PMptConfigurationPageIOUnit3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit3, 8);
+
+/*
+ * IO Unit page 4. - Readonly for everyone except the BIOS.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOUnit4
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[20];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved */
+ uint32_t u32Reserved;
+ /** SG entry describing the Firmware location. */
+ MptSGEntrySimple64 FWImageSGE;
+ } fields;
+ } u;
+} MptConfigurationPageIOUnit4, *PMptConfigurationPageIOUnit4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOUnit4, 20);
+
+/**
+ * IOC page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[28];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Total amount of NV memory in bytes. */
+ uint32_t u32TotalNVStore;
+ /** Number of free bytes in the NV store. */
+ uint32_t u32FreeNVStore;
+ /** PCI vendor ID. */
+ uint16_t u16VendorId;
+ /** PCI device ID. */
+ uint16_t u16DeviceId;
+ /** PCI revision ID. */
+ uint8_t u8RevisionId;
+ /** Reserved. */
+ uint8_t abReserved[3];
+ /** PCI class code. */
+ uint32_t u32ClassCode;
+ /** Subsystem vendor Id. */
+ uint16_t u16SubsystemVendorId;
+ /** Subsystem Id. */
+ uint16_t u16SubsystemId;
+ } fields;
+ } u;
+} MptConfigurationPageIOC0, *PMptConfigurationPageIOC0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC0, 28);
+
+/**
+ * IOC page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[16];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Flag whether reply coalescing is enabled. */
+ unsigned fReplyCoalescingEnabled: 1;
+ /** Reserved. */
+ unsigned u31Reserved: 31;
+ /** Coalescing Timeout in microseconds. */
+ unsigned u32CoalescingTimeout: 32;
+ /** Coalescing depth. */
+ unsigned u8CoalescingDepth: 8;
+ /** Reserved. */
+ unsigned u8Reserved0: 8;
+ unsigned u8Reserved1: 8;
+ unsigned u8Reserved2: 8;
+ } fields;
+ } u;
+} MptConfigurationPageIOC1, *PMptConfigurationPageIOC1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC1, 16);
+
+/**
+ * IOC page 2. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Flag whether striping is supported. */
+ unsigned fStripingSupported: 1;
+ /** Flag whether enhanced mirroring is supported. */
+ unsigned fEnhancedMirroringSupported: 1;
+ /** Flag whether mirroring is supported. */
+ unsigned fMirroringSupported: 1;
+ /** Reserved. */
+ unsigned u26Reserved: 26;
+ /** Flag whether SES is supported. */
+ unsigned fSESSupported: 1;
+ /** Flag whether SAF-TE is supported. */
+ unsigned fSAFTESupported: 1;
+ /** Flag whether cross channel volumes are supported. */
+ unsigned fCrossChannelVolumesSupported: 1;
+ /** Number of active integrated RAID volumes. */
+ unsigned u8NumActiveVolumes: 8;
+ /** Maximum number of integrated RAID volumes supported. */
+ unsigned u8MaxVolumes: 8;
+ /** Number of active integrated RAID physical disks. */
+ unsigned u8NumActivePhysDisks: 8;
+ /** Maximum number of integrated RAID physical disks supported. */
+ unsigned u8MaxPhysDisks: 8;
+ /** RAID volumes... - not supported. */
+ } fields;
+ } u;
+} MptConfigurationPageIOC2, *PMptConfigurationPageIOC2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC2, 12);
+
+/**
+ * IOC page 3. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC3
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Number of active integrated RAID physical disks. */
+ uint8_t u8NumPhysDisks;
+ /** Reserved. */
+ uint8_t abReserved[3];
+ } fields;
+ } u;
+} MptConfigurationPageIOC3, *PMptConfigurationPageIOC3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC3, 8);
+
+/**
+ * IOC page 4. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC4
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[8];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Number of SEP entries in this page. */
+ uint8_t u8ActiveSEP;
+ /** Maximum number of SEp entries supported. */
+ uint8_t u8MaxSEP;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** SEP entries... - not supported. */
+ } fields;
+ } u;
+} MptConfigurationPageIOC4, *PMptConfigurationPageIOC4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC4, 8);
+
+/**
+ * IOC page 6. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageIOC6
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[60];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ uint32_t u32CapabilitiesFlags;
+ uint8_t u8MaxDrivesIS;
+ uint8_t u8MaxDrivesIM;
+ uint8_t u8MaxDrivesIME;
+ uint8_t u8Reserved1;
+ uint8_t u8MinDrivesIS;
+ uint8_t u8MinDrivesIM;
+ uint8_t u8MinDrivesIME;
+ uint8_t u8Reserved2;
+ uint8_t u8MaxGlobalHotSpares;
+ uint8_t u8Reserved3;
+ uint16_t u16Reserved4;
+ uint32_t u32Reserved5;
+ uint32_t u32SupportedStripeSizeMapIS;
+ uint32_t u32SupportedStripeSizeMapIME;
+ uint32_t u32Reserved6;
+ uint8_t u8MetadataSize;
+ uint8_t u8Reserved7;
+ uint16_t u16Reserved8;
+ uint16_t u16MaxBadBlockTableEntries;
+ uint16_t u16Reserved9;
+ uint16_t u16IRNvsramUsage;
+ uint16_t u16Reserved10;
+ uint32_t u32IRNvsramVersion;
+ uint32_t u32Reserved11;
+ } fields;
+ } u;
+} MptConfigurationPageIOC6, *PMptConfigurationPageIOC6;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageIOC6, 60);
+
+/**
+ * BIOS page 1 - Read/write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageBIOS1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[48];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** BIOS options */
+ uint32_t u32BiosOptions;
+ /** IOC settings */
+ uint32_t u32IOCSettings;
+ /** Reserved */
+ uint32_t u32Reserved;
+ /** Device settings */
+ uint32_t u32DeviceSettings;
+ /** Number of devices */
+ uint16_t u16NumberOfDevices;
+ /** Expander spinup */
+ uint8_t u8ExpanderSpinup;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** I/O timeout of block devices without removable media */
+ uint16_t u16IOTimeoutBlockDevicesNonRM;
+ /** I/O timeout sequential */
+ uint16_t u16IOTimeoutSequential;
+ /** I/O timeout other */
+ uint16_t u16IOTimeoutOther;
+ /** I/O timeout of block devices with removable media */
+ uint16_t u16IOTimeoutBlockDevicesRM;
+ } fields;
+ } u;
+} MptConfigurationPageBIOS1, *PMptConfigurationPageBIOS1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageBIOS1, 48);
+
+#define LSILOGICSCSI_BIOS1_BIOSOPTIONS_BIOS_DISABLE RT_BIT(0)
+#define LSILOGICSCSI_BIOS1_BIOSOPTIONS_SCAN_FROM_HIGH_TO_LOW RT_BIT(1)
+#define LSILOGICSCSI_BIOS1_BIOSOPTIONS_BIOS_EXTENDED_SAS_SUPPORT RT_BIT(8)
+#define LSILOGICSCSI_BIOS1_BIOSOPTIONS_BIOS_EXTENDED_FC_SUPPORT RT_BIT(9)
+#define LSILOGICSCSI_BIOS1_BIOSOPTIONS_BIOS_EXTENDED_SPI_SUPPORT RT_BIT(10)
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ALTERNATE_CHS RT_BIT(3)
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ADAPTER_SUPPORT_SET(x) ((x) << 4)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ADAPTER_SUPPORT_DISABLED 0x00
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ADAPTER_SUPPORT_BIOS_ONLY 0x01
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ADAPTER_SUPPORT_OS_ONLY 0x02
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_ADAPTER_SUPPORT_BOT 0x03
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_REMOVABLE_MEDIA_SET(x) ((x) << 6)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_REMOVABLE_MEDIA_NO_INT13H 0x00
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_REMOVABLE_BOOT_MEDIA_INT13H 0x01
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_REMOVABLE_MEDIA_INT13H 0x02
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_SPINUP_DELAY_SET(x) ((x & 0xF) << 8)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_SPINUP_DELAY_GET(x) ((x >> 8) & 0x0F)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_MAX_TARGET_SPINUP_SET(x) ((x & 0xF) << 12)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_MAX_TARGET_SPINUP_GET(x) ((x >> 12) & 0x0F)
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_BOOT_PREFERENCE_SET(x) (((x) & 0x3) << 16)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_BOOT_PREFERENCE_ENCLOSURE 0x0
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_BOOT_PREFERENCE_SAS_ADDRESS 0x1
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_DIRECT_ATTACH_SPINUP_MODE_ALL RT_BIT(18)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_AUTO_PORT_ENABLE RT_BIT(19)
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_PORT_ENABLE_REPLY_DELAY_SET(x) (((x) & 0xF) << 20)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_PORT_ENABLE_REPLY_DELAY_GET(x) ((x >> 20) & 0x0F)
+
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_PORT_ENABLE_SPINUP_DELAY_SET(x) (((x) & 0xF) << 24)
+#define LSILOGICSCSI_BIOS1_IOCSETTINGS_PORT_ENABLE_SPINUP_DELAY_GET(x) ((x >> 24) & 0x0F)
+
+#define LSILOGICSCSI_BIOS1_DEVICESETTINGS_DISABLE_LUN_SCANS RT_BIT(0)
+#define LSILOGICSCSI_BIOS1_DEVICESETTINGS_DISABLE_LUN_SCANS_FOR_NON_REMOVABLE_DEVICES RT_BIT(1)
+#define LSILOGICSCSI_BIOS1_DEVICESETTINGS_DISABLE_LUN_SCANS_FOR_REMOVABLE_DEVICES RT_BIT(2)
+#define LSILOGICSCSI_BIOS1_DEVICESETTINGS_DISABLE_LUN_SCANS2 RT_BIT(3)
+#define LSILOGICSCSI_BIOS1_DEVICESETTINGS_DISABLE_SMART_POLLING RT_BIT(4)
+
+#define LSILOGICSCSI_BIOS1_EXPANDERSPINUP_SPINUP_DELAY_SET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_BIOS1_EXPANDERSPINUP_SPINUP_DELAY_GET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_BIOS1_EXPANDERSPINUP_MAX_SPINUP_DELAY_SET(x) (((x) & 0x0F) << 4)
+#define LSILOGICSCSI_BIOS1_EXPANDERSPINUP_MAX_SPINUP_DELAY_GET(x) ((x >> 4) & 0x0F)
+
+/**
+ * BIOS page 2 - Read/write.
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageBIOS2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[384];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved */
+ uint32_t au32Reserved[6];
+ /** Format of the boot device field. */
+ uint8_t u8BootDeviceForm;
+ /** Previous format of the boot device field. */
+ uint8_t u8PrevBootDeviceForm;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** Boot device fields - dependent on the format */
+ union
+ {
+ /** Device for AdapterNumber:Bus:Target:LUN */
+ struct
+ {
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus */
+ uint8_t u8Bus;
+ /** Adapter Number */
+ uint8_t u8AdapterNumber;
+ /** Reserved */
+ uint8_t u8Reserved;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } AdapterNumberBusTargetLUN;
+ /** Device for PCIAddress:Bus:Target:LUN */
+ struct
+ {
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus */
+ uint8_t u8Bus;
+ /** Adapter Number */
+ uint16_t u16PCIAddress;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } PCIAddressBusTargetLUN;
+ /** Device for PCISlotNo:Bus:Target:LUN */
+ struct
+ {
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus */
+ uint8_t u8Bus;
+ /** PCI Slot Number */
+ uint8_t u16PCISlotNo;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } PCIAddressBusSlotLUN;
+ /** Device for FC channel world wide name */
+ struct
+ {
+ /** World wide port name low */
+ uint32_t u32WorldWidePortNameLow;
+ /** World wide port name high */
+ uint32_t u32WorldWidePortNameHigh;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } FCWorldWideName;
+ /** Device for FC channel world wide name */
+ struct
+ {
+ /** SAS address */
+ SASADDRESS SASAddress;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } SASWorldWideName;
+ /** Device for Enclosure/Slot */
+ struct
+ {
+ /** Enclosure logical ID */
+ uint64_t u64EnclosureLogicalID;
+ /** Reserved */
+ uint32_t au32Reserved[3];
+ /** LUN */
+ uint32_t aLUN[5];
+ /** Reserved */
+ uint32_t au32Reserved2[56];
+ } EnclosureSlot;
+ } BootDevice;
+ } fields;
+ } u;
+} MptConfigurationPageBIOS2, *PMptConfigurationPageBIOS2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageBIOS2, 384);
+
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_SET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_FIRST 0x0
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_ADAPTER_BUS_TARGET_LUN 0x1
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_PCIADDR_BUS_TARGET_LUN 0x2
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_PCISLOT_BUS_TARGET_LUN 0x3
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_FC_WWN 0x4
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_SAS_WWN 0x5
+#define LSILOGICSCSI_BIOS2_BOOT_DEVICE_FORM_ENCLOSURE_SLOT 0x6
+
+/**
+ * BIOS page 4 - Read/Write (Where is 3? - not defined in the spec)
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageBIOS4
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reassignment Base WWID */
+ uint64_t u64ReassignmentBaseWWID;
+ } fields;
+ } u;
+} MptConfigurationPageBIOS4, *PMptConfigurationPageBIOS4;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageBIOS4, 12);
+
+/**
+ * SCSI-SPI port page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Flag whether this port is information unit transfers capable. */
+ unsigned fInformationUnitTransfersCapable: 1;
+ /** Flag whether the port is DT (Dual Transfer) capable. */
+ unsigned fDTCapable: 1;
+ /** Flag whether the port is QAS (Quick Arbitrate and Select) capable. */
+ unsigned fQASCapable: 1;
+ /** Reserved. */
+ unsigned u5Reserved1: 5;
+ /** Minimum Synchronous transfer period. */
+ unsigned u8MinimumSynchronousTransferPeriod: 8;
+ /** Maximum synchronous offset. */
+ unsigned u8MaximumSynchronousOffset: 8;
+ /** Reserved. */
+ unsigned u5Reserved2: 5;
+ /** Flag whether indicating the width of the bus - 0 narrow and 1 for wide. */
+ unsigned fWide: 1;
+ /** Reserved */
+ unsigned fReserved: 1;
+ /** Flag whether the port is AIP (Asynchronous Information Protection) capable. */
+ unsigned fAIPCapable: 1;
+ /** Signaling Type. */
+ unsigned u2SignalingType: 2;
+ /** Reserved. */
+ unsigned u30Reserved: 30;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIPort0, *PMptConfigurationPageSCSISPIPort0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort0, 12);
+
+/**
+ * SCSI-SPI port page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** The SCSI ID of the port. */
+ uint8_t u8SCSIID;
+ /** Reserved. */
+ uint8_t u8Reserved;
+ /** Port response IDs Bit mask field. */
+ uint16_t u16PortResponseIDsBitmask;
+ /** Value for the on BUS timer. */
+ uint32_t u32OnBusTimerValue;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIPort1, *PMptConfigurationPageSCSISPIPort1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort1, 12);
+
+/**
+ * Device settings for one device.
+ */
+#pragma pack(1)
+typedef struct MptDeviceSettings
+{
+ /** Timeout for I/O in seconds. */
+ unsigned u8Timeout: 8;
+ /** Minimum synchronous factor. */
+ unsigned u8SyncFactor: 8;
+ /** Flag whether disconnect is enabled. */
+ unsigned fDisconnectEnable: 1;
+ /** Flag whether Scan ID is enabled. */
+ unsigned fScanIDEnable: 1;
+ /** Flag whether Scan LUNs is enabled. */
+ unsigned fScanLUNEnable: 1;
+ /** Flag whether tagged queuing is enabled. */
+ unsigned fTaggedQueuingEnabled: 1;
+ /** Flag whether wide is enabled. */
+ unsigned fWideDisable: 1;
+ /** Flag whether this device is bootable. */
+ unsigned fBootChoice: 1;
+ /** Reserved. */
+ unsigned u10Reserved: 10;
+} MptDeviceSettings, *PMptDeviceSettings;
+#pragma pack()
+AssertCompileSize(MptDeviceSettings, 4);
+
+/**
+ * SCSI-SPI port page 2. - Read/Write for the BIOS
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIPort2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[76];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Flag indicating the bus scan order. */
+ unsigned fBusScanOrderHighToLow: 1;
+ /** Reserved. */
+ unsigned fReserved: 1;
+ /** Flag whether SCSI Bus resets are avoided. */
+ unsigned fAvoidSCSIBusResets: 1;
+ /** Flag whether alternate CHS is used. */
+ unsigned fAlternateCHS: 1;
+ /** Flag whether termination is disabled. */
+ unsigned fTerminationDisabled: 1;
+ /** Reserved. */
+ unsigned u27Reserved: 27;
+ /** Host SCSI ID. */
+ unsigned u4HostSCSIID: 4;
+ /** Initialize HBA. */
+ unsigned u2InitializeHBA: 2;
+ /** Removeable media setting. */
+ unsigned u2RemovableMediaSetting: 2;
+ /** Spinup delay. */
+ unsigned u4SpinupDelay: 4;
+ /** Negotiating settings. */
+ unsigned u2NegotitatingSettings: 2;
+ /** Reserved. */
+ unsigned u18Reserved: 18;
+ /** Device Settings. */
+ MptDeviceSettings aDeviceSettings[16];
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIPort2, *PMptConfigurationPageSCSISPIPort2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIPort2, 76);
+
+/**
+ * SCSI-SPI device page 0. - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[12];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Negotiated Parameters. */
+ /** Information Units enabled. */
+ unsigned fInformationUnitsEnabled: 1;
+ /** Dual Transfers Enabled. */
+ unsigned fDTEnabled: 1;
+ /** QAS enabled. */
+ unsigned fQASEnabled: 1;
+ /** Reserved. */
+ unsigned u5Reserved1: 5;
+ /** Synchronous Transfer period. */
+ unsigned u8NegotiatedSynchronousTransferPeriod: 8;
+ /** Synchronous offset. */
+ unsigned u8NegotiatedSynchronousOffset: 8;
+ /** Reserved. */
+ unsigned u5Reserved2: 5;
+ /** Width - 0 for narrow and 1 for wide. */
+ unsigned fWide: 1;
+ /** Reserved. */
+ unsigned fReserved: 1;
+ /** AIP enabled. */
+ unsigned fAIPEnabled: 1;
+ /** Flag whether negotiation occurred. */
+ unsigned fNegotationOccured: 1;
+ /** Flag whether a SDTR message was rejected. */
+ unsigned fSDTRRejected: 1;
+ /** Flag whether a WDTR message was rejected. */
+ unsigned fWDTRRejected: 1;
+ /** Flag whether a PPR message was rejected. */
+ unsigned fPPRRejected: 1;
+ /** Reserved. */
+ unsigned u28Reserved: 28;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIDevice0, *PMptConfigurationPageSCSISPIDevice0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice0, 12);
+
+/**
+ * SCSI-SPI device page 1. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[16];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Requested Parameters. */
+ /** Information Units enable. */
+ bool fInformationUnitsEnable: 1;
+ /** Dual Transfers Enable. */
+ bool fDTEnable: 1;
+ /** QAS enable. */
+ bool fQASEnable: 1;
+ /** Reserved. */
+ unsigned u5Reserved1: 5;
+ /** Synchronous Transfer period. */
+ unsigned u8NegotiatedSynchronousTransferPeriod: 8;
+ /** Synchronous offset. */
+ unsigned u8NegotiatedSynchronousOffset: 8;
+ /** Reserved. */
+ unsigned u5Reserved2: 5;
+ /** Width - 0 for narrow and 1 for wide. */
+ bool fWide: 1;
+ /** Reserved. */
+ bool fReserved1: 1;
+ /** AIP enable. */
+ bool fAIPEnable: 1;
+ /** Reserved. */
+ bool fReserved2: 1;
+ /** WDTR disallowed. */
+ bool fWDTRDisallowed: 1;
+ /** SDTR disallowed. */
+ bool fSDTRDisallowed: 1;
+ /** Reserved. */
+ unsigned u29Reserved: 29;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIDevice1, *PMptConfigurationPageSCSISPIDevice1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice1, 16);
+
+/**
+ * SCSI-SPI device page 2. - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[16];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Reserved. */
+ unsigned u4Reserved: 4;
+ /** ISI enable. */
+ unsigned fISIEnable: 1;
+ /** Secondary driver enable. */
+ unsigned fSecondaryDriverEnable: 1;
+ /** Reserved. */
+ unsigned fReserved: 1;
+ /** Slew create controller. */
+ unsigned u3SlewRateControler: 3;
+ /** Primary drive strength controller. */
+ unsigned u3PrimaryDriveStrengthControl: 3;
+ /** Secondary drive strength controller. */
+ unsigned u3SecondaryDriveStrengthControl: 3;
+ /** Reserved. */
+ unsigned u12Reserved: 12;
+ /** XCLKH_ST. */
+ unsigned fXCLKH_ST: 1;
+ /** XCLKS_ST. */
+ unsigned fXCLKS_ST: 1;
+ /** XCLKH_DT. */
+ unsigned fXCLKH_DT: 1;
+ /** XCLKS_DT. */
+ unsigned fXCLKS_DT: 1;
+ /** Parity pipe select. */
+ unsigned u2ParityPipeSelect: 2;
+ /** Reserved. */
+ unsigned u30Reserved: 30;
+ /** Data bit pipeline select. */
+ unsigned u32DataPipelineSelect: 32;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIDevice2, *PMptConfigurationPageSCSISPIDevice2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice2, 16);
+
+/**
+ * SCSI-SPI device page 3 (Revision G). - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSCSISPIDevice3
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Number of times the IOC rejected a message because it doesn't support the operation. */
+ uint16_t u16MsgRejectCount;
+ /** Number of times the SCSI bus entered an invalid operation state. */
+ uint16_t u16PhaseErrorCount;
+ /** Number of parity errors. */
+ uint16_t u16ParityCount;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIDevice3, *PMptConfigurationPageSCSISPIDevice3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSCSISPIDevice3, 12);
+
+/**
+ * PHY entry for the SAS I/O unit page 0
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit0PHY
+{
+ /** Port number */
+ uint8_t u8Port;
+ /** Port flags */
+ uint8_t u8PortFlags;
+ /** Phy flags */
+ uint8_t u8PhyFlags;
+ /** negotiated link rate */
+ uint8_t u8NegotiatedLinkRate;
+ /** Controller phy device info */
+ uint32_t u32ControllerPhyDeviceInfo;
+ /** Attached device handle */
+ uint16_t u16AttachedDevHandle;
+ /** Controller device handle */
+ uint16_t u16ControllerDevHandle;
+ /** Discovery status */
+ uint32_t u32DiscoveryStatus;
+} MptConfigurationPageSASIOUnit0PHY, *PMptConfigurationPageSASIOUnit0PHY;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit0PHY, 16);
+
+/**
+ * SAS I/O Unit page 0 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Nvdata version default */
+ uint16_t u16NvdataVersionDefault;
+ /** Nvdata version persistent */
+ uint16_t u16NvdataVersionPersistent;
+ /** Number of physical ports */
+ uint8_t u8NumPhys;
+ /** Reserved */
+ uint8_t au8Reserved[3];
+ /** Content for each physical port - variable depending on the amount of ports. */
+ MptConfigurationPageSASIOUnit0PHY aPHY[1];
+ } fields;
+ } u;
+} MptConfigurationPageSASIOUnit0, *PMptConfigurationPageSASIOUnit0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit0, 8+2+2+1+3+sizeof(MptConfigurationPageSASIOUnit0PHY));
+
+#define LSILOGICSCSI_SASIOUNIT0_GET_SIZE(ports) (sizeof(MptConfigurationPageSASIOUnit0) + ((ports) - 1) * sizeof(MptConfigurationPageSASIOUnit0PHY))
+
+#define LSILOGICSCSI_SASIOUNIT0_PORT_CONFIGURATION_AUTO RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT0_PORT_TARGET_IOC RT_BIT(2)
+#define LSILOGICSCSI_SASIOUNIT0_PORT_DISCOVERY_IN_STATUS RT_BIT(3)
+
+#define LSILOGICSCSI_SASIOUNIT0_PHY_RX_INVERTED RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT0_PHY_TX_INVERTED RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT0_PHY_DISABLED RT_BIT(2)
+
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_SET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_GET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_UNKNOWN 0x00
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_DISABLED 0x01
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_FAILED 0x02
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_SATA_OOB 0x03
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_15GB 0x08
+#define LSILOGICSCSI_SASIOUNIT0_NEGOTIATED_RATE_30GB 0x09
+
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_SET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_NO 0x0
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_END 0x1
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_EDGE_EXPANDER 0x2
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_TYPE_FANOUT_EXPANDER 0x3
+
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SATA_HOST RT_BIT(3)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SMP_INITIATOR RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_STP_INITIATOR RT_BIT(5)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SSP_INITIATOR RT_BIT(6)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SATA RT_BIT(7)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SMP_TARGET RT_BIT(8)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_STP_TARGET RT_BIT(9)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SSP_TARGET RT_BIT(10)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_DIRECT_ATTACHED RT_BIT(11)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_LSI RT_BIT(12)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_ATAPI_DEVICE RT_BIT(13)
+#define LSILOGICSCSI_SASIOUNIT0_DEVICE_SEP_DEVICE RT_BIT(14)
+
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_LOOP RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_UNADDRESSABLE RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_SAME_SAS_ADDR RT_BIT(2)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_EXPANDER_ERROR RT_BIT(3)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_SMP_TIMEOUT RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_EXP_ROUTE_OOE RT_BIT(5)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_EXP_ROUTE_IDX RT_BIT(6)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_SMP_FUNC_FAILED RT_BIT(7)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_SMP_CRC_ERROR RT_BIT(8)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_SUBTRSCTIVE_LNK RT_BIT(9)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_TBL_LNK RT_BIT(10)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_UNSUPPORTED_DEV RT_BIT(11)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_MAX_SATA_TGTS RT_BIT(12)
+#define LSILOGICSCSI_SASIOUNIT0_DISCOVERY_STATUS_MULT_CTRLS RT_BIT(13)
+
+/**
+ * PHY entry for the SAS I/O unit page 1
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit1PHY
+{
+ /** Port number */
+ uint8_t u8Port;
+ /** Port flags */
+ uint8_t u8PortFlags;
+ /** Phy flags */
+ uint8_t u8PhyFlags;
+ /** Max link rate */
+ uint8_t u8MaxMinLinkRate;
+ /** Controller phy device info */
+ uint32_t u32ControllerPhyDeviceInfo;
+ /** Maximum target port connect time */
+ uint16_t u16MaxTargetPortConnectTime;
+ /** Reserved */
+ uint16_t u16Reserved;
+} MptConfigurationPageSASIOUnit1PHY, *PMptConfigurationPageSASIOUnit1PHY;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit1PHY, 12);
+
+/**
+ * SAS I/O Unit page 1 - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Control flags */
+ uint16_t u16ControlFlags;
+ /** maximum number of SATA targets */
+ uint16_t u16MaxNumSATATargets;
+ /** additional control flags */
+ uint16_t u16AdditionalControlFlags;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** Number of PHYs */
+ uint8_t u8NumPhys;
+ /** maximum SATA queue depth */
+ uint8_t u8SATAMaxQDepth;
+ /** Delay for reporting missing devices. */
+ uint8_t u8ReportDeviceMissingDelay;
+ /** I/O device missing delay */
+ uint8_t u8IODeviceMissingDelay;
+ /** Content for each physical port - variable depending on the number of ports */
+ MptConfigurationPageSASIOUnit1PHY aPHY[1];
+ } fields;
+ } u;
+} MptConfigurationPageSASIOUnit1, *PMptConfigurationPageSASIOUnit1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit1, 8+12+sizeof(MptConfigurationPageSASIOUnit1PHY));
+
+#define LSILOGICSCSI_SASIOUNIT1_GET_SIZE(ports) (sizeof(MptConfigurationPageSASIOUnit1) + ((ports) - 1) * sizeof(MptConfigurationPageSASIOUnit1PHY))
+
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_CLEAR_SATA_AFFILIATION RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_FIRST_LEVEL_DISCOVERY_ONLY RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SUBTRACTIVE_LNK_ILLEGAL RT_BIT(2)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_IOC_ENABLE_HIGH_PHY RT_BIT(3)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_FUA_REQUIRED RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_NCQ_REQUIRED RT_BIT(5)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_SMART_REQUIRED RT_BIT(6)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_LBA48_REQUIRED RT_BIT(7)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_INIT_POSTPONED RT_BIT(8)
+
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_DEVICE_SUPPORT_SET(x) (((x) & 0x3) << 9)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_DEVICE_SUPPORT_GET(x) (((x) >> 9) & 0x3)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_DEVICE_SUPPORT_SAS_AND_SATA 0x00
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_DEVICE_SUPPORT_SAS 0x01
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_DEVICE_SUPPORT_SATA 0x02
+
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_EXP_ADDR RT_BIT(11)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_SETTINGS_PRESERV_REQUIRED RT_BIT(12)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_LIMIT_RATE_15GB RT_BIT(13)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SATA_LIMIT_RATE_30GB RT_BIT(14)
+#define LSILOGICSCSI_SASIOUNIT1_CONTROL_SAS_SELF_TEST_ENABLED RT_BIT(15)
+
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_TBL_LNKS_ALLOW RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_SATA_RST_NO_AFFIL RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_SATA_RST_SELF_AFFIL RT_BIT(2)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_SATA_RST_OTHER_AFFIL RT_BIT(3)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_SATA_RST_PORT_EN_ONLY RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_HIDE_NON_ZERO_PHYS RT_BIT(5)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_SATA_ASYNC_NOTIF RT_BIT(6)
+#define LSILOGICSCSI_SASIOUNIT1_ADDITIONAL_CONTROL_MULT_PORTS_ILL_SAME_DOMAIN RT_BIT(7)
+
+#define LSILOGICSCSI_SASIOUNIT1_MISSING_DEVICE_DELAY_UNITS_16_SEC RT_BIT(7)
+#define LSILOGICSCSI_SASIOUNIT1_MISSING_DEVICE_DELAY_SET(x) ((x) & 0x7F)
+#define LSILOGICSCSI_SASIOUNIT1_MISSING_DEVICE_DELAY_GET(x) ((x) & 0x7F)
+
+#define LSILOGICSCSI_SASIOUNIT1_PORT_CONFIGURATION_AUTO RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT1_PORT_CONFIGURATION_IOC1 RT_BIT(2)
+
+#define LSILOGICSCSI_SASIOUNIT1_PHY_RX_INVERT RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT1_PHY_TX_INVERT RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT1_PHY_DISABLE RT_BIT(2)
+
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MIN_SET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MIN_GET(x) ((x) & 0x0F)
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MAX_SET(x) (((x) & 0x0F) << 4)
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_MAX_GET(x) ((x >> 4) & 0x0F)
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_15GB 0x8
+#define LSILOGICSCSI_SASIOUNIT1_LINK_RATE_30GB 0x9
+
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_SET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_GET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_NO 0x0
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_END 0x1
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_EDGE_EXPANDER 0x2
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_TYPE_FANOUT_EXPANDER 0x3
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_SMP_INITIATOR RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_STP_INITIATOR RT_BIT(5)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_SSP_INITIATOR RT_BIT(6)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_SMP_TARGET RT_BIT(8)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_STP_TARGET RT_BIT(9)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_SSP_TARGET RT_BIT(10)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_DIRECT_ATTACHED RT_BIT(11)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_LSI RT_BIT(12)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_ATAPI RT_BIT(13)
+#define LSILOGICSCSI_SASIOUNIT1_CTL_PHY_DEVICE_SEP RT_BIT(14)
+
+/**
+ * SAS I/O unit page 2 - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Device numbers per enclosure */
+ uint8_t u8NumDevsPerEnclosure;
+ /** Boot device wait time */
+ uint8_t u8BootDeviceWaitTime;
+ /** Reserved */
+ uint16_t u16Reserved;
+ /** Maximum number of persistent Bus and target ID mappings */
+ uint16_t u16MaxPersistentIDs;
+ /** Number of persistent IDs used */
+ uint16_t u16NumPersistentIDsUsed;
+ /** Status */
+ uint8_t u8Status;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Maximum number of physical mapped IDs */
+ uint16_t u16MaxNumPhysicalMappedIDs;
+ } fields;
+ } u;
+} MptConfigurationPageSASIOUnit2, *PMptConfigurationPageSASIOUnit2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit2, 20);
+
+#define LSILOGICSCSI_SASIOUNIT2_STATUS_PERSISTENT_MAP_TBL_FULL RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT2_STATUS_PERSISTENT_MAP_DISABLED RT_BIT(1)
+#define LSILOGICSCSI_SASIOUNIT2_STATUS_PERSISTENT_ENC_DEV_UNMAPPED RT_BIT(2)
+#define LSILOGICSCSI_SASIOUNIT2_STATUS_PERSISTENT_DEV_LIMIT_EXCEEDED RT_BIT(3)
+
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_MAP_DISABLE RT_BIT(0)
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_SET(x) ((x & 0x7) << 1)
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_GET(x) ((x >> 1) & 0x7)
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_NO 0x0
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_DIRECT_ATTACHED 0x1
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_ENC 0x2
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_PERSISTENT_PHYS_MAP_MODE_HOST 0x7
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_RESERVE_TARGET_ID_ZERO RT_BIT(4)
+#define LSILOGICSCSI_SASIOUNIT2_FLAGS_START_SLOT_NUMBER_ONE RT_BIT(5)
+
+/**
+ * SAS I/O unit page 3 - Read/Write
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASIOUnit3
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Reserved */
+ uint32_t u32Reserved;
+ uint32_t u32MaxInvalidDwordCount;
+ uint32_t u32InvalidDwordCountTime;
+ uint32_t u32MaxRunningDisparityErrorCount;
+ uint32_t u32RunningDisparityErrorTime;
+ uint32_t u32MaxLossDwordSynchCount;
+ uint32_t u32LossDwordSynchCountTime;
+ uint32_t u32MaxPhysResetProblemCount;
+ uint32_t u32PhyResetProblemTime;
+ } fields;
+ } u;
+} MptConfigurationPageSASIOUnit3, *PMptConfigurationPageSASIOUnit3;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASIOUnit3, 44);
+
+/**
+ * SAS PHY page 0 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASPHY0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Owner dev handle. */
+ uint16_t u16OwnerDevHandle;
+ /** Reserved */
+ uint16_t u16Reserved0;
+ /** SAS address */
+ SASADDRESS SASAddress;
+ /** Attached device handle */
+ uint16_t u16AttachedDevHandle;
+ /** Attached phy identifier */
+ uint8_t u8AttachedPhyIdentifier;
+ /** Reserved */
+ uint8_t u8Reserved1;
+ /** Attached device information */
+ uint32_t u32AttachedDeviceInfo;
+ /** Programmed link rate */
+ uint8_t u8ProgrammedLinkRate;
+ /** Hardware link rate */
+ uint8_t u8HwLinkRate;
+ /** Change count */
+ uint8_t u8ChangeCount;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Phy information */
+ uint32_t u32PhyInfo;
+ } fields;
+ } u;
+} MptConfigurationPageSASPHY0, *PMptConfigurationPageSASPHY0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASPHY0, 36);
+
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_SET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_GET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_NO 0x0
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_END 0x1
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_EDGE_EXPANDER 0x2
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_TYPE_FANOUT_EXPANDER 0x3
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_SMP_INITIATOR RT_BIT(4)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_STP_INITIATOR RT_BIT(5)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_SSP_INITIATOR RT_BIT(6)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_SMP_TARGET RT_BIT(8)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_STP_TARGET RT_BIT(9)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_SSP_TARGET RT_BIT(10)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_DIRECT_ATTACHED RT_BIT(11)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_LSI RT_BIT(12)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_ATAPI RT_BIT(13)
+#define LSILOGICSCSI_SASPHY0_DEV_INFO_DEVICE_SEP RT_BIT(14)
+
+/**
+ * SAS PHY page 1 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASPHY1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Reserved */
+ uint32_t u32Reserved0;
+ uint32_t u32InvalidDwordCound;
+ uint32_t u32RunningDisparityErrorCount;
+ uint32_t u32LossDwordSynchCount;
+ uint32_t u32PhyResetProblemCount;
+ } fields;
+ } u;
+} MptConfigurationPageSASPHY1, *PMptConfigurationPageSASPHY1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASPHY1, 28);
+
+/**
+ * SAS Device page 0 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASDevice0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Slot number */
+ uint16_t u16Slot;
+ /** Enclosure handle. */
+ uint16_t u16EnclosureHandle;
+ /** SAS address */
+ SASADDRESS SASAddress;
+ /** Parent device handle */
+ uint16_t u16ParentDevHandle;
+ /** Phy number */
+ uint8_t u8PhyNum;
+ /** Access status */
+ uint8_t u8AccessStatus;
+ /** Device handle */
+ uint16_t u16DevHandle;
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus */
+ uint8_t u8Bus;
+ /** Device info */
+ uint32_t u32DeviceInfo;
+ /** Flags */
+ uint16_t u16Flags;
+ /** Physical port */
+ uint8_t u8PhysicalPort;
+ /** Reserved */
+ uint8_t u8Reserved0;
+ } fields;
+ } u;
+} MptConfigurationPageSASDevice0, *PMptConfigurationPageSASDevice0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASDevice0, 36);
+
+#define LSILOGICSCSI_SASDEVICE0_STATUS_NO_ERRORS (0x00)
+
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_SET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_GET(x) ((x) & 0x3)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_NO 0x0
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_END 0x1
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_EDGE_EXPANDER 0x2
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_TYPE_FANOUT_EXPANDER 0x3
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_SMP_INITIATOR RT_BIT(4)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_STP_INITIATOR RT_BIT(5)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_SSP_INITIATOR RT_BIT(6)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_SMP_TARGET RT_BIT(8)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_STP_TARGET RT_BIT(9)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_SSP_TARGET RT_BIT(10)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_DIRECT_ATTACHED RT_BIT(11)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_LSI RT_BIT(12)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_ATAPI RT_BIT(13)
+#define LSILOGICSCSI_SASDEVICE0_DEV_INFO_DEVICE_SEP RT_BIT(14)
+
+#define LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_PRESENT (RT_BIT(0))
+#define LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_MAPPED_TO_BUS_AND_TARGET_ID (RT_BIT(1))
+#define LSILOGICSCSI_SASDEVICE0_FLAGS_DEVICE_MAPPING_PERSISTENT (RT_BIT(2))
+
+/**
+ * SAS Device page 1 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASDevice1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Reserved */
+ uint32_t u32Reserved0;
+ /** SAS address */
+ SASADDRESS SASAddress;
+ /** Reserved */
+ uint32_t u32Reserved;
+ /** Device handle */
+ uint16_t u16DevHandle;
+ /** Target ID */
+ uint8_t u8TargetID;
+ /** Bus */
+ uint8_t u8Bus;
+ /** Initial REgister device FIS */
+ uint32_t au32InitialRegDeviceFIS[5];
+ } fields;
+ } u;
+} MptConfigurationPageSASDevice1, *PMptConfigurationPageSASDevice1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASDevice1, 48);
+
+/**
+ * SAS Device page 2 - Read/Write persistent
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASDevice2
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Physical identifier */
+ SASADDRESS SASAddress;
+ /** Enclosure mapping */
+ uint32_t u32EnclosureMapping;
+ } fields;
+ } u;
+} MptConfigurationPageSASDevice2, *PMptConfigurationPageSASDevice2;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASDevice2, 20);
+
+/**
+ * A device entitiy containing all pages.
+ */
+typedef struct MptSASDevice
+{
+ /** Pointer to the next device if any. */
+ struct MptSASDevice *pNext;
+ /** Pointer to the previous device if any. */
+ struct MptSASDevice *pPrev;
+
+ MptConfigurationPageSASDevice0 SASDevicePage0;
+ MptConfigurationPageSASDevice1 SASDevicePage1;
+ MptConfigurationPageSASDevice2 SASDevicePage2;
+} MptSASDevice, *PMptSASDevice;
+
+/**
+ * SAS Expander page 0 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASExpander0
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Physical port */
+ uint8_t u8PhysicalPort;
+ /** Reserved */
+ uint8_t u8Reserved0;
+ /** Enclosure handle */
+ uint16_t u16EnclosureHandle;
+ /** SAS address */
+ SASADDRESS SASAddress;
+ /** Discovery status */
+ uint32_t u32DiscoveryStatus;
+ /** Device handle. */
+ uint16_t u16DevHandle;
+ /** Parent device handle */
+ uint16_t u16ParentDevHandle;
+ /** Expander change count */
+ uint16_t u16ExpanderChangeCount;
+ /** Expander route indexes */
+ uint16_t u16ExpanderRouteIndexes;
+ /** Number of PHys in this expander */
+ uint8_t u8NumPhys;
+ /** SAS level */
+ uint8_t u8SASLevel;
+ /** Flags */
+ uint8_t u8Flags;
+ /** Reserved */
+ uint8_t u8Reserved1;
+ } fields;
+ } u;
+} MptConfigurationPageSASExpander0, *PMptConfigurationPageSASExpander0;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASExpander0, 36);
+
+/**
+ * SAS Expander page 1 - Readonly
+ */
+#pragma pack(1)
+typedef struct MptConfigurationPageSASExpander1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view - variable. */
+ uint8_t abPageData[1];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptExtendedConfigurationPageHeader ExtHeader;
+ /** Physical port */
+ uint8_t u8PhysicalPort;
+ /** Reserved */
+ uint8_t u8Reserved0[3];
+ /** Number of PHYs */
+ uint8_t u8NumPhys;
+ /** Number of the Phy the information in this page is for. */
+ uint8_t u8Phy;
+ /** Number of routing table entries */
+ uint16_t u16NumTableEntriesProgrammed;
+ /** Programmed link rate */
+ uint8_t u8ProgrammedLinkRate;
+ /** Hardware link rate */
+ uint8_t u8HwLinkRate;
+ /** Attached device handle */
+ uint16_t u16AttachedDevHandle;
+ /** Phy information */
+ uint32_t u32PhyInfo;
+ /** Attached device information */
+ uint32_t u32AttachedDeviceInfo;
+ /** Owner device handle. */
+ uint16_t u16OwnerDevHandle;
+ /** Change count */
+ uint8_t u8ChangeCount;
+ /** Negotiated link rate */
+ uint8_t u8NegotiatedLinkRate;
+ /** Phy identifier */
+ uint8_t u8PhyIdentifier;
+ /** Attached phy identifier */
+ uint8_t u8AttachedPhyIdentifier;
+ /** Reserved */
+ uint8_t u8Reserved1;
+ /** Discovery information */
+ uint8_t u8DiscoveryInfo;
+ /** Reserved */
+ uint32_t u32Reserved;
+ } fields;
+ } u;
+} MptConfigurationPageSASExpander1, *PMptConfigurationPageSASExpander1;
+#pragma pack()
+AssertCompileSize(MptConfigurationPageSASExpander1, 40);
+
+/**
+ * Structure of all supported pages for the SCSI SPI controller.
+ * Used to load the device state from older versions.
+ */
+typedef struct MptConfigurationPagesSupported_SSM_V2
+{
+ MptConfigurationPageManufacturing0 ManufacturingPage0;
+ MptConfigurationPageManufacturing1 ManufacturingPage1;
+ MptConfigurationPageManufacturing2 ManufacturingPage2;
+ MptConfigurationPageManufacturing3 ManufacturingPage3;
+ MptConfigurationPageManufacturing4 ManufacturingPage4;
+ MptConfigurationPageIOUnit0 IOUnitPage0;
+ MptConfigurationPageIOUnit1 IOUnitPage1;
+ MptConfigurationPageIOUnit2 IOUnitPage2;
+ MptConfigurationPageIOUnit3 IOUnitPage3;
+ MptConfigurationPageIOC0 IOCPage0;
+ MptConfigurationPageIOC1 IOCPage1;
+ MptConfigurationPageIOC2 IOCPage2;
+ MptConfigurationPageIOC3 IOCPage3;
+ MptConfigurationPageIOC4 IOCPage4;
+ MptConfigurationPageIOC6 IOCPage6;
+ struct
+ {
+ MptConfigurationPageSCSISPIPort0 SCSISPIPortPage0;
+ MptConfigurationPageSCSISPIPort1 SCSISPIPortPage1;
+ MptConfigurationPageSCSISPIPort2 SCSISPIPortPage2;
+ } aPortPages[1]; /* Currently only one port supported. */
+ struct
+ {
+ struct
+ {
+ MptConfigurationPageSCSISPIDevice0 SCSISPIDevicePage0;
+ MptConfigurationPageSCSISPIDevice1 SCSISPIDevicePage1;
+ MptConfigurationPageSCSISPIDevice2 SCSISPIDevicePage2;
+ MptConfigurationPageSCSISPIDevice3 SCSISPIDevicePage3;
+ } aDevicePages[LSILOGICSCSI_PCI_SPI_DEVICES_MAX];
+ } aBuses[1]; /* Only one bus at the moment. */
+} MptConfigurationPagesSupported_SSM_V2, *PMptConfigurationPagesSupported_SSM_V2;
+
+typedef struct MptConfigurationPagesSpi
+{
+ struct
+ {
+ MptConfigurationPageSCSISPIPort0 SCSISPIPortPage0;
+ MptConfigurationPageSCSISPIPort1 SCSISPIPortPage1;
+ MptConfigurationPageSCSISPIPort2 SCSISPIPortPage2;
+ } aPortPages[1]; /* Currently only one port supported. */
+ struct
+ {
+ struct
+ {
+ MptConfigurationPageSCSISPIDevice0 SCSISPIDevicePage0;
+ MptConfigurationPageSCSISPIDevice1 SCSISPIDevicePage1;
+ MptConfigurationPageSCSISPIDevice2 SCSISPIDevicePage2;
+ MptConfigurationPageSCSISPIDevice3 SCSISPIDevicePage3;
+ } aDevicePages[LSILOGICSCSI_PCI_SPI_DEVICES_MAX];
+ } aBuses[1]; /* Only one bus at the moment. */
+} MptConfigurationPagesSpi, *PMptConfigurationPagesSpi;
+
+typedef struct MptPHY
+{
+ MptConfigurationPageSASPHY0 SASPHYPage0;
+ MptConfigurationPageSASPHY1 SASPHYPage1;
+} MptPHY, *PMptPHY;
+
+#pragma pack(1)
+typedef struct MptConfigurationPagesSas
+{
+ /** Size of the manufacturing page 7 */
+ uint32_t cbManufacturingPage7;
+ /** Pointer to the manufacturing page 7 */
+ PMptConfigurationPageManufacturing7 pManufacturingPage7;
+ /** Size of the I/O unit page 0 */
+ uint32_t cbSASIOUnitPage0;
+ /** Pointer to the I/O unit page 0 */
+ PMptConfigurationPageSASIOUnit0 pSASIOUnitPage0;
+ /** Size of the I/O unit page 1 */
+ uint32_t cbSASIOUnitPage1;
+ /** Pointer to the I/O unit page 1 */
+ PMptConfigurationPageSASIOUnit1 pSASIOUnitPage1;
+ /** I/O unit page 2 */
+ MptConfigurationPageSASIOUnit2 SASIOUnitPage2;
+ /** I/O unit page 3 */
+ MptConfigurationPageSASIOUnit3 SASIOUnitPage3;
+
+ /** Number of PHYs in the array. */
+ uint32_t cPHYs;
+ /** Pointer to an array of per PHYS pages. */
+ R3PTRTYPE(PMptPHY) paPHYs;
+
+ /** Number of devices detected. */
+ uint32_t cDevices;
+ /** Pointer to the first SAS device. */
+ R3PTRTYPE(PMptSASDevice) pSASDeviceHead;
+ /** Pointer to the last SAS device. */
+ R3PTRTYPE(PMptSASDevice) pSASDeviceTail;
+} MptConfigurationPagesSas, *PMptConfigurationPagesSas;
+#pragma pack()
+
+/**
+ * Structure of all supported pages for both controllers.
+ */
+typedef struct MptConfigurationPagesSupported
+{
+ MptConfigurationPageManufacturing0 ManufacturingPage0;
+ MptConfigurationPageManufacturing1 ManufacturingPage1;
+ MptConfigurationPageManufacturing2 ManufacturingPage2;
+ MptConfigurationPageManufacturing3 ManufacturingPage3;
+ MptConfigurationPageManufacturing4 ManufacturingPage4;
+ MptConfigurationPageManufacturing5 ManufacturingPage5;
+ MptConfigurationPageManufacturing6 ManufacturingPage6;
+ MptConfigurationPageManufacturing8 ManufacturingPage8;
+ MptConfigurationPageManufacturing9 ManufacturingPage9;
+ MptConfigurationPageManufacturing10 ManufacturingPage10;
+ MptConfigurationPageIOUnit0 IOUnitPage0;
+ MptConfigurationPageIOUnit1 IOUnitPage1;
+ MptConfigurationPageIOUnit2 IOUnitPage2;
+ MptConfigurationPageIOUnit3 IOUnitPage3;
+ MptConfigurationPageIOUnit4 IOUnitPage4;
+ MptConfigurationPageIOC0 IOCPage0;
+ MptConfigurationPageIOC1 IOCPage1;
+ MptConfigurationPageIOC2 IOCPage2;
+ MptConfigurationPageIOC3 IOCPage3;
+ MptConfigurationPageIOC4 IOCPage4;
+ MptConfigurationPageIOC6 IOCPage6;
+ /* BIOS page 0 is not described */
+ MptConfigurationPageBIOS1 BIOSPage1;
+ MptConfigurationPageBIOS2 BIOSPage2;
+ /* BIOS page 3 is not described */
+ MptConfigurationPageBIOS4 BIOSPage4;
+
+ /** Controller dependent data. */
+ union
+ {
+ MptConfigurationPagesSpi SpiPages;
+ MptConfigurationPagesSas SasPages;
+ } u;
+} MptConfigurationPagesSupported, *PMptConfigurationPagesSupported;
+
+/**
+ * Initializes a page header.
+ */
+#define MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags) \
+ (pg)->u.fields.Header.u8PageType = flags; \
+ (pg)->u.fields.Header.u8PageNumber = nr; \
+ (pg)->u.fields.Header.u8PageLength = sizeof(type) / 4
+
+#define MPT_CONFIG_PAGE_HEADER_INIT_MANUFACTURING(pg, type, nr, flags) \
+ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING)
+
+#define MPT_CONFIG_PAGE_HEADER_INIT_IO_UNIT(pg, type, nr, flags) \
+ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_IO_UNIT)
+
+#define MPT_CONFIG_PAGE_HEADER_INIT_IOC(pg, type, nr, flags) \
+ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_IOC)
+
+#define MPT_CONFIG_PAGE_HEADER_INIT_BIOS(pg, type, nr, flags) \
+ MPT_CONFIG_PAGE_HEADER_INIT(pg, type, nr, flags | MPT_CONFIGURATION_PAGE_TYPE_BIOS)
+
+/**
+ * Initializes a extended page header.
+ */
+#define MPT_CONFIG_EXTENDED_PAGE_HEADER_INIT(pg, cb, nr, flags, exttype) \
+ (pg)->u.fields.ExtHeader.u8PageType = flags | MPT_CONFIGURATION_PAGE_TYPE_EXTENDED; \
+ (pg)->u.fields.ExtHeader.u8PageNumber = nr; \
+ (pg)->u.fields.ExtHeader.u8ExtPageType = exttype; \
+ (pg)->u.fields.ExtHeader.u16ExtPageLength = cb / 4
+
+/**
+ * Possible SG element types.
+ */
+enum MPTSGENTRYTYPE
+{
+ MPTSGENTRYTYPE_TRANSACTION_CONTEXT = 0x00,
+ MPTSGENTRYTYPE_SIMPLE = 0x01,
+ MPTSGENTRYTYPE_CHAIN = 0x03
+};
+
+/**
+ * Register interface.
+ */
+
+/**
+ * Defined states that the SCSI controller can have.
+ */
+typedef enum LSILOGICSTATE
+{
+ /** Reset state. */
+ LSILOGICSTATE_RESET = 0x00,
+ /** Ready state. */
+ LSILOGICSTATE_READY = 0x01,
+ /** Operational state. */
+ LSILOGICSTATE_OPERATIONAL = 0x02,
+ /** Fault state. */
+ LSILOGICSTATE_FAULT = 0x04,
+ /** 32bit size hack */
+ LSILOGICSTATE_32BIT_HACK = 0x7fffffff
+} LSILOGICSTATE;
+
+/**
+ * Which entity needs to initialize the controller
+ * to get into the operational state.
+ */
+typedef enum LSILOGICWHOINIT
+{
+ /** Not initialized. */
+ LSILOGICWHOINIT_NOT_INITIALIZED = 0x00,
+ /** System BIOS. */
+ LSILOGICWHOINIT_SYSTEM_BIOS = 0x01,
+ /** ROM Bios. */
+ LSILOGICWHOINIT_ROM_BIOS = 0x02,
+ /** PCI Peer. */
+ LSILOGICWHOINIT_PCI_PEER = 0x03,
+ /** Host driver. */
+ LSILOGICWHOINIT_HOST_DRIVER = 0x04,
+ /** Manufacturing. */
+ LSILOGICWHOINIT_MANUFACTURING = 0x05,
+ /** 32bit size hack. */
+ LSILOGICWHOINIT_32BIT_HACK = 0x7fffffff
+} LSILOGICWHOINIT;
+
+
+/**
+ * Doorbell state.
+ */
+typedef enum LSILOGICDOORBELLSTATE
+{
+ /** Invalid value. */
+ LSILOGICDOORBELLSTATE_INVALID = 0,
+ /** Doorbell not in use. */
+ LSILOGICDOORBELLSTATE_NOT_IN_USE,
+ /** Reply frame removal, transfer number of entries, low 16bits. */
+ LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_LOW,
+ /** Reply frame removal, transfer number of entries, high 16bits. */
+ LSILOGICDOORBELLSTATE_RFR_FRAME_COUNT_HIGH,
+ /** Reply frame removal, remove next free frame, low part. */
+ LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW,
+ /** Reply frame removal, remove next free frame, high part. */
+ LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_HIGH,
+ /** Function handshake. */
+ LSILOGICDOORBELLSTATE_FN_HANDSHAKE,
+ /** 32bit hack. */
+ LSILOGICDOORBELLSTATE_32BIT_HACK = 0x7fffffff
+} LSILOGICDOORBELLSTATE;
+/** Pointer to a doorbell state. */
+typedef LSILOGICDOORBELLSTATE *PLSILOGICDOORBELLSTATE;
+
+
+/**
+ * IOC status codes.
+ */
+#define LSILOGIC_IOCSTATUS_SUCCESS 0x0000
+#define LSILOGIC_IOCSTATUS_INVALID_FUNCTION 0x0001
+#define LSILOGIC_IOCSTATUS_BUSY 0x0002
+#define LSILOGIC_IOCSTATUS_INVALID_SGL 0x0003
+#define LSILOGIC_IOCSTATUS_INTERNAL_ERROR 0x0004
+#define LSILOGIC_IOCSTATUS_RESERVED 0x0005
+#define LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES 0x0006
+#define LSILOGIC_IOCSTATUS_INVALID_FIELD 0x0007
+#define LSILOGIC_IOCSTATUS_INVALID_STATE 0x0008
+#define LSILOGIC_IOCSTATUS_OP_STATE_NOT_SUPPOTED 0x0009
+
+/**
+ * Size of the I/O and MMIO space.
+ */
+#define LSILOGIC_PCI_SPACE_IO_SIZE 256
+#define LSILOGIC_PCI_SPACE_MEM_SIZE 128 * _1K
+
+/**
+ * Doorbell register - Used to get the status of the controller and
+ * initialise it.
+ */
+#define LSILOGIC_REG_DOORBELL 0x00
+# define LSILOGIC_REG_DOORBELL_SET_STATE(enmState) (((enmState) & 0x0f) << 28)
+# define LSILOGIC_REG_DOORBELL_SET_USED(enmDoorbell) (((enmDoorbell != LSILOGICDOORBELLSTATE_NOT_IN_USE) ? 1 : 0) << 27)
+# define LSILOGIC_REG_DOORBELL_SET_WHOINIT(enmWhoInit) (((enmWhoInit) & 0x07) << 24)
+# define LSILOGIC_REG_DOORBELL_SET_FAULT_CODE(u16Code) (u16Code)
+# define LSILOGIC_REG_DOORBELL_GET_FUNCTION(x) (((x) & 0xff000000) >> 24)
+# define LSILOGIC_REG_DOORBELL_GET_SIZE(x) (((x) & 0x00ff0000) >> 16)
+
+/**
+ * Functions which can be passed through the system doorbell.
+ */
+#define LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET 0x40
+#define LSILOGIC_DOORBELL_FUNCTION_IO_UNIT_RESET 0x41
+#define LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE 0x42
+#define LSILOGIC_DOORBELL_FUNCTION_REPLY_FRAME_REMOVAL 0x43
+
+/**
+ * Write sequence register for the diagnostic register.
+ */
+#define LSILOGIC_REG_WRITE_SEQUENCE 0x04
+
+/**
+ * Diagnostic register - used to reset the controller.
+ */
+#define LSILOGIC_REG_HOST_DIAGNOSTIC 0x08
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_MEM_ENABLE (RT_BIT(0))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_DISABLE_ARM (RT_BIT(1))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_ADAPTER (RT_BIT(2))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_RW_ENABLE (RT_BIT(4))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_HISTORY (RT_BIT(5))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_FLASH_BAD_SIG (RT_BIT(6))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_DRWE (RT_BIT(7))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_PREVENT_IOC_BOOT (RT_BIT(9))
+# define LSILOGIC_REG_HOST_DIAGNOSTIC_CLEAR_FLASH_BAD_SIG (RT_BIT(10))
+
+#define LSILOGIC_REG_TEST_BASE_ADDRESS 0x0c
+#define LSILOGIC_REG_DIAG_RW_DATA 0x10
+#define LSILOGIC_REG_DIAG_RW_ADDRESS 0x14
+
+/**
+ * Interrupt status register.
+ */
+#define LSILOGIC_REG_HOST_INTR_STATUS 0x30
+# define LSILOGIC_REG_HOST_INTR_STATUS_W_MASK (RT_BIT(3))
+# define LSILOGIC_REG_HOST_INTR_STATUS_DOORBELL_STS (RT_BIT(31))
+# define LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR (RT_BIT(3))
+# define LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL (RT_BIT(0))
+
+/**
+ * Interrupt mask register.
+ */
+#define LSILOGIC_REG_HOST_INTR_MASK 0x34
+# define LSILOGIC_REG_HOST_INTR_MASK_W_MASK (RT_BIT(0) | RT_BIT(3) | RT_BIT(8) | RT_BIT(9))
+# define LSILOGIC_REG_HOST_INTR_MASK_IRQ_ROUTING (RT_BIT(8) | RT_BIT(9))
+# define LSILOGIC_REG_HOST_INTR_MASK_DOORBELL RT_BIT(0)
+# define LSILOGIC_REG_HOST_INTR_MASK_REPLY RT_BIT(3)
+
+/**
+ * Queue registers.
+ */
+#define LSILOGIC_REG_REQUEST_QUEUE 0x40
+#define LSILOGIC_REG_REPLY_QUEUE 0x44
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_DevLsiLogicSCSI_h */
diff --git a/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp b/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp
new file mode 100644
index 00000000..17ffa81d
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp
@@ -0,0 +1,2115 @@
+/* $Id: DrvDiskIntegrity.cpp $ */
+/** @file
+ * VBox storage devices: Disk integrity check.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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/mem.h>
+#include <iprt/memcache.h>
+#include <iprt/message.h>
+#include <iprt/sg.h>
+#include <iprt/time.h>
+#include <iprt/tracelog.h>
+#include <iprt/semaphore.h>
+#include <iprt/asm.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Transfer direction.
+ */
+typedef enum DRVDISKAIOTXDIR
+{
+ /** Invalid. */
+ DRVDISKAIOTXDIR_INVALID = 0,
+ /** Read */
+ DRVDISKAIOTXDIR_READ,
+ /** Write */
+ DRVDISKAIOTXDIR_WRITE,
+ /** Flush */
+ DRVDISKAIOTXDIR_FLUSH,
+ /** Discard */
+ DRVDISKAIOTXDIR_DISCARD,
+ /** Read after write for immediate verification. */
+ DRVDISKAIOTXDIR_READ_AFTER_WRITE
+} DRVDISKAIOTXDIR;
+
+/**
+ * async I/O request.
+ */
+typedef struct DRVDISKAIOREQ
+{
+ /** Transfer direction. */
+ DRVDISKAIOTXDIR enmTxDir;
+ /** Start offset. */
+ uint64_t off;
+ /** Transfer size. */
+ size_t cbTransfer;
+ /** Segment array. */
+ PCRTSGSEG paSeg;
+ /** Number of array entries. */
+ unsigned cSeg;
+ /** User argument */
+ void *pvUser;
+ /** Slot in the array. */
+ unsigned iSlot;
+ /** Start timestamp */
+ uint64_t tsStart;
+ /** Completion timestamp. */
+ uint64_t tsComplete;
+ /** Ranges to discard. */
+ PCRTRANGE paRanges;
+ /** Number of ranges. */
+ unsigned cRanges;
+ /** I/O segment for the extended media interface
+ * to hold the data. */
+ RTSGSEG IoSeg;
+} DRVDISKAIOREQ, *PDRVDISKAIOREQ;
+
+/**
+ * I/O log entry.
+ */
+typedef struct IOLOGENT
+{
+ /** Start offset */
+ uint64_t off;
+ /** Write size */
+ size_t cbWrite;
+ /** Number of references to this entry. */
+ unsigned cRefs;
+} IOLOGENT, *PIOLOGENT;
+
+/**
+ * Disk segment.
+ */
+typedef struct DRVDISKSEGMENT
+{
+ /** AVL core. */
+ AVLRFOFFNODECORE Core;
+ /** Size of the segment */
+ size_t cbSeg;
+ /** Data for this segment */
+ uint8_t *pbSeg;
+ /** Number of entries in the I/O array. */
+ unsigned cIoLogEntries;
+ /** Array of I/O log references. */
+ PIOLOGENT apIoLog[1];
+} DRVDISKSEGMENT, *PDRVDISKSEGMENT;
+
+/**
+ * Active requests list entry.
+ */
+typedef struct DRVDISKAIOREQACTIVE
+{
+ /** Pointer to the request. */
+ volatile PDRVDISKAIOREQ pIoReq;
+ /** Start timestamp. */
+ uint64_t tsStart;
+} DRVDISKAIOREQACTIVE, *PDRVDISKAIOREQACTIVE;
+
+/**
+ * Disk integrity driver instance data.
+ *
+ * @implements PDMIMEDIA
+ * @implements PDMIMEDIAPORT
+ * @implements PDMIMEDIAEX
+ * @implements PDMIMEDIAEXPORT
+ * @implements PDMIMEDIAMOUNT
+ * @implements PDMIMEDIAMOUNTNOTIFY
+ */
+typedef struct DRVDISKINTEGRITY
+{
+ /** 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;
+
+ /** The extended media port interface above. */
+ PPDMIMEDIAEXPORT pDrvMediaExPort;
+ /** Our extended media port interface */
+ PDMIMEDIAEXPORT IMediaExPort;
+
+ /** The extended media interface below. */
+ PPDMIMEDIAEX pDrvMediaEx;
+ /** Our extended media interface */
+ PDMIMEDIAEX IMediaEx;
+
+ /** The mount interface below. */
+ PPDMIMOUNT pDrvMount;
+ /** Our mount interface */
+ PDMIMOUNT IMount;
+
+ /** The mount notify interface above. */
+ PPDMIMOUNTNOTIFY pDrvMountNotify;
+ /** Our mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+
+ /** Flag whether consistency checks are enabled. */
+ bool fCheckConsistency;
+ /** Flag whether the RAM disk was prepopulated. */
+ bool fPrepopulateRamDisk;
+ /** AVL tree containing the disk blocks to check. */
+ PAVLRFOFFTREE pTreeSegments;
+
+ /** Flag whether async request tracing is enabled. */
+ bool fTraceRequests;
+ /** Interval the thread should check for expired requests (milliseconds). */
+ uint32_t uCheckIntervalMs;
+ /** Expire timeout for a request (milliseconds). */
+ uint32_t uExpireIntervalMs;
+ /** Thread which checks for lost requests. */
+ RTTHREAD hThread;
+ /** Event semaphore */
+ RTSEMEVENT SemEvent;
+ /** Flag whether the thread should run. */
+ bool fRunning;
+ /** Array containing active requests. */
+ DRVDISKAIOREQACTIVE apReqActive[128];
+ /** Next free slot in the array */
+ volatile unsigned iNextFreeSlot;
+ /** Request cache. */
+ RTMEMCACHE hReqCache;
+
+ /** Flag whether we check for requests completing twice. */
+ bool fCheckDoubleCompletion;
+ /** Number of requests we go back. */
+ unsigned cEntries;
+ /** Array of completed but still observed requests. */
+ PDRVDISKAIOREQ *papIoReq;
+ /** Current entry in the array. */
+ unsigned iEntry;
+
+ /** Flag whether to do a immediate read after write for verification. */
+ bool fReadAfterWrite;
+ /** Flag whether to record the data to write before the write completed successfully.
+ * Useful in case the data is modified in place later on (encryption for instance). */
+ bool fRecordWriteBeforeCompletion;
+ /** Flag whether to validate memory buffers when the extended media interface is used. */
+ bool fValidateMemBufs;
+
+ /** I/O logger to use if enabled. */
+ RTTRACELOGWR hIoLogger;
+ /** Size of the opaque handle until our tracking structure starts in bytes. */
+ size_t cbIoReqOpaque;
+} DRVDISKINTEGRITY, *PDRVDISKINTEGRITY;
+
+
+/**
+ * Read/Write event items.
+ */
+static const RTTRACELOGEVTITEMDESC g_aEvtItemsReadWrite[] =
+{
+ { "Async", "Flag whether the request is asynchronous", RTTRACELOGTYPE_BOOL, 0 },
+ { "Offset", "Offset to start reading/writing from/to", RTTRACELOGTYPE_UINT64, 0 },
+ { "Size", "Number of bytes to transfer", RTTRACELOGTYPE_SIZE, 0 }
+};
+
+/**
+ * Flush event items.
+ */
+static const RTTRACELOGEVTITEMDESC g_aEvtItemsFlush[] =
+{
+ { "Async", "Flag whether the request is asynchronous", RTTRACELOGTYPE_BOOL, 0 }
+};
+
+/**
+ * I/O request complete items.
+ */
+static const RTTRACELOGEVTITEMDESC g_aEvtItemsComplete[] =
+{
+ { "Status", "Status code the request completed with", RTTRACELOGTYPE_INT32, 0 }
+};
+
+/** Read event descriptor. */
+static const RTTRACELOGEVTDESC g_EvtRead =
+ { "Read", "Read data from disk", RTTRACELOGEVTSEVERITY_DEBUG, RT_ELEMENTS(g_aEvtItemsReadWrite), &g_aEvtItemsReadWrite[0] };
+/** Write event descriptor. */
+static const RTTRACELOGEVTDESC g_EvtWrite =
+ { "Write", "Write data to disk", RTTRACELOGEVTSEVERITY_DEBUG, RT_ELEMENTS(g_aEvtItemsReadWrite), &g_aEvtItemsReadWrite[0] };
+/** Flush event descriptor. */
+static const RTTRACELOGEVTDESC g_EvtFlush =
+ { "Flush", "Flush written data to disk", RTTRACELOGEVTSEVERITY_DEBUG, RT_ELEMENTS(g_aEvtItemsFlush), &g_aEvtItemsFlush[0] };
+/** I/O request complete event descriptor. */
+static const RTTRACELOGEVTDESC g_EvtComplete =
+ { "Complete", "A previously started I/O request completed", RTTRACELOGEVTSEVERITY_DEBUG,
+ RT_ELEMENTS(g_aEvtItemsComplete), &g_aEvtItemsComplete[0]};
+
+#define DISKINTEGRITY_IOREQ_HANDLE_2_DRVDISKAIOREQ(a_pThis, a_hIoReq) ((*(PDRVDISKAIOREQ *)((uintptr_t)(a_hIoReq) + (a_pThis)->cbIoReqOpaque)))
+#define DISKINTEGRITY_IOREQ_HANDLE_2_UPPER_OPAQUE(a_pThis, a_hIoReq) ((void *)((uintptr_t)(a_hIoReq) + (a_pThis)->cbIoReqOpaque + sizeof(PDRVDISKAIOREQ)))
+#define DISKINTEGRITY_IOREQ_ALLOC_2_DRVDISKAIOREQ(a_pvIoReqAlloc) (*(PDRVDISKAIOREQ *)(a_pvIoReqAlloc))
+#define DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(a_pvIoReqAlloc) ((void *)((uintptr_t)(a_pvIoReqAlloc) + sizeof(PDRVDISKAIOREQ)))
+
+static void drvdiskintIoReqCheckForDoubleCompletion(PDRVDISKINTEGRITY pThis, PDRVDISKAIOREQ pIoReq,
+ bool fMediaEx)
+{
+ /* Search if the I/O request completed already. */
+ for (unsigned i = 0; i < pThis->cEntries; i++)
+ {
+ if (RT_UNLIKELY(pThis->papIoReq[i] == pIoReq))
+ {
+ RTMsgError("Request %#p completed already!\n", pIoReq);
+ if (!fMediaEx)
+ RTMsgError("Start timestamp %llu Completion timestamp %llu (completed after %llu ms)\n",
+ pIoReq->tsStart, pIoReq->tsComplete, pIoReq->tsComplete - pIoReq->tsStart);
+ RTAssertDebugBreak();
+ }
+ }
+
+ pIoReq->tsComplete = RTTimeSystemMilliTS();
+ Assert(!pThis->papIoReq[pThis->iEntry]);
+ pThis->papIoReq[pThis->iEntry] = pIoReq;
+
+ pThis->iEntry = (pThis->iEntry+1) % pThis->cEntries;
+ if (pThis->papIoReq[pThis->iEntry])
+ {
+ if (!fMediaEx)
+ RTMemFree(pThis->papIoReq[pThis->iEntry]);
+ pThis->papIoReq[pThis->iEntry] = NULL;
+ }
+}
+
+static void drvdiskintIoLogEntryRelease(PIOLOGENT pIoLogEnt)
+{
+ pIoLogEnt->cRefs--;
+ if (!pIoLogEnt->cRefs)
+ RTMemFree(pIoLogEnt);
+}
+
+/**
+ * Record a successful write to the virtual disk.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param paSeg Segment array of the write to record.
+ * @param cSeg Number of segments.
+ * @param off Start offset.
+ * @param cbWrite Number of bytes to record.
+ */
+static int drvdiskintWriteRecord(PDRVDISKINTEGRITY pThis, PCRTSGSEG paSeg, unsigned cSeg,
+ uint64_t off, size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p paSeg=%#p cSeg=%u off=%llx cbWrite=%u\n",
+ pThis, paSeg, cSeg, off, cbWrite));
+
+ /* Update the segments */
+ size_t cbLeft = cbWrite;
+ RTFOFF offCurr = (RTFOFF)off;
+ RTSGBUF SgBuf;
+ PIOLOGENT pIoLogEnt = (PIOLOGENT)RTMemAllocZ(sizeof(IOLOGENT));
+ if (!pIoLogEnt)
+ return VERR_NO_MEMORY;
+
+ pIoLogEnt->off = off;
+ pIoLogEnt->cbWrite = cbWrite;
+ pIoLogEnt->cRefs = 0;
+
+ RTSgBufInit(&SgBuf, paSeg, cSeg);
+
+ 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(RT_UOFFSETOF_DYN(DRVDISKSEGMENT, apIoLog[cbRange / 512]));
+ if (pSeg)
+ {
+ pSeg->Core.Key = offCurr;
+ pSeg->Core.KeyLast = offCurr + (RTFOFF)cbRange - 1;
+ pSeg->cbSeg = cbRange;
+ pSeg->pbSeg = (uint8_t *)RTMemAllocZ(cbRange);
+ pSeg->cIoLogEntries = (uint32_t)cbRange / 512;
+ 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(&SgBuf, pSeg->pbSeg + offSeg, cbRange);
+ Assert(cbCopied == cbRange); RT_NOREF(cbCopied);
+
+ /* Update the I/O log pointers */
+ Assert(offSeg % 512 == 0);
+ Assert(cbRange % 512 == 0);
+ while (offSeg < cbRange)
+ {
+ uint32_t uSector = offSeg / 512;
+ PIOLOGENT pIoLogOld = NULL;
+
+ AssertMsg(uSector < pSeg->cIoLogEntries, ("Internal bug!\n"));
+
+ pIoLogOld = pSeg->apIoLog[uSector];
+ if (pIoLogOld)
+ {
+ pIoLogOld->cRefs--;
+ if (!pIoLogOld->cRefs)
+ RTMemFree(pIoLogOld);
+ }
+
+ pSeg->apIoLog[uSector] = pIoLogEnt;
+ pIoLogEnt->cRefs++;
+
+ offSeg += 512;
+ }
+ }
+ else
+ RTSgBufAdvance(&SgBuf, cbRange);
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies a read request.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param paSeg Segment array of the containing the data buffers to verify.
+ * @param cSeg Number of segments.
+ * @param off Start offset.
+ * @param cbRead Number of bytes to verify.
+ */
+static int drvdiskintReadVerify(PDRVDISKINTEGRITY pThis, PCRTSGSEG paSeg, unsigned cSeg,
+ uint64_t off, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p paSeg=%#p cSeg=%u off=%llx cbRead=%u\n",
+ pThis, paSeg, cSeg, off, cbRead));
+
+ Assert(off % 512 == 0);
+ Assert(cbRead % 512 == 0);
+
+ /* Compare read data */
+ size_t cbLeft = cbRead;
+ RTFOFF offCurr = (RTFOFF)off;
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, paSeg, cSeg);
+
+ 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)
+ {
+ /* No data in the tree for this read. Assume everything is ok. */
+ cbRange = cbLeft;
+ }
+ else if (offCurr + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ if (pThis->fPrepopulateRamDisk)
+ {
+ /* No segment means everything should be 0 for this part. */
+ if (!RTSgBufIsZero(&SgBuf, cbRange))
+ {
+ RTMsgError("Corrupted disk at offset %llu (expected everything to be 0)!\n",
+ offCurr);
+ RTAssertDebugBreak();
+ }
+ }
+ }
+ else
+ {
+ fCmp = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (fCmp)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBufCmp;
+ size_t cbOff = 0;
+
+ Seg.cbSeg = cbRange;
+ Seg.pvSeg = pSeg->pbSeg + offSeg;
+
+ RTSgBufInit(&SgBufCmp, &Seg, 1);
+ if (RTSgBufCmpEx(&SgBuf, &SgBufCmp, cbRange, &cbOff, true))
+ {
+ /* Corrupted disk, print I/O log entry of the last write which accessed this range. */
+ uint32_t cSector = (offSeg + (uint32_t)cbOff) / 512;
+ AssertMsg(cSector < pSeg->cIoLogEntries, ("Internal bug!\n"));
+
+ RTMsgError("Corrupted disk at offset %llu (%u bytes in the current read buffer)!\n",
+ offCurr + cbOff, cbOff);
+ RTMsgError("Last write to this sector started at offset %llu with %u bytes (%u references to this log entry)\n",
+ pSeg->apIoLog[cSector]->off,
+ pSeg->apIoLog[cSector]->cbWrite,
+ pSeg->apIoLog[cSector]->cRefs);
+ RTAssertDebugBreak();
+ }
+ }
+ else
+ RTSgBufAdvance(&SgBuf, 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 drvdiskintDiscardRecords(PDRVDISKINTEGRITY 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);
+ for (unsigned idx = 0; idx < pSeg->cIoLogEntries; idx++)
+ drvdiskintIoLogEntryRelease(pSeg->apIoLog[idx]);
+ 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);
+ for (unsigned idx = (uint32_t)(cbPreLeft / 512); idx < pSeg->cIoLogEntries; idx++)
+ drvdiskintIoLogEntryRelease(pSeg->apIoLog[idx]);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, RT_UOFFSETOF_DYN(DRVDISKSEGMENT, apIoLog[cbPreLeft / 512]));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ pSeg->cIoLogEntries = (uint32_t)(cbPreLeft / 512);
+ 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);
+ for (unsigned idx = 0; idx < cbRange / 512; idx++)
+ drvdiskintIoLogEntryRelease(pSeg->apIoLog[idx]);
+ for (unsigned idx = 0; idx < cbPostLeft /512; idx++)
+ pSeg->apIoLog[idx] = pSeg->apIoLog[(cbRange / 512) + idx];
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, RT_UOFFSETOF_DYN(DRVDISKSEGMENT, apIoLog[cbPostLeft / 512]));
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPostLeft);
+ pSeg->Core.Key += cbRange;
+ pSeg->cbSeg = cbPostLeft;
+ pSeg->cIoLogEntries = (uint32_t)(cbPostLeft / 512);
+ 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(RT_UOFFSETOF_DYN(DRVDISKSEGMENT, apIoLog[cbPostLeft / 512]));
+ 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);
+ pSegPost->cIoLogEntries = (uint32_t)(cbPostLeft / 512);
+ if (!pSegPost->pbSeg)
+ RTMemFree(pSegPost);
+ else
+ {
+ memcpy(pSegPost->pbSeg, pSeg->pbSeg + cbPreLeft + cbRange, cbPostLeft);
+ for (unsigned idx = 0; idx < (uint32_t)(cbPostLeft / 512); idx++)
+ pSegPost->apIoLog[idx] = pSeg->apIoLog[((cbPreLeft + cbRange) / 512) + idx];
+
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSegPost->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ }
+
+ /* Shrink the current segment. */
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPreLeft);
+ for (unsigned idx = (uint32_t)(cbPreLeft / 512); idx < (uint32_t)((cbPreLeft + cbRange) / 512); idx++)
+ drvdiskintIoLogEntryRelease(pSeg->apIoLog[idx]);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, RT_UOFFSETOF_DYN(DRVDISKSEGMENT, apIoLog[cbPreLeft / 512]));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ pSeg->cIoLogEntries = (uint32_t)(cbPreLeft / 512);
+ 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;
+}
+
+/**
+ * Adds a request to the active list.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param pIoReq The request to add.
+ */
+static void drvdiskintIoReqAdd(PDRVDISKINTEGRITY pThis, PDRVDISKAIOREQ pIoReq)
+{
+ PDRVDISKAIOREQACTIVE pReqActive = &pThis->apReqActive[pThis->iNextFreeSlot];
+
+ Assert(!pReqActive->pIoReq);
+ pReqActive->tsStart = pIoReq->tsStart;
+ pReqActive->pIoReq = pIoReq;
+ pIoReq->iSlot = pThis->iNextFreeSlot;
+
+ /* Search for the next one. */
+ while (pThis->apReqActive[pThis->iNextFreeSlot].pIoReq)
+ pThis->iNextFreeSlot = (pThis->iNextFreeSlot+1) % RT_ELEMENTS(pThis->apReqActive);
+}
+
+/**
+ * Removes a request from the active list.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param pIoReq The request to remove.
+ */
+static void drvdiskintIoReqRemove(PDRVDISKINTEGRITY pThis, PDRVDISKAIOREQ pIoReq)
+{
+ PDRVDISKAIOREQACTIVE pReqActive = &pThis->apReqActive[pIoReq->iSlot];
+
+ Assert(pReqActive->pIoReq == pIoReq);
+
+ ASMAtomicWriteNullPtr(&pReqActive->pIoReq);
+}
+
+/**
+ * Thread checking for expired requests.
+ *
+ * @returns IPRT status code.
+ * @param pThread Thread handle.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) drvdiskIntIoReqExpiredCheck(RTTHREAD pThread, void *pvUser)
+{
+ PDRVDISKINTEGRITY pThis = (PDRVDISKINTEGRITY)pvUser;
+
+ RT_NOREF(pThread);
+
+ while (pThis->fRunning)
+ {
+ int rc = RTSemEventWait(pThis->SemEvent, pThis->uCheckIntervalMs);
+
+ if (!pThis->fRunning)
+ break;
+
+ Assert(rc == VERR_TIMEOUT); RT_NOREF(rc);
+
+ /* Get current timestamp for comparison. */
+ uint64_t tsCurr = RTTimeSystemMilliTS();
+
+ /* Go through the array and check for expired requests. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apReqActive); i++)
+ {
+ PDRVDISKAIOREQACTIVE pReqActive = &pThis->apReqActive[i];
+ PDRVDISKAIOREQ pIoReq = ASMAtomicReadPtrT(&pReqActive->pIoReq, PDRVDISKAIOREQ);
+
+ if ( pIoReq
+ && (tsCurr > pReqActive->tsStart)
+ && (tsCurr - pReqActive->tsStart) >= pThis->uExpireIntervalMs)
+ {
+ RTMsgError("Request %#p expired (active for %llu ms already)\n",
+ pIoReq, tsCurr - pReqActive->tsStart);
+ RTAssertDebugBreak();
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Verify a completed read after write request.
+ *
+ * @returns VBox status code.
+ * @param pThis The driver instance data.
+ * @param pIoReq The request to be verified.
+ */
+static int drvdiskintReadAfterWriteVerify(PDRVDISKINTEGRITY pThis, PDRVDISKAIOREQ pIoReq)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->fCheckConsistency)
+ rc = drvdiskintReadVerify(pThis, pIoReq->paSeg, pIoReq->cSeg, pIoReq->off, pIoReq->cbTransfer);
+ else /** @todo Implement read after write verification without a memory based image of the disk. */
+ AssertMsgFailed(("TODO\n"));
+
+ return rc;
+}
+
+
+/**
+ * Fires a read event if enabled.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param uGrp The group ID.
+ * @param fAsync Flag whether this is an async request.
+ * @param off The offset to put into the event log.
+ * @param cbRead Amount of bytes to read.
+ */
+DECLINLINE(void) drvdiskintTraceLogFireEvtRead(PDRVDISKINTEGRITY pThis, uintptr_t uGrp, bool fAsync, uint64_t off, size_t cbRead)
+{
+ if (pThis->hIoLogger)
+ {
+ int rc = RTTraceLogWrEvtAddL(pThis->hIoLogger, &g_EvtRead, RTTRACELOG_WR_ADD_EVT_F_GRP_START,
+ (RTTRACELOGEVTGRPID)uGrp, 0, fAsync, off, cbRead);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Fires a write event if enabled.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param uGrp The group ID.
+ * @param fAsync Flag whether this is an async request.
+ * @param off The offset to put into the event log.
+ * @param cbWrite Amount of bytes to write.
+ */
+DECLINLINE(void) drvdiskintTraceLogFireEvtWrite(PDRVDISKINTEGRITY pThis, uintptr_t uGrp, bool fAsync, uint64_t off, size_t cbWrite)
+{
+ if (pThis->hIoLogger)
+ {
+ int rc = RTTraceLogWrEvtAddL(pThis->hIoLogger, &g_EvtWrite, RTTRACELOG_WR_ADD_EVT_F_GRP_START,
+ (RTTRACELOGEVTGRPID)uGrp, 0, fAsync, off, cbWrite);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Fires a flush event if enabled.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param uGrp The group ID.
+ * @param fAsync Flag whether this is an async request.
+ */
+DECLINLINE(void) drvdiskintTraceLogFireEvtFlush(PDRVDISKINTEGRITY pThis, uintptr_t uGrp, bool fAsync)
+{
+ if (pThis->hIoLogger)
+ {
+ int rc = RTTraceLogWrEvtAddL(pThis->hIoLogger, &g_EvtFlush, RTTRACELOG_WR_ADD_EVT_F_GRP_START,
+ (RTTRACELOGEVTGRPID)uGrp, 0, fAsync);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Fires a request complete event if enabled.
+ *
+ * @returns nothing.
+ * @param pThis The driver instance data.
+ * @param uGrp The group ID.
+ * @param rcReq Status code the request completed with.
+ * @param pSgBuf The S/G buffer holding the data.
+ */
+DECLINLINE(void) drvdiskintTraceLogFireEvtComplete(PDRVDISKINTEGRITY pThis, uintptr_t uGrp, int rcReq, PRTSGBUF pSgBuf)
+{
+ RT_NOREF(pSgBuf);
+
+ if (pThis->hIoLogger)
+ {
+ int rc = RTTraceLogWrEvtAddL(pThis->hIoLogger, &g_EvtComplete, RTTRACELOG_WR_ADD_EVT_F_GRP_FINISH,
+ (RTTRACELOGEVTGRPID)uGrp, 0, rcReq);
+ AssertRC(rc);
+ }
+}
+
+
+/* -=-=-=-=- IMedia -=-=-=-=- */
+
+/** Makes a PDRVDISKINTEGRITY out of a PPDMIMEDIA. */
+#define PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface) ( (PDRVDISKINTEGRITY)((uintptr_t)pInterface - RT_UOFFSETOF(DRVDISKINTEGRITY, IMedia)) )
+
+
+/*********************************************************************************************************************************
+* Media interface methods *
+*********************************************************************************************************************************/
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnRead} */
+static DECLCALLBACK(int) drvdiskintRead(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+
+ drvdiskintTraceLogFireEvtRead(pThis, (uintptr_t)pvBuf, false /* fAsync */, off, cbRead);
+ rc = pThis->pDrvMedia->pfnRead(pThis->pDrvMedia, off, pvBuf, cbRead);
+
+ if (pThis->hIoLogger)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.pvSeg = pvBuf;
+ Seg.cbSeg = cbRead;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)pvBuf, rc, &SgBuf);
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fCheckConsistency)
+ {
+ /* Verify the read. */
+ RTSGSEG Seg;
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pvBuf;
+ rc = drvdiskintReadVerify(pThis, &Seg, 1, off, cbRead);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnWrite} */
+static DECLCALLBACK(int) drvdiskintWrite(PPDMIMEDIA pInterface,
+ uint64_t off, const void *pvBuf,
+ size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+
+ drvdiskintTraceLogFireEvtWrite(pThis, (uintptr_t)pvBuf, false /* fAsync */, off, cbWrite);
+
+ if (pThis->fRecordWriteBeforeCompletion)
+ {
+ RTSGSEG Seg;
+ Seg.cbSeg = cbWrite;
+ Seg.pvSeg = (void *)pvBuf;
+
+ rc = drvdiskintWriteRecord(pThis, &Seg, 1, off, cbWrite);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ rc = pThis->pDrvMedia->pfnWrite(pThis->pDrvMedia, off, pvBuf, cbWrite);
+
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)pvBuf, rc, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if ( pThis->fCheckConsistency
+ && !pThis->fRecordWriteBeforeCompletion)
+ {
+ /* Record the write. */
+ RTSGSEG Seg;
+ Seg.cbSeg = cbWrite;
+ Seg.pvSeg = (void *)pvBuf;
+ rc = drvdiskintWriteRecord(pThis, &Seg, 1, off, cbWrite);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnFlush} */
+static DECLCALLBACK(int) drvdiskintFlush(PPDMIMEDIA pInterface)
+{
+ int rc = VINF_SUCCESS;
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+
+ drvdiskintTraceLogFireEvtFlush(pThis, 1, false /* fAsync */);
+ rc = pThis->pDrvMedia->pfnFlush(pThis->pDrvMedia);
+ drvdiskintTraceLogFireEvtComplete(pThis, 1, rc, NULL);
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSize} */
+static DECLCALLBACK(uint64_t) drvdiskintGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnGetSize(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsReadOnly} */
+static DECLCALLBACK(bool) drvdiskintIsReadOnly(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnIsReadOnly(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvdiskintBiosIsVisible(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnBiosIsVisible(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetType} */
+static DECLCALLBACK(PDMMEDIATYPE) drvdiskintGetType(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnGetType(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetPCHSGeometry} */
+static DECLCALLBACK(int) drvdiskintBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnBiosGetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetPCHSGeometry} */
+static DECLCALLBACK(int) drvdiskintBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnBiosSetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetLCHSGeometry} */
+static DECLCALLBACK(int) drvdiskintBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnBiosGetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetLCHSGeometry} */
+static DECLCALLBACK(int) drvdiskintBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnBiosSetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetUuid} */
+static DECLCALLBACK(int) drvdiskintGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnGetUuid(pThis->pDrvMedia, pUuid);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSectorSize} */
+static DECLCALLBACK(uint32_t) drvdiskintGetSectorSize(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnGetSectorSize(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnDiscard} */
+static DECLCALLBACK(int) drvdiskintDiscard(PPDMIMEDIA pInterface, PCRTRANGE paRanges, unsigned cRanges)
+{
+ int rc = VINF_SUCCESS;
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+
+ rc = pThis->pDrvMedia->pfnDiscard(pThis->pDrvMedia, paRanges, cRanges);
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)paRanges, rc, NULL);
+
+ if (pThis->fCheckConsistency)
+ rc = drvdiskintDiscardRecords(pThis, paRanges, cRanges);
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnReadPcBios} */
+static DECLCALLBACK(int) drvdiskintReadPcBios(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ LogFlowFunc(("\n"));
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+
+ return pThis->pDrvMedia->pfnReadPcBios(pThis->pDrvMedia, off, pvBuf, cbRead);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvdiskintIsNonRotational(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnIsNonRotational(pThis->pDrvMedia);
+}
+
+/* -=-=-=-=- IMediaPort -=-=-=-=- */
+
+/** Makes a PDRVBLOCK out of a PPDMIMEDIAPORT. */
+#define PDMIMEDIAPORT_2_DRVDISKINTEGRITY(pInterface) ( (PDRVDISKINTEGRITY((uintptr_t)pInterface - RT_UOFFSETOF(DRVDISKINTEGRITY, IMediaPort))) )
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) drvdiskintQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIAPORT_2_DRVDISKINTEGRITY(pInterface);
+
+ return pThis->pDrvMediaPort->pfnQueryDeviceLocation(pThis->pDrvMediaPort, ppcszController,
+ piInstance, piLUN);
+}
+
+/* -=-=-=-=- IMediaExPort -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaExPort);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_ALLOC_2_DRVDISKAIOREQ(pvIoReqAlloc);
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pIoReq=%#p\n", pIoReq));
+
+ /* Remove from the active list. */
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqRemove(pThis, pIoReq);
+
+ if (RT_SUCCESS(rcReq) && pThis->fCheckConsistency)
+ {
+ if (pIoReq->enmTxDir == DRVDISKAIOTXDIR_READ)
+ rc = drvdiskintReadVerify(pThis, &pIoReq->IoSeg, 1, pIoReq->off, pIoReq->cbTransfer);
+ else if ( pIoReq->enmTxDir == DRVDISKAIOTXDIR_WRITE
+ && !pThis->fRecordWriteBeforeCompletion)
+ rc = drvdiskintWriteRecord(pThis, &pIoReq->IoSeg, 1, pIoReq->off, pIoReq->cbTransfer);
+ else if (pIoReq->enmTxDir == DRVDISKAIOTXDIR_DISCARD)
+ rc = drvdiskintDiscardRecords(pThis, pIoReq->paRanges, pIoReq->cRanges);
+ else if (pIoReq->enmTxDir == DRVDISKAIOTXDIR_READ_AFTER_WRITE)
+ rc = drvdiskintReadAfterWriteVerify(pThis, pIoReq);
+ else
+ AssertMsg( pIoReq->enmTxDir == DRVDISKAIOTXDIR_FLUSH
+ || ( pIoReq->enmTxDir == DRVDISKAIOTXDIR_WRITE
+ && pThis->fRecordWriteBeforeCompletion), ("Huh?\n"));
+
+ AssertRC(rc);
+ }
+
+ if ( RT_SUCCESS(rcReq)
+ && pThis->fValidateMemBufs
+ && pIoReq->enmTxDir == DRVDISKAIOTXDIR_READ)
+ {
+ /* Check that the guest memory buffer matches what was written. */
+ RTSGSEG SegCmp;
+ SegCmp.pvSeg = RTMemAlloc(pIoReq->cbTransfer);
+ SegCmp.cbSeg = pIoReq->cbTransfer;
+
+ RTSGBUF SgBufCmp;
+ RTSgBufInit(&SgBufCmp, &SegCmp, 1);
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ 0, &SgBufCmp, pIoReq->cbTransfer);
+ AssertRC(rc);
+
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pIoReq->IoSeg, 1);
+ if (RTSgBufCmp(&SgBuf, &SgBufCmp, pIoReq->cbTransfer))
+ {
+ RTMsgError("Corrupted memory buffer at offset %llu!\n", 0);
+ RTAssertDebugBreak();
+ }
+
+ RTMemFree(SegCmp.pvSeg);
+ }
+
+ if (pThis->hIoLogger)
+ {
+ RTSGBUF SgBuf;
+
+ if (pIoReq->enmTxDir == DRVDISKAIOTXDIR_READ)
+ RTSgBufInit(&SgBuf, &pIoReq->IoSeg, 1);
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rcReq, &SgBuf);
+ }
+
+ if ( pThis->fReadAfterWrite
+ && pIoReq->enmTxDir == DRVDISKAIOTXDIR_WRITE)
+ {
+#if 0 /** @todo */
+ pIoReq->enmTxDir = DRVDISKAIOTXDIR_READ_AFTER_WRITE;
+
+ /* Add again because it was removed above. */
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqAdd(pThis, pIoReq);
+
+ rc = pThis->pDrvMediaAsync->pfnStartRead(pThis->pDrvMediaAsync, pIoReq->off, pIoReq->paSeg, pIoReq->cSeg,
+ pIoReq->cbTransfer, pIoReq);
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ rc = drvdiskintReadAfterWriteVerify(pThis, pIoReq);
+
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqRemove(pThis, pIoReq);
+ RTMemFree(pIoReq);
+ }
+ else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ RTMemFree(pIoReq);
+#endif
+ }
+ else
+ {
+ rc = pThis->pDrvMediaExPort->pfnIoReqCompleteNotify(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ rcReq);
+ /* Put on the watch list. */
+ if (pThis->fCheckDoubleCompletion)
+ drvdiskintIoReqCheckForDoubleCompletion(pThis, pIoReq, true /* fMediaEx */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaExPort);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_ALLOC_2_DRVDISKAIOREQ(pvIoReqAlloc);
+ RTSGBUF SgBuf;
+
+ RTSgBufClone(&SgBuf, pSgBuf);
+
+ int rc = pThis->pDrvMediaExPort->pfnIoReqCopyFromBuf(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ offDst, pSgBuf, cbCopy);
+ if ( RT_SUCCESS(rc)
+ && pIoReq->IoSeg.pvSeg)
+ {
+ /* Update our copy. */
+ RTSgBufCopyToBuf(&SgBuf, (uint8_t *)pIoReq->IoSeg.pvSeg + offDst, cbCopy);
+
+ /* Validate the just read data against our copy if possible. */
+ if ( pThis->fValidateMemBufs
+ && pThis->fCheckConsistency
+ && pIoReq->enmTxDir == DRVDISKAIOTXDIR_READ)
+ {
+ RTSGSEG Seg;
+
+ Seg.pvSeg = (uint8_t *)pIoReq->IoSeg.pvSeg + offDst;
+ Seg.cbSeg = cbCopy;
+
+ rc = drvdiskintReadVerify(pThis, &Seg, 1, pIoReq->off + offDst,
+ cbCopy);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaExPort);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_ALLOC_2_DRVDISKAIOREQ(pvIoReqAlloc);
+ RTSGBUF SgBuf;
+
+ RTSgBufClone(&SgBuf, pSgBuf);
+
+ int rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ offSrc, pSgBuf, cbCopy);
+ if ( RT_SUCCESS(rc)
+ && pIoReq->IoSeg.pvSeg)
+ {
+ if (pThis->fValidateMemBufs)
+ {
+ /* Make sure what the caller requested matches what we got earlier. */
+ RTSGBUF SgBufCmp;
+ RTSgBufInit(&SgBufCmp, &pIoReq->IoSeg, 1);
+ RTSgBufAdvance(&SgBufCmp, offSrc);
+
+ if (RTSgBufCmp(&SgBuf, &SgBufCmp, cbCopy))
+ {
+ RTMsgError("Corrupted memory buffer at offset %llu!\n", offSrc);
+ RTAssertDebugBreak();
+ }
+ }
+ else
+ {
+ /* Update our copy. */
+ RTSgBufCopyToBuf(&SgBuf, (uint8_t *)pIoReq->IoSeg.pvSeg + offSrc, cbCopy);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryDiscardRanges}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqQueryDiscardRanges(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t idxRangeStart,
+ uint32_t cRanges, PRTRANGE paRanges,
+ uint32_t *pcRanges)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaExPort);
+ return pThis->pDrvMediaExPort->pfnIoReqQueryDiscardRanges(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ idxRangeStart, cRanges, paRanges, pcRanges);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) drvdiskintIoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaExPort);
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_ALLOC_2_UPPER(pvIoReqAlloc),
+ enmState);
+}
+
+/* -=-=-=-=- IMediaEx -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures}
+ */
+static DECLCALLBACK(int) drvdiskintQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnQueryFeatures(pThis->pDrvMediaEx, pfFeatures);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+
+ /* Increase the amount by the size of a pointer to our private tracking structure. */
+ cbIoReqAlloc += sizeof(PDRVDISKAIOREQ);
+
+ pThis->fCheckDoubleCompletion = false;
+
+ return pThis->pDrvMediaEx->pfnIoReqAllocSizeSet(pThis->pDrvMediaEx, cbIoReqAlloc);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAlloc}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ int rc = VINF_SUCCESS;
+ PDRVDISKAIOREQ pIoReq = (PDRVDISKAIOREQ)RTMemCacheAlloc(pThis->hReqCache);
+ if (RT_LIKELY(pIoReq))
+ {
+ pIoReq->enmTxDir = DRVDISKAIOTXDIR_INVALID;
+ pIoReq->off = 0;
+ pIoReq->cbTransfer = 0;
+ pIoReq->paSeg = NULL;
+ pIoReq->cSeg = 0;
+ pIoReq->pvUser = NULL;
+ pIoReq->iSlot = 0;
+ pIoReq->tsStart = 0;
+ pIoReq->tsComplete = 0;
+ pIoReq->IoSeg.pvSeg = NULL;
+ pIoReq->IoSeg.cbSeg = 0;
+
+ PDRVDISKAIOREQ *ppIoReq = NULL;
+ rc = pThis->pDrvMediaEx->pfnIoReqAlloc(pThis->pDrvMediaEx, phIoReq, (void **)&ppIoReq, uIoReqId, fFlags);
+ if RT_SUCCESS(rc)
+ {
+ /*
+ * Store the size off the start of our tracking structure because it is
+ * required to access it for the read/write callbacks.
+ *
+ * ASSUMPTION that the offset is constant.
+ */
+ if (!pThis->cbIoReqOpaque)
+ pThis->cbIoReqOpaque = (uintptr_t)ppIoReq - (uintptr_t)*phIoReq;
+ else
+ Assert(pThis->cbIoReqOpaque == (uintptr_t)ppIoReq - (uintptr_t)*phIoReq);
+
+ *ppIoReq = pIoReq;
+ *ppvIoReqAlloc = ((uint8_t *)ppIoReq) + sizeof(PDRVDISKAIOREQ);
+ }
+ else
+ RTMemCacheFree(pThis->hReqCache, pIoReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFree}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_HANDLE_2_DRVDISKAIOREQ(pThis, hIoReq);
+
+ if (pIoReq->IoSeg.pvSeg)
+ RTMemFree(pIoReq->IoSeg.pvSeg);
+
+ return pThis->pDrvMediaEx->pfnIoReqFree(pThis->pDrvMediaEx, hIoReq);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryResidual}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqQueryResidual(pThis->pDrvMediaEx, hIoReq, pcbResidual);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqQueryXferSize(pThis->pDrvMediaEx, hIoReq, pcbXfer);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqCancelAll(pThis->pDrvMediaEx);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqCancel(pThis->pDrvMediaEx, uIoReqId);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_HANDLE_2_DRVDISKAIOREQ(pThis, hIoReq);
+
+ pIoReq->enmTxDir = DRVDISKAIOTXDIR_READ;
+ pIoReq->off = off;
+ pIoReq->cbTransfer = cbRead;
+
+ /* Allocate a I/O buffer if the I/O is verified.*/
+ if (pThis->fCheckConsistency)
+ {
+ pIoReq->IoSeg.pvSeg = RTMemAlloc(cbRead);
+ pIoReq->IoSeg.cbSeg = cbRead;
+ }
+
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqAdd(pThis, pIoReq);
+
+ drvdiskintTraceLogFireEvtRead(pThis, (uintptr_t)hIoReq, true /* fAsync */, off, cbRead);
+ int rc = pThis->pDrvMediaEx->pfnIoReqRead(pThis->pDrvMediaEx, hIoReq, off, cbRead);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Verify the read now. */
+ if (pThis->fCheckConsistency)
+ {
+ int rc2 = drvdiskintReadVerify(pThis, &pIoReq->IoSeg, 1, off, cbRead);
+ AssertRC(rc2);
+ }
+
+ if (pThis->hIoLogger)
+ {
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, &pIoReq->IoSeg, 1);
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, &SgBuf);
+ }
+
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqRemove(pThis, pIoReq);
+ }
+ else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, NULL);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_HANDLE_2_DRVDISKAIOREQ(pThis, hIoReq);
+
+ pIoReq->enmTxDir = DRVDISKAIOTXDIR_WRITE;
+ pIoReq->off = off;
+ pIoReq->cbTransfer = cbWrite;
+
+ /* Allocate a I/O buffer if the I/O is verified.*/
+ if ( pThis->fCheckConsistency
+ || pThis->fValidateMemBufs
+ || pThis->hIoLogger
+ || pThis->fRecordWriteBeforeCompletion)
+ {
+ pIoReq->IoSeg.pvSeg = RTMemAlloc(cbWrite);
+ pIoReq->IoSeg.cbSeg = cbWrite;
+
+ /* Sync the memory buffer over if we should validate it. */
+ if ( pThis->fValidateMemBufs
+ || pThis->hIoLogger
+ || pThis->fRecordWriteBeforeCompletion)
+ {
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, &pIoReq->IoSeg, 1);
+ int rc2 = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, hIoReq,
+ DISKINTEGRITY_IOREQ_HANDLE_2_UPPER_OPAQUE(pThis, hIoReq),
+ 0, &SgBuf, cbWrite);
+ AssertRC(rc2);
+ }
+ }
+
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqAdd(pThis, pIoReq);
+
+ drvdiskintTraceLogFireEvtWrite(pThis, (uintptr_t)hIoReq, true /* fAsync */, off, cbWrite);
+ if (pThis->fRecordWriteBeforeCompletion)
+ {
+
+ int rc2 = drvdiskintWriteRecord(pThis, &pIoReq->IoSeg, 1, off, cbWrite);
+ AssertRC(rc2);
+ }
+
+ int rc = pThis->pDrvMediaEx->pfnIoReqWrite(pThis->pDrvMediaEx, hIoReq, off, cbWrite);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Record the write. */
+ if ( pThis->fCheckConsistency
+ && !pThis->fRecordWriteBeforeCompletion)
+ {
+ int rc2 = drvdiskintWriteRecord(pThis, &pIoReq->IoSeg, 1, off, cbWrite);
+ AssertRC(rc2);
+ }
+
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pIoReq->IoSeg, 1);
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, &SgBuf);
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqRemove(pThis, pIoReq);
+ }
+ else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, NULL);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ PDRVDISKAIOREQ pIoReq = DISKINTEGRITY_IOREQ_HANDLE_2_DRVDISKAIOREQ(pThis, hIoReq);
+
+ pIoReq->enmTxDir = DRVDISKAIOTXDIR_FLUSH;
+ pIoReq->off = 0;
+ pIoReq->cbTransfer = 0;
+
+ if (pThis->fTraceRequests)
+ drvdiskintIoReqAdd(pThis, pIoReq);
+
+ drvdiskintTraceLogFireEvtFlush(pThis, (uintptr_t)hIoReq, true /* fAsync */);
+ int rc = pThis->pDrvMediaEx->pfnIoReqFlush(pThis->pDrvMediaEx, hIoReq);
+ if (rc == VINF_SUCCESS)
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, NULL);
+ else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ drvdiskintTraceLogFireEvtComplete(pThis, (uintptr_t)hIoReq, rc, NULL);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqDiscard(pThis->pDrvMediaEx, hIoReq, cRangesMax);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount}
+ */
+static DECLCALLBACK(uint32_t) drvdiskintIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqGetActiveCount(pThis->pDrvMediaEx);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount}
+ */
+static DECLCALLBACK(uint32_t) drvdiskintIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqGetSuspendedCount(pThis->pDrvMediaEx);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqQuerySuspendedStart(pThis->pDrvMediaEx, phIoReq, ppvIoReqAlloc);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pThis->pDrvMediaEx, hIoReq, phIoReqNext, ppvIoReqAllocNext);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqSuspendedSave(pThis->pDrvMediaEx, pSSM, hIoReq);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad}
+ */
+static DECLCALLBACK(int) drvdiskintIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnIoReqSuspendedLoad(pThis->pDrvMediaEx, pSSM, hIoReq);
+}
+
+/* -=-=-=-=- IMount -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMOUNT,pfnUnmount} */
+static DECLCALLBACK(int) drvdiskintUnmount(PPDMIMOUNT pInterface, bool fForce, bool fEject)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMount);
+ return pThis->pDrvMount->pfnUnmount(pThis->pDrvMount, fForce, fEject);
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsMounted} */
+static DECLCALLBACK(bool) drvdiskintIsMounted(PPDMIMOUNT pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMount);
+ return pThis->pDrvMount->pfnIsMounted(pThis->pDrvMount);
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnLock} */
+static DECLCALLBACK(int) drvdiskintLock(PPDMIMOUNT pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMount);
+ return pThis->pDrvMount->pfnLock(pThis->pDrvMount);
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnUnlock} */
+static DECLCALLBACK(int) drvdiskintUnlock(PPDMIMOUNT pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMount);
+ return pThis->pDrvMount->pfnUnlock(pThis->pDrvMount);
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsLocked} */
+static DECLCALLBACK(bool) drvdiskintIsLocked(PPDMIMOUNT pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMount);
+ return pThis->pDrvMount->pfnIsLocked(pThis->pDrvMount);
+}
+
+/* -=-=-=-=- IMountNotify -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMOUNTNOTIFY,pfnMountNotify} */
+static DECLCALLBACK(void) drvdiskintMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMountNotify);
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+}
+
+/** @interface_method_impl{PDMIMOUNTNOTIFY,pfnUnmountNotify} */
+static DECLCALLBACK(void) drvdiskintUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMountNotify);
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvdiskintQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVDISKINTEGRITY pThis = PDMINS_2_DATA(pDrvIns, PDRVDISKINTEGRITY);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, &pThis->IMedia);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pThis->IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pThis->IMediaExPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, pThis->pDrvMediaEx ? &pThis->IMediaEx : NULL);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, pThis->pDrvMount ? &pThis->IMount : NULL);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pThis->IMountNotify);
+ return NULL;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+static DECLCALLBACK(int) drvdiskintTreeDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
+{
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)pNode;
+
+ RT_NOREF(pvUser);
+
+ RTMemFree(pSeg->pbSeg);
+ RTMemFree(pSeg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvdiskintDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVDISKINTEGRITY pThis = PDMINS_2_DATA(pDrvIns, PDRVDISKINTEGRITY);
+
+ if (pThis->pTreeSegments)
+ {
+ RTAvlrFileOffsetDestroy(pThis->pTreeSegments, drvdiskintTreeDestroy, NULL);
+ RTMemFree(pThis->pTreeSegments);
+ }
+
+ if (pThis->fTraceRequests)
+ {
+ pThis->fRunning = false;
+ RTSemEventSignal(pThis->SemEvent);
+ RTSemEventDestroy(pThis->SemEvent);
+ }
+
+ if (pThis->fCheckDoubleCompletion)
+ {
+ /* Free all requests */
+ while (pThis->papIoReq[pThis->iEntry])
+ {
+ RTMemFree(pThis->papIoReq[pThis->iEntry]);
+ pThis->papIoReq[pThis->iEntry] = NULL;
+ pThis->iEntry = (pThis->iEntry+1) % pThis->cEntries;
+ }
+ }
+
+ if (pThis->hIoLogger)
+ RTTraceLogWrDestroy(pThis->hIoLogger);
+
+ if (pThis->hReqCache != NIL_RTMEMCACHE)
+ {
+ RTMemCacheDestroy(pThis->hReqCache);
+ pThis->hReqCache = NIL_RTMEMCACHE;
+ }
+}
+
+/**
+ * Construct a disk integrity driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvdiskintConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+ PDRVDISKINTEGRITY pThis = PDMINS_2_DATA(pDrvIns, PDRVDISKINTEGRITY);
+ LogFlow(("drvdiskintConstruct: iInstance=%d\n", pDrvIns->iInstance));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "CheckConsistency\0"
+ "TraceRequests\0"
+ "CheckIntervalMs\0"
+ "ExpireIntervalMs\0"
+ "CheckDoubleCompletions\0"
+ "HistorySize\0"
+ "IoLogType\0"
+ "IoLogFile\0"
+ "IoLogAddress\0"
+ "IoLogPort\0"
+ "IoLogData\0"
+ "PrepopulateRamDisk\0"
+ "ReadAfterWrite\0"
+ "RecordWriteBeforeCompletion\0"
+ "ValidateMemoryBuffers\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ rc = CFGMR3QueryBoolDef(pCfg, "CheckConsistency", &pThis->fCheckConsistency, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "TraceRequests", &pThis->fTraceRequests, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryU32Def(pCfg, "CheckIntervalMs", &pThis->uCheckIntervalMs, 5000); /* 5 seconds */
+ AssertRC(rc);
+ rc = CFGMR3QueryU32Def(pCfg, "ExpireIntervalMs", &pThis->uExpireIntervalMs, 20000); /* 20 seconds */
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "CheckDoubleCompletions", &pThis->fCheckDoubleCompletion, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryU32Def(pCfg, "HistorySize", &pThis->cEntries, 512);
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "PrepopulateRamDisk", &pThis->fPrepopulateRamDisk, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "ReadAfterWrite", &pThis->fReadAfterWrite, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "RecordWriteBeforeCompletion", &pThis->fRecordWriteBeforeCompletion, false);
+ AssertRC(rc);
+ rc = CFGMR3QueryBoolDef(pCfg, "ValidateMemoryBuffers", &pThis->fValidateMemBufs, false);
+ AssertRC(rc);
+
+ bool fIoLogData = false;
+ rc = CFGMR3QueryBoolDef(pCfg, "IoLogData", &fIoLogData, false);
+ AssertRC(rc);
+
+ char *pszIoLogType = NULL;
+ char *pszIoLogFilename = NULL;
+ char *pszAddress = NULL;
+ uint32_t uPort = 0;
+ rc = CFGMR3QueryStringAlloc(pCfg, "IoLogType", &pszIoLogType);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTStrICmp(pszIoLogType, "File"))
+ {
+ rc = CFGMR3QueryStringAlloc(pCfg, "IoLogFile", &pszIoLogFilename);
+ AssertRC(rc);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Server"))
+ {
+ rc = CFGMR3QueryStringAllocDef(pCfg, "IoLogAddress", &pszAddress, NULL);
+ AssertRC(rc);
+ rc = CFGMR3QueryU32Def(pCfg, "IoLogPort", &uPort, 4000);
+ AssertRC(rc);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Client"))
+ {
+ rc = CFGMR3QueryStringAlloc(pCfg, "IoLogAddress", &pszAddress);
+ AssertRC(rc);
+ rc = CFGMR3QueryU32Def(pCfg, "IoLogPort", &uPort, 4000);
+ AssertRC(rc);
+ }
+ else
+ AssertMsgFailed(("Invalid I/O log type given: %s\n", pszIoLogType));
+ }
+ else
+ Assert(rc == VERR_CFGM_VALUE_NOT_FOUND);
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->hReqCache = NIL_RTMEMCACHE;
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvdiskintQueryInterface;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvdiskintRead;
+ pThis->IMedia.pfnWrite = drvdiskintWrite;
+ pThis->IMedia.pfnFlush = drvdiskintFlush;
+ pThis->IMedia.pfnGetSize = drvdiskintGetSize;
+ pThis->IMedia.pfnIsReadOnly = drvdiskintIsReadOnly;
+ pThis->IMedia.pfnBiosIsVisible = drvdiskintBiosIsVisible;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvdiskintBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvdiskintBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvdiskintBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvdiskintBiosSetLCHSGeometry;
+ pThis->IMedia.pfnGetUuid = drvdiskintGetUuid;
+ pThis->IMedia.pfnGetSectorSize = drvdiskintGetSectorSize;
+ pThis->IMedia.pfnGetType = drvdiskintGetType;
+ pThis->IMedia.pfnReadPcBios = drvdiskintReadPcBios;
+ pThis->IMedia.pfnIsNonRotational = drvdiskintIsNonRotational;
+
+ /* IMediaEx. */
+ pThis->IMediaEx.pfnQueryFeatures = drvdiskintQueryFeatures;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvdiskintIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvdiskintIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvdiskintIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvdiskintIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvdiskintIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvdiskintIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvdiskintIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvdiskintIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvdiskintIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvdiskintIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvdiskintIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvdiskintIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvdiskintIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvdiskintIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvdiskintIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvdiskintIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvdiskintIoReqSuspendedLoad;
+
+ /* IMediaPort. */
+ pThis->IMediaPort.pfnQueryDeviceLocation = drvdiskintQueryDeviceLocation;
+
+ /* IMediaExPort. */
+ pThis->IMediaExPort.pfnIoReqCompleteNotify = drvdiskintIoReqCompleteNotify;
+ pThis->IMediaExPort.pfnIoReqCopyFromBuf = drvdiskintIoReqCopyFromBuf;
+ pThis->IMediaExPort.pfnIoReqCopyToBuf = drvdiskintIoReqCopyToBuf;
+ pThis->IMediaExPort.pfnIoReqQueryDiscardRanges = drvdiskintIoReqQueryDiscardRanges;
+ pThis->IMediaExPort.pfnIoReqStateChanged = drvdiskintIoReqStateChanged;
+
+ /* IMount */
+ pThis->IMount.pfnUnmount = drvdiskintUnmount;
+ pThis->IMount.pfnIsMounted = drvdiskintIsMounted;
+ pThis->IMount.pfnLock = drvdiskintLock;
+ pThis->IMount.pfnUnlock = drvdiskintUnlock;
+ pThis->IMount.pfnIsLocked = drvdiskintIsLocked;
+
+ /* IMountNotify */
+ pThis->IMountNotify.pfnMountNotify = drvdiskintMountNotify;
+ pThis->IMountNotify.pfnUnmountNotify = drvdiskintUnmountNotify;
+
+ /* 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);
+
+ rc = RTMemCacheCreate(&pThis->hReqCache, sizeof(DRVDISKAIOREQ), 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("Failed to create request tracking structure cache"));
+
+ /*
+ * Try attach driver below and query it's media interface.
+ */
+ PPDMIBASE pBase;
+ rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Failed to attach driver below us! %Rrc"), rc);
+
+ pThis->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIA);
+ if (!pThis->pDrvMedia)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("No media or async media interface below"));
+
+ pThis->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIAEX);
+ pThis->pDrvMount = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMOUNT);
+
+ if (pThis->pDrvMedia->pfnDiscard)
+ pThis->IMedia.pfnDiscard = drvdiskintDiscard;
+
+ if (pThis->fCheckConsistency)
+ {
+ /* Create the AVL tree. */
+ pThis->pTreeSegments = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
+ if (!pThis->pTreeSegments)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (pThis->fTraceRequests)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apReqActive); i++)
+ {
+ pThis->apReqActive[i].pIoReq = NULL;
+ pThis->apReqActive[i].tsStart = 0;
+ }
+
+ pThis->iNextFreeSlot = 0;
+
+ /* Init event semaphore. */
+ rc = RTSemEventCreate(&pThis->SemEvent);
+ AssertRC(rc);
+ pThis->fRunning = true;
+ rc = RTThreadCreate(&pThis->hThread, drvdiskIntIoReqExpiredCheck, pThis,
+ 0, RTTHREADTYPE_INFREQUENT_POLLER, 0, "DiskIntegrity");
+ AssertRC(rc);
+ }
+
+ if (pThis->fCheckDoubleCompletion)
+ {
+ pThis->iEntry = 0;
+ pThis->papIoReq = (PDRVDISKAIOREQ *)RTMemAllocZ(pThis->cEntries * sizeof(PDRVDISKAIOREQ));
+ AssertPtr(pThis->papIoReq);
+ }
+
+ if (pszIoLogType)
+ {
+ if (!RTStrICmp(pszIoLogType, "File"))
+ {
+ rc = RTTraceLogWrCreateFile(&pThis->hIoLogger, NULL, pszIoLogFilename);
+ MMR3HeapFree(pszIoLogFilename);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Server"))
+ {
+ rc = RTTraceLogWrCreateTcpServer(&pThis->hIoLogger, NULL, pszAddress, uPort);
+ if (pszAddress)
+ MMR3HeapFree(pszAddress);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Client"))
+ {
+ rc = RTTraceLogWrCreateTcpClient(&pThis->hIoLogger, NULL, pszAddress, uPort);
+ MMR3HeapFree(pszAddress);
+ }
+ else
+ AssertMsgFailed(("Invalid I/O log type given: %s\n", pszIoLogType));
+
+ MMR3HeapFree(pszIoLogType);
+ }
+
+ /* Read in all data before the start if requested. */
+ if (pThis->fPrepopulateRamDisk)
+ {
+ uint64_t cbDisk = 0;
+
+ LogRel(("DiskIntegrity: Prepopulating RAM disk, this will take some time...\n"));
+
+ cbDisk = pThis->pDrvMedia->pfnGetSize(pThis->pDrvMedia);
+ if (cbDisk)
+ {
+ uint64_t off = 0;
+ uint8_t abBuffer[_64K];
+ RTSGSEG Seg;
+
+ Seg.pvSeg = abBuffer;
+
+ while (cbDisk)
+ {
+ size_t cbThisRead = RT_MIN(cbDisk, sizeof(abBuffer));
+
+ rc = pThis->pDrvMedia->pfnRead(pThis->pDrvMedia, off, abBuffer, cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMBitFirstSet(abBuffer, sizeof(abBuffer) * 8) != -1)
+ {
+ Seg.cbSeg = cbThisRead;
+ rc = drvdiskintWriteRecord(pThis, &Seg, 1,
+ off, cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ cbDisk -= cbThisRead;
+ off += cbThisRead;
+ }
+
+ LogRel(("DiskIntegrity: Prepopulating RAM disk finished with %Rrc\n", rc));
+ }
+ else
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_INTERNAL_ERROR,
+ N_("DiskIntegrity: Error querying the media size below"));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvDiskIntegrity =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "DiskIntegrity",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Disk integrity driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVDISKINTEGRITY),
+ /* pfnConstruct */
+ drvdiskintConstruct,
+ /* pfnDestruct */
+ drvdiskintDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase-darwin.cpp b/src/VBox/Devices/Storage/DrvHostBase-darwin.cpp
new file mode 100644
index 00000000..052b22d3
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-darwin.cpp
@@ -0,0 +1,758 @@
+/* $Id: DrvHostBase-darwin.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, OS X specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#include <mach/mach.h>
+#include <Carbon/Carbon.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <IOKit/scsi/SCSITaskLib.h>
+#include <IOKit/scsi/SCSICommandOperationCodes.h>
+#include <IOKit/IOBSD.h>
+#include <DiskArbitration/DiskArbitration.h>
+#include <mach/mach_error.h>
+#include <VBox/err.h>
+#include <VBox/scsi.h>
+#include <iprt/string.h>
+
+
+/**
+ * Host backend specific data.
+ */
+typedef struct DRVHOSTBASEOS
+{
+ /** The master port. */
+ mach_port_t MasterPort;
+ /** The MMC-2 Device Interface. (This is only used to get the scsi task interface.) */
+ MMCDeviceInterface **ppMMCDI;
+ /** The SCSI Task Device Interface. */
+ SCSITaskDeviceInterface **ppScsiTaskDI;
+ /** The block size. Set when querying the media size. */
+ uint32_t cbBlock;
+ /** The disk arbitration session reference. NULL if we didn't have to claim & unmount the device. */
+ DASessionRef pDASession;
+ /** The disk arbitration disk reference. NULL if we didn't have to claim & unmount the device. */
+ DADiskRef pDADisk;
+ /** The number of errors that could go into the release log. (flood gate) */
+ uint32_t cLogRelErrors;
+} DRVHOSTBASEOS;
+/** Pointer to the host backend specific data. */
+typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
+AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
+
+#define DRVHOSTBASE_OS_INT_DECLARED
+#include "DrvHostBase.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum buffer size we support, check whether darwin has some real upper limit. */
+#define DARWIN_SCSI_MAX_BUFFER_SIZE (100 * _1K)
+
+/** The runloop input source name for the disk arbitration events. */
+#define MY_RUN_LOOP_MODE CFSTR("drvHostBaseDA") /** @todo r=bird: Check if this will cause trouble in the same way that the one in the USB code did. */
+
+
+
+/**
+ * Gets the BSD Name (/dev/disc[0-9]+) for the service.
+ *
+ * This is done by recursing down the I/O registry until we hit upon an entry
+ * with a BSD Name. Usually we find it two levels down. (Further down under
+ * the IOCDPartitionScheme, the volume (slices) BSD Name is found. We don't
+ * seem to have to go this far fortunately.)
+ *
+ * @return VINF_SUCCESS if found, VERR_FILE_NOT_FOUND otherwise.
+ * @param Entry The current I/O registry entry reference.
+ * @param pszName Where to store the name. 128 bytes.
+ * @param cRecursions Number of recursions. This is used as an precaution
+ * just to limit the depth and avoid blowing the stack
+ * should we hit a bug or something.
+ */
+static int drvHostBaseGetBSDName(io_registry_entry_t Entry, char *pszName, unsigned cRecursions)
+{
+ int rc = VERR_FILE_NOT_FOUND;
+ io_iterator_t Children = 0;
+ kern_return_t krc = IORegistryEntryGetChildIterator(Entry, kIOServicePlane, &Children);
+ if (krc == KERN_SUCCESS)
+ {
+ io_object_t Child;
+ while ( rc == VERR_FILE_NOT_FOUND
+ && (Child = IOIteratorNext(Children)) != 0)
+ {
+ CFStringRef BSDNameStrRef = (CFStringRef)IORegistryEntryCreateCFProperty(Child, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
+ if (BSDNameStrRef)
+ {
+ if (CFStringGetCString(BSDNameStrRef, pszName, 128, kCFStringEncodingUTF8))
+ rc = VINF_SUCCESS;
+ else
+ AssertFailed();
+ CFRelease(BSDNameStrRef);
+ }
+ if (rc == VERR_FILE_NOT_FOUND && cRecursions < 10)
+ rc = drvHostBaseGetBSDName(Child, pszName, cRecursions + 1);
+ IOObjectRelease(Child);
+ }
+ IOObjectRelease(Children);
+ }
+ return rc;
+}
+
+
+/**
+ * Callback notifying us that the async DADiskClaim()/DADiskUnmount call has completed.
+ *
+ * @param DiskRef The disk that was attempted claimed / unmounted.
+ * @param DissenterRef NULL on success, contains details on failure.
+ * @param pvContext Pointer to the return code variable.
+ */
+static void drvHostBaseDADoneCallback(DADiskRef DiskRef, DADissenterRef DissenterRef, void *pvContext)
+{
+ RT_NOREF(DiskRef);
+ int *prc = (int *)pvContext;
+ if (!DissenterRef)
+ *prc = 0;
+ else
+ *prc = DADissenterGetStatus(DissenterRef) ? DADissenterGetStatus(DissenterRef) : -1;
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+
+/**
+ * Obtain exclusive access to the DVD device, umount it if necessary.
+ *
+ * @return VBox status code.
+ * @param pThis The driver instance.
+ * @param DVDService The DVD service object.
+ */
+static int drvHostBaseObtainExclusiveAccess(PDRVHOSTBASE pThis, io_object_t DVDService)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns; NOREF(pDrvIns);
+
+ for (unsigned iTry = 0;; iTry++)
+ {
+ IOReturn irc = (*pThis->Os.ppScsiTaskDI)->ObtainExclusiveAccess(pThis->Os.ppScsiTaskDI);
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * This is a bit weird, but if we unmounted the DVD drive we also need to
+ * unlock it afterwards or the guest won't be able to eject it later on.
+ */
+ if (pThis->Os.pDADisk)
+ {
+ uint8_t abCmd[16] =
+ {
+ SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, false, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
+ }
+ return VINF_SUCCESS;
+ }
+ if (irc == kIOReturnExclusiveAccess)
+ return VERR_SHARING_VIOLATION; /* already used exclusivly. */
+ if (irc != kIOReturnBusy)
+ return VERR_GENERAL_FAILURE; /* not mounted */
+
+ /*
+ * Attempt to the unmount all volumes of the device.
+ * It seems we can can do this all in one go without having to enumerate the
+ * volumes (sessions) and deal with them one by one. This is very fortuitous
+ * as the disk arbitration API is a bit cumbersome to deal with.
+ */
+ if (iTry > 2)
+ return VERR_DRIVE_LOCKED;
+ char szName[128];
+ int rc = drvHostBaseGetBSDName(DVDService, &szName[0], 0);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Os.pDASession = DASessionCreate(kCFAllocatorDefault);
+ if (pThis->Os.pDASession)
+ {
+ DASessionScheduleWithRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ pThis->Os.pDADisk = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->Os.pDASession, szName);
+ if (pThis->Os.pDADisk)
+ {
+ /*
+ * Try claim the device.
+ */
+ Log(("%s-%d: calling DADiskClaim on '%s'.\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
+ int rcDA = -2;
+ DADiskClaim(pThis->Os.pDADisk, kDADiskClaimOptionDefault, NULL, NULL, drvHostBaseDADoneCallback, &rcDA);
+ SInt32 rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
+ AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
+ if ( rc32 == kCFRunLoopRunStopped
+ && !rcDA)
+ {
+ /*
+ * Try unmount the device.
+ */
+ Log(("%s-%d: calling DADiskUnmount on '%s'.\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
+ rcDA = -2;
+ DADiskUnmount(pThis->Os.pDADisk, kDADiskUnmountOptionWhole, drvHostBaseDADoneCallback, &rcDA);
+ rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
+ AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
+ if ( rc32 == kCFRunLoopRunStopped
+ && !rcDA)
+ {
+ iTry = 99;
+ DASessionUnscheduleFromRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ Log(("%s-%d: unmount succeed - retrying.\n", pDrvIns->pReg->szName, pDrvIns->iInstance));
+ continue;
+ }
+ Log(("%s-%d: umount => rc32=%d & rcDA=%#x\n", pDrvIns->pReg->szName, pDrvIns->iInstance, rc32, rcDA));
+
+ /* failed - cleanup */
+ DADiskUnclaim(pThis->Os.pDADisk);
+ }
+ else
+ Log(("%s-%d: claim => rc32=%d & rcDA=%#x\n", pDrvIns->pReg->szName, pDrvIns->iInstance, rc32, rcDA));
+
+ CFRelease(pThis->Os.pDADisk);
+ pThis->Os.pDADisk = NULL;
+ }
+ else
+ Log(("%s-%d: failed to open disk '%s'!\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
+
+ DASessionUnscheduleFromRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
+ CFRelease(pThis->Os.pDASession);
+ pThis->Os.pDASession = NULL;
+ }
+ else
+ Log(("%s-%d: failed to create DA session!\n", pDrvIns->pReg->szName, pDrvIns->iInstance));
+ }
+ RTThreadSleep(10);
+ }
+}
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
+ Assert(pbSense || !cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1);
+ const uint32_t cbBuf = pcbBuf ? *pcbBuf : 0;
+ if (pcbBuf)
+ *pcbBuf = 0;
+
+ Assert(pThis->Os.ppScsiTaskDI);
+
+ int rc = VERR_GENERAL_FAILURE;
+ SCSITaskInterface **ppScsiTaskI = (*pThis->Os.ppScsiTaskDI)->CreateSCSITask(pThis->Os.ppScsiTaskDI);
+ if (!ppScsiTaskI)
+ return VERR_NO_MEMORY;
+ do
+ {
+ /* Setup the scsi command. */
+ SCSICommandDescriptorBlock cdb = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ memcpy(&cdb[0], pbCmd, cbCmd);
+ IOReturn irc = (*ppScsiTaskI)->SetCommandDescriptorBlock(ppScsiTaskI, cdb, cbCmd);
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Setup the buffer. */
+ if (enmTxDir == PDMMEDIATXDIR_NONE)
+ irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, NULL, 0, 0, kSCSIDataTransfer_NoDataTransfer);
+ else
+ {
+ IOVirtualRange Range = { (IOVirtualAddress)pvBuf, cbBuf };
+ irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, &Range, 1, cbBuf,
+ enmTxDir == PDMMEDIATXDIR_FROM_DEVICE
+ ? kSCSIDataTransfer_FromTargetToInitiator
+ : kSCSIDataTransfer_FromInitiatorToTarget);
+ }
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Set the timeout. */
+ irc = (*ppScsiTaskI)->SetTimeoutDuration(ppScsiTaskI, cTimeoutMillies ? cTimeoutMillies : 30000 /*ms*/);
+ AssertBreak(irc == kIOReturnSuccess);
+
+ /* Execute the command and get the response. */
+ SCSI_Sense_Data SenseData = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ SCSIServiceResponse ServiceResponse = kSCSIServiceResponse_Request_In_Process;
+ SCSITaskStatus TaskStatus = kSCSITaskStatus_GOOD;
+ UInt64 cbReturned = 0;
+ irc = (*ppScsiTaskI)->ExecuteTaskSync(ppScsiTaskI, &SenseData, &TaskStatus, &cbReturned);
+ AssertBreak(irc == kIOReturnSuccess);
+ if (pcbBuf)
+ *pcbBuf = (int32_t)cbReturned;
+
+ irc = (*ppScsiTaskI)->GetSCSIServiceResponse(ppScsiTaskI, &ServiceResponse);
+ AssertBreak(irc == kIOReturnSuccess);
+ AssertBreak(ServiceResponse == kSCSIServiceResponse_TASK_COMPLETE);
+
+ if (TaskStatus == kSCSITaskStatus_GOOD)
+ rc = VINF_SUCCESS;
+ else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
+ && pbSense)
+ {
+ memset(pbSense, 0, cbSense); /* lazy */
+ memcpy(pbSense, &SenseData, RT_MIN(sizeof(SenseData), cbSense));
+ rc = VERR_UNRESOLVED_ERROR;
+ }
+ /** @todo convert sense codes when caller doesn't wish to do this himself. */
+ /*else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
+ && SenseData.ADDITIONAL_SENSE_CODE == 0x3A)
+ rc = VERR_MEDIA_NOT_PRESENT; */
+ else
+ {
+ rc = enmTxDir == PDMMEDIATXDIR_NONE
+ ? VERR_DEV_IO_ERROR
+ : enmTxDir == PDMMEDIATXDIR_FROM_DEVICE
+ ? VERR_READ_ERROR
+ : VERR_WRITE_ERROR;
+ if (pThis->Os.cLogRelErrors++ < 10)
+ LogRel(("DVD scsi error: cmd={%.*Rhxs} TaskStatus=%#x key=%#x ASC=%#x ASCQ=%#x (%Rrc)\n",
+ cbCmd, pbCmd, TaskStatus, SenseData.SENSE_KEY, SenseData.ADDITIONAL_SENSE_CODE,
+ SenseData.ADDITIONAL_SENSE_CODE_QUALIFIER, rc));
+ }
+ } while (0);
+
+ (*ppScsiTaskI)->Release(ppScsiTaskI);
+
+ return rc;
+}
+
+
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+
+ return DARWIN_SCSI_MAX_BUFFER_SIZE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ /*
+ * Try a READ_CAPACITY command...
+ */
+ struct
+ {
+ uint32_t cBlocks;
+ uint32_t cbBlock;
+ } Buf = {0, 0};
+ uint32_t cbBuf = sizeof(Buf);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_CAPACITY, 0, 0, 0, 0, 0, 0,
+ 0,0,0,0,0,0,0,0,0
+ };
+ int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_FROM_DEVICE, &Buf, &cbBuf, NULL, 0, 0);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbBuf == sizeof(Buf));
+ Buf.cBlocks = RT_BE2H_U32(Buf.cBlocks);
+ Buf.cbBlock = RT_BE2H_U32(Buf.cbBlock);
+ //if (Buf.cbBlock > 2048) /* everyone else is doing this... check if it needed/right.*/
+ // Buf.cbBlock = 2048;
+ pThis->Os.cbBlock = Buf.cbBlock;
+
+ *pcb = (uint64_t)Buf.cBlocks * Buf.cbBlock;
+ }
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( pThis->Os.ppScsiTaskDI
+ && pThis->Os.cbBlock)
+ {
+ /*
+ * Issue a READ(12) request.
+ */
+ do
+ {
+ const uint32_t LBA = off / pThis->Os.cbBlock;
+ AssertReturn(!(off % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
+ uint32_t cbRead32 = cbRead > SCSI_MAX_BUFFER_SIZE
+ ? SCSI_MAX_BUFFER_SIZE
+ : (uint32_t)cbRead;
+ const uint32_t cBlocks = cbRead32 / pThis->Os.cbBlock;
+ AssertReturn(!(cbRead % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_12, 0,
+ RT_BYTE4(LBA), RT_BYTE3(LBA), RT_BYTE2(LBA), RT_BYTE1(LBA),
+ RT_BYTE4(cBlocks), RT_BYTE3(cBlocks), RT_BYTE2(cBlocks), RT_BYTE1(cBlocks),
+ 0, 0, 0, 0, 0
+ };
+ rc = drvHostBaseScsiCmdOs(pThis, abCmd, 12, PDMMEDIATXDIR_FROM_DEVICE, pvBuf, &cbRead32, NULL, 0, 0);
+
+ off += cbRead32;
+ cbRead -= cbRead32;
+ pvBuf = (uint8_t *)pvBuf + cbRead32;
+ } while ((cbRead > 0) && RT_SUCCESS(rc));
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ RT_NOREF4(pThis, off, pvBuf, cbWrite);
+ return VERR_WRITE_PROTECT;
+}
+
+
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF1(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
+{
+ uint8_t abCmd[16] =
+ {
+ SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
+}
+
+
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
+{
+ uint8_t abCmd[16] =
+ {
+ SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
+}
+
+
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
+{
+ AssertReturn(pThis->Os.ppScsiTaskDI, VERR_INTERNAL_ERROR);
+
+ /*
+ * Issue a TEST UNIT READY request.
+ */
+ *pfMediaChanged = false;
+ *pfMediaPresent = false;
+ uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ uint8_t abSense[32];
+ int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
+ if (RT_SUCCESS(rc))
+ *pfMediaPresent = true;
+ else if ( rc == VERR_UNRESOLVED_ERROR
+ && abSense[2] == 6 /* unit attention */
+ && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
+ || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 3 /* inquiry parameters changed */)
+ || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
+ )
+ )
+ {
+ *pfMediaPresent = false;
+ *pfMediaChanged = true;
+ rc = VINF_SUCCESS;
+ /** @todo check this media change stuff on Darwin. */
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
+{
+ pThis->Os.MasterPort = IO_OBJECT_NULL;
+ pThis->Os.ppMMCDI = NULL;
+ pThis->Os.ppScsiTaskDI = NULL;
+ pThis->Os.cbBlock = 0;
+ pThis->Os.pDADisk = NULL;
+ pThis->Os.pDASession = NULL;
+}
+
+
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
+{
+ RT_NOREF(fReadOnly);
+
+ /* Darwin is kind of special... */
+ Assert(!pThis->Os.cbBlock);
+ Assert(pThis->Os.MasterPort == IO_OBJECT_NULL);
+ Assert(!pThis->Os.ppMMCDI);
+ Assert(!pThis->Os.ppScsiTaskDI);
+
+ /*
+ * Open the master port on the first invocation.
+ */
+ kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &pThis->Os.MasterPort);
+ AssertReturn(krc == KERN_SUCCESS, VERR_GENERAL_FAILURE);
+
+ /*
+ * Create a matching dictionary for searching for CD, DVD and BlueRay services in the IOKit.
+ *
+ * The idea is to find all the devices which are of class IOCDBlockStorageDevice.
+ * CD devices are represented by IOCDBlockStorageDevice class itself, while DVD and BlueRay ones
+ * have it as a parent class.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOCDBlockStorageDevice");
+ AssertReturn(RefMatchingDict, VERR_NOT_FOUND);
+
+ /*
+ * do the search and get a collection of keyboards.
+ */
+ io_iterator_t DVDServices = IO_OBJECT_NULL;
+ IOReturn irc = IOServiceGetMatchingServices(pThis->Os.MasterPort, RefMatchingDict, &DVDServices);
+ AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), VERR_NOT_FOUND);
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ /*
+ * Enumerate the matching drives (services).
+ * (This enumeration must be identical to the one performed in Main/src-server/darwin/iokit.cpp.)
+ */
+ int rc = VERR_FILE_NOT_FOUND;
+ unsigned i = 0;
+ io_object_t DVDService;
+ while ((DVDService = IOIteratorNext(DVDServices)) != 0)
+ {
+ /*
+ * Get the properties we use to identify the DVD drive.
+ *
+ * While there is a (weird 12 byte) GUID, it isn't persistent
+ * across boots. So, we have to use a combination of the
+ * vendor name and product name properties with an optional
+ * sequence number for identification.
+ */
+ CFMutableDictionaryRef PropsRef = 0;
+ krc = IORegistryEntryCreateCFProperties(DVDService, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ /* Get the Device Characteristics dictionary. */
+ CFDictionaryRef DevCharRef = (CFDictionaryRef)CFDictionaryGetValue(PropsRef, CFSTR(kIOPropertyDeviceCharacteristicsKey));
+ if (DevCharRef)
+ {
+ /* The vendor name. */
+ char szVendor[128];
+ char *pszVendor = &szVendor[0];
+ CFTypeRef ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyVendorNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szVendor, sizeof(szVendor), kCFStringEncodingUTF8))
+ pszVendor = RTStrStrip(szVendor);
+ else
+ *pszVendor = '\0';
+
+ /* The product name. */
+ char szProduct[128];
+ char *pszProduct = &szProduct[0];
+ ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyProductNameKey));
+ if ( ValueRef
+ && CFGetTypeID(ValueRef) == CFStringGetTypeID()
+ && CFStringGetCString((CFStringRef)ValueRef, szProduct, sizeof(szProduct), kCFStringEncodingUTF8))
+ pszProduct = RTStrStrip(szProduct);
+ else
+ *pszProduct = '\0';
+
+ /* Construct the two names and compare thwm with the one we're searching for. */
+ char szName1[256 + 32];
+ char szName2[256 + 32];
+ if (*pszVendor || *pszProduct)
+ {
+ if (*pszVendor && *pszProduct)
+ {
+ RTStrPrintf(szName1, sizeof(szName1), "%s %s", pszVendor, pszProduct);
+ RTStrPrintf(szName2, sizeof(szName2), "%s %s (#%u)", pszVendor, pszProduct, i);
+ }
+ else
+ {
+ strcpy(szName1, *pszVendor ? pszVendor : pszProduct);
+ RTStrPrintf(szName2, sizeof(szName2), "%s (#%u)", *pszVendor ? pszVendor : pszProduct, i);
+ }
+ }
+ else
+ {
+ RTStrPrintf(szName1, sizeof(szName1), "(#%u)", i);
+ strcpy(szName2, szName1);
+ }
+
+ if ( !strcmp(szName1, pThis->pszDevice)
+ || !strcmp(szName2, pThis->pszDevice))
+ {
+ /*
+ * Found it! Now, get the client interface and stuff.
+ * Note that we could also query kIOSCSITaskDeviceUserClientTypeID here if the
+ * MMC client plugin is missing. For now we assume this won't be necessary.
+ */
+ SInt32 Score = 0;
+ IOCFPlugInInterface **ppPlugInInterface = NULL;
+ krc = IOCreatePlugInInterfaceForService(DVDService, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
+ &ppPlugInInterface, &Score);
+ if (krc == KERN_SUCCESS)
+ {
+ HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+ CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
+ (LPVOID *)&pThis->Os.ppMMCDI);
+ (*ppPlugInInterface)->Release(ppPlugInInterface);
+ ppPlugInInterface = NULL;
+ if (hrc == S_OK)
+ {
+ pThis->Os.ppScsiTaskDI = (*pThis->Os.ppMMCDI)->GetSCSITaskDeviceInterface(pThis->Os.ppMMCDI);
+ if (pThis->Os.ppScsiTaskDI)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("GetSCSITaskDeviceInterface failed on '%s'\n", pThis->pszDevice));
+ rc = VERR_NOT_SUPPORTED;
+ (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
+ }
+ }
+ else
+ {
+ rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinCOM(krc);
+ pThis->Os.ppMMCDI = NULL;
+ }
+ }
+ else /* Check for kIOSCSITaskDeviceUserClientTypeID? */
+ rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinKern(krc);
+
+ /* Obtain exclusive access to the device so we can send SCSI commands. */
+ if (RT_SUCCESS(rc))
+ rc = drvHostBaseObtainExclusiveAccess(pThis, DVDService);
+
+ /* Cleanup on failure. */
+ if (RT_FAILURE(rc))
+ {
+ if (pThis->Os.ppScsiTaskDI)
+ {
+ (*pThis->Os.ppScsiTaskDI)->Release(pThis->Os.ppScsiTaskDI);
+ pThis->Os.ppScsiTaskDI = NULL;
+ }
+ if (pThis->Os.ppMMCDI)
+ {
+ (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
+ pThis->Os.ppMMCDI = NULL;
+ }
+ }
+
+ IOObjectRelease(DVDService);
+ break;
+ }
+ }
+ CFRelease(PropsRef);
+ }
+ else
+ AssertMsgFailed(("krc=%#x\n", krc));
+
+ IOObjectRelease(DVDService);
+ i++;
+ }
+
+ IOObjectRelease(DVDServices);
+ return rc;
+
+}
+
+
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
+{
+ if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ return true;
+
+ AssertMsgFailed(("Darwin supports only CD/DVD host drive access\n"));
+ return false;
+}
+
+
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+ if ( ( pThis->fLocked
+ || pThis->IMedia.pfnSendCmd)
+ && pThis->Os.ppScsiTaskDI
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ /*
+ * The unclaiming doesn't seem to mean much, the DVD is actually
+ * remounted when we release exclusive access. I'm not quite sure
+ * if I should put the unclaim first or not...
+ *
+ * Anyway, that it's automatically remounted very good news for us,
+ * because that means we don't have to mess with that ourselves. Of
+ * course there is the unlikely scenario that we've succeeded in claiming
+ * and umount the DVD but somehow failed to gain exclusive scsi access...
+ */
+ if (pThis->Os.ppScsiTaskDI)
+ {
+ LogFlow(("%s-%d: releasing exclusive scsi access!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ (*pThis->Os.ppScsiTaskDI)->ReleaseExclusiveAccess(pThis->Os.ppScsiTaskDI);
+ (*pThis->Os.ppScsiTaskDI)->Release(pThis->Os.ppScsiTaskDI);
+ pThis->Os.ppScsiTaskDI = NULL;
+ }
+ if (pThis->Os.pDADisk)
+ {
+ LogFlow(("%s-%d: unclaiming the disk!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ DADiskUnclaim(pThis->Os.pDADisk);
+ CFRelease(pThis->Os.pDADisk);
+ pThis->Os.pDADisk = NULL;
+ }
+ if (pThis->Os.ppMMCDI)
+ {
+ LogFlow(("%s-%d: releasing the MMC object!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
+ pThis->Os.ppMMCDI = NULL;
+ }
+ if (pThis->Os.MasterPort != IO_OBJECT_NULL)
+ {
+ mach_port_deallocate(mach_task_self(), pThis->Os.MasterPort);
+ pThis->Os.MasterPort = IO_OBJECT_NULL;
+ }
+ if (pThis->Os.pDASession)
+ {
+ LogFlow(("%s-%d: releasing the DA session!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ CFRelease(pThis->Os.pDASession);
+ pThis->Os.pDASession = NULL;
+ }
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp b/src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp
new file mode 100644
index 00000000..baf98206
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp
@@ -0,0 +1,434 @@
+/* $Id: DrvHostBase-freebsd.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, FreeBSD specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <stdio.h>
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/scsi/scsi_pass.h>
+#include <VBox/err.h>
+
+#include <VBox/scsi.h>
+#include <iprt/log.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Host backend specific data.
+ */
+typedef struct DRVHOSTBASEOS
+{
+ /** The filehandle of the device. */
+ RTFILE hFileDevice;
+ /** The block size. Set when querying the media size. */
+ uint32_t cbBlock;
+ /** SCSI bus number. */
+ path_id_t ScsiBus;
+ /** target ID of the passthrough device. */
+ target_id_t ScsiTargetID;
+ /** LUN of the passthrough device. */
+ lun_id_t ScsiLunID;
+} DRVHOSTBASEOS;
+/** Pointer to the host backend specific data. */
+typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
+AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
+
+#define DRVHOSTBASE_OS_INT_DECLARED
+#include "DrvHostBase.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum buffer size supported by the CAM subsystem. */
+#define FBSD_SCSI_MAX_BUFFER_SIZE (64 * _1K)
+
+
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
+ Assert(pbSense || !cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1);
+ const uint32_t cbBuf = pcbBuf ? *pcbBuf : 0;
+ if (pcbBuf)
+ *pcbBuf = 0;
+
+ int rc = VINF_SUCCESS;
+ int rcBSD = 0;
+ union ccb DeviceCCB;
+ union ccb *pDeviceCCB = &DeviceCCB;
+ u_int32_t fFlags;
+
+ memset(pDeviceCCB, 0, sizeof(DeviceCCB));
+ pDeviceCCB->ccb_h.path_id = pThis->Os.ScsiBus;
+ pDeviceCCB->ccb_h.target_id = pThis->Os.ScsiTargetID;
+ pDeviceCCB->ccb_h.target_lun = pThis->Os.ScsiLunID;
+
+ /* The SCSI INQUIRY command can't be passed through directly. */
+ if (pbCmd[0] == SCSI_INQUIRY)
+ {
+ pDeviceCCB->ccb_h.func_code = XPT_GDEV_TYPE;
+
+ rcBSD = ioctl(RTFileToNative(pThis->Os.hFileDevice), CAMIOCOMMAND, pDeviceCCB);
+ if (!rcBSD)
+ {
+ uint32_t cbCopy = cbBuf < sizeof(struct scsi_inquiry_data)
+ ? cbBuf
+ : sizeof(struct scsi_inquiry_data);;
+ memcpy(pvBuf, &pDeviceCCB->cgd.inq_data, cbCopy);
+ memset(pbSense, 0, cbSense);
+
+ if (pcbBuf)
+ *pcbBuf = cbCopy;
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else
+ {
+ /* Copy the CDB. */
+ memcpy(&pDeviceCCB->csio.cdb_io.cdb_bytes, pbCmd, cbCmd);
+
+ /* Set direction. */
+ if (enmTxDir == PDMMEDIATXDIR_NONE)
+ fFlags = CAM_DIR_NONE;
+ else if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ fFlags = CAM_DIR_IN;
+ else
+ fFlags = CAM_DIR_OUT;
+
+ fFlags |= CAM_DEV_QFRZDIS;
+
+ cam_fill_csio(&pDeviceCCB->csio, 1, NULL, fFlags, MSG_SIMPLE_Q_TAG,
+ (u_int8_t *)pvBuf, cbBuf, cbSense, cbCmd,
+ cTimeoutMillies ? cTimeoutMillies : 30000/* timeout */);
+
+ /* Send command */
+ rcBSD = ioctl(RTFileToNative(pThis->Os.hFileDevice), CAMIOCOMMAND, pDeviceCCB);
+ if (!rcBSD)
+ {
+ switch (pDeviceCCB->ccb_h.status & CAM_STATUS_MASK)
+ {
+ case CAM_REQ_CMP:
+ rc = VINF_SUCCESS;
+ break;
+ case CAM_SEL_TIMEOUT:
+ rc = VERR_DEV_IO_ERROR;
+ break;
+ case CAM_CMD_TIMEOUT:
+ rc = VERR_TIMEOUT;
+ break;
+ default:
+ rc = VERR_DEV_IO_ERROR;
+ }
+
+ if (pcbBuf)
+ *pcbBuf = cbBuf - pDeviceCCB->csio.resid;
+
+ if (pbSense)
+ memcpy(pbSense, &pDeviceCCB->csio.sense_data,
+ cbSense - pDeviceCCB->csio.sense_resid);
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+}
+
+
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+
+ return FBSD_SCSI_MAX_BUFFER_SIZE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ /*
+ * Try a READ_CAPACITY command...
+ */
+ struct
+ {
+ uint32_t cBlocks;
+ uint32_t cbBlock;
+ } Buf = {0, 0};
+ uint32_t cbBuf = sizeof(Buf);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_CAPACITY, 0, 0, 0, 0, 0, 0,
+ 0,0,0,0,0,0,0,0,0
+ };
+ int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_FROM_DEVICE, &Buf, &cbBuf, NULL, 0, 0);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbBuf == sizeof(Buf));
+ Buf.cBlocks = RT_BE2H_U32(Buf.cBlocks);
+ Buf.cbBlock = RT_BE2H_U32(Buf.cbBlock);
+ //if (Buf.cbBlock > 2048) /* everyone else is doing this... check if it needed/right.*/
+ // Buf.cbBlock = 2048;
+ pThis->Os.cbBlock = Buf.cbBlock;
+
+ *pcb = (uint64_t)Buf.cBlocks * Buf.cbBlock;
+ }
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->Os.cbBlock)
+ {
+ /*
+ * Issue a READ(12) request.
+ */
+ do
+ {
+ const uint32_t LBA = off / pThis->Os.cbBlock;
+ AssertReturn(!(off % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
+ uint32_t cbRead32 = cbRead > FBSD_SCSI_MAX_BUFFER_SIZE
+ ? FBSD_SCSI_MAX_BUFFER_SIZE
+ : (uint32_t)cbRead;
+ const uint32_t cBlocks = cbRead32 / pThis->Os.cbBlock;
+ AssertReturn(!(cbRead % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
+ uint8_t abCmd[16] =
+ {
+ SCSI_READ_12, 0,
+ RT_BYTE4(LBA), RT_BYTE3(LBA), RT_BYTE2(LBA), RT_BYTE1(LBA),
+ RT_BYTE4(cBlocks), RT_BYTE3(cBlocks), RT_BYTE2(cBlocks), RT_BYTE1(cBlocks),
+ 0, 0, 0, 0, 0
+ };
+ rc = drvHostBaseScsiCmdOs(pThis, abCmd, 12, PDMMEDIATXDIR_FROM_DEVICE, pvBuf, &cbRead32, NULL, 0, 0);
+
+ off += cbRead32;
+ cbRead -= cbRead32;
+ pvBuf = (uint8_t *)pvBuf + cbRead32;
+ } while ((cbRead > 0) && RT_SUCCESS(rc));
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ RT_NOREF4(pThis, off, pvBuf, cbWrite);
+ return VERR_WRITE_PROTECT;
+}
+
+
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF1(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
+{
+ uint8_t abCmd[16] =
+ {
+ SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
+}
+
+
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
+{
+ uint8_t abCmd[16] =
+ {
+ SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
+ 0,0,0,0,0,0,0,0,0,0
+ };
+ return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
+}
+
+
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
+{
+ /*
+ * Issue a TEST UNIT READY request.
+ */
+ *pfMediaChanged = false;
+ *pfMediaPresent = false;
+ uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
+ uint8_t abSense[32];
+ int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
+ if (RT_SUCCESS(rc))
+ *pfMediaPresent = true;
+ else if ( rc == VERR_UNRESOLVED_ERROR
+ && abSense[2] == 6 /* unit attention */
+ && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
+ || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
+ || (abSense[12] == 0x3f && abSense[13] == 3 /* inquiry parameters changed */)
+ || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
+ )
+ )
+ {
+ *pfMediaPresent = false;
+ *pfMediaChanged = true;
+ rc = VINF_SUCCESS;
+ /** @todo check this media change stuff on Darwin. */
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
+{
+ pThis->Os.hFileDevice = NIL_RTFILE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
+{
+ RT_NOREF(fReadOnly);
+ RTFILE hFileDevice;
+ int rc = RTFileOpen(&hFileDevice, pThis->pszDevice, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The current device handle can't passthrough SCSI commands.
+ * We have to get he passthrough device path and open this.
+ */
+ union ccb DeviceCCB;
+ memset(&DeviceCCB, 0, sizeof(DeviceCCB));
+
+ DeviceCCB.ccb_h.func_code = XPT_GDEVLIST;
+ int rcBSD = ioctl(RTFileToNative(hFileDevice), CAMGETPASSTHRU, &DeviceCCB);
+ if (!rcBSD)
+ {
+ char *pszPassthroughDevice = NULL;
+ rc = RTStrAPrintf(&pszPassthroughDevice, "/dev/%s%u",
+ DeviceCCB.cgdl.periph_name, DeviceCCB.cgdl.unit_number);
+ if (rc >= 0)
+ {
+ RTFILE hPassthroughDevice;
+ rc = RTFileOpen(&hPassthroughDevice, pszPassthroughDevice, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ RTStrFree(pszPassthroughDevice);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get needed device parameters. */
+
+ /*
+ * The device path, target id and lun id. Those are
+ * needed for the SCSI passthrough ioctl.
+ */
+ memset(&DeviceCCB, 0, sizeof(DeviceCCB));
+ DeviceCCB.ccb_h.func_code = XPT_GDEVLIST;
+
+ rcBSD = ioctl(RTFileToNative(hPassthroughDevice), CAMGETPASSTHRU, &DeviceCCB);
+ if (!rcBSD)
+ {
+ if (DeviceCCB.cgdl.status != CAM_GDEVLIST_ERROR)
+ {
+ pThis->Os.ScsiBus = DeviceCCB.ccb_h.path_id;
+ pThis->Os.ScsiTargetID = DeviceCCB.ccb_h.target_id;
+ pThis->Os.ScsiLunID = DeviceCCB.ccb_h.target_lun;
+ pThis->Os.hFileDevice = hPassthroughDevice;
+ }
+ else
+ {
+ /* The passthrough device wasn't found. */
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ if (RT_FAILURE(rc))
+ RTFileClose(hPassthroughDevice);
+ }
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ RTFileClose(hFileDevice);
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
+{
+ if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ return true;
+
+ AssertMsgFailed(("FreeBSD supports only CD/DVD host drive access\n"));
+ return false;
+}
+
+
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+ if ( pThis->fLocked
+ && pThis->Os.hFileDevice != NIL_RTFILE
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ if (pThis->Os.hFileDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->Os.hFileDevice);
+ AssertRC(rc);
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ }
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase-linux.cpp b/src/VBox/Devices/Storage/DrvHostBase-linux.cpp
new file mode 100644
index 00000000..265e93dc
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-linux.cpp
@@ -0,0 +1,389 @@
+/* $Id: DrvHostBase-linux.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Linux specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <errno.h>
+#include <linux/version.h>
+/* All the following crap is apparently not necessary anymore since Linux
+ * version 2.6.29. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
+/* This is a hack to work around conflicts between these linux kernel headers
+ * and the GLIBC tcpip headers. They have different declarations of the 4
+ * standard byte order functions. */
+# define _LINUX_BYTEORDER_GENERIC_H
+/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
+/* Those macros that are needed are defined in the header below. */
+# include "swab.h"
+#endif
+#include <linux/fd.h>
+#include <linux/cdrom.h>
+#include <limits.h>
+
+#include <iprt/mem.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/scsi.h>
+
+
+/**
+ * Host backend specific data (needed by DrvHostBase.h).
+ */
+typedef struct DRVHOSTBASEOS
+{
+ /** The filehandle of the device. */
+ RTFILE hFileDevice;
+ /** Double buffer required for ioctl with the Linux kernel as long as we use
+ * remap_pfn_range() instead of vm_insert_page(). */
+ uint8_t *pbDoubleBuffer;
+ /** Previous disk inserted indicator for the media polling on floppy drives. */
+ bool fPrevDiskIn;
+} DRVHOSTBASEOS;
+/** Pointer to the host backend specific data. */
+typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
+AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
+
+#define DRVHOSTBASE_OS_INT_DECLARED
+#include "DrvHostBase.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum buffer size supported by the kernel interface. */
+#define LNX_SCSI_MAX_BUFFER_SIZE (100 * _1K)
+
+
+
+
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
+ Assert(pbSense || !cbSense); RT_NOREF(cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1);
+
+ /* Allocate the temporary buffer lazily. */
+ if(RT_UNLIKELY(!pThis->Os.pbDoubleBuffer))
+ {
+ pThis->Os.pbDoubleBuffer = (uint8_t *)RTMemAlloc(LNX_SCSI_MAX_BUFFER_SIZE);
+ if (!pThis->Os.pbDoubleBuffer)
+ return VERR_NO_MEMORY;
+ }
+
+ int rc = VERR_GENERAL_FAILURE;
+ int direction;
+ struct cdrom_generic_command cgc;
+
+ switch (enmTxDir)
+ {
+ case PDMMEDIATXDIR_NONE:
+ Assert(*pcbBuf == 0);
+ direction = CGC_DATA_NONE;
+ break;
+ case PDMMEDIATXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ Assert(*pcbBuf <= LNX_SCSI_MAX_BUFFER_SIZE);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pThis->Os.pbDoubleBuffer, '\0', *pcbBuf);
+ direction = CGC_DATA_READ;
+ break;
+ case PDMMEDIATXDIR_TO_DEVICE:
+ Assert(*pcbBuf != 0);
+ Assert(*pcbBuf <= LNX_SCSI_MAX_BUFFER_SIZE);
+ memcpy(pThis->Os.pbDoubleBuffer, pvBuf, *pcbBuf);
+ direction = CGC_DATA_WRITE;
+ break;
+ default:
+ AssertMsgFailed(("enmTxDir invalid!\n"));
+ direction = CGC_DATA_NONE;
+ }
+ memset(&cgc, '\0', sizeof(cgc));
+ memcpy(cgc.cmd, pbCmd, RT_MIN(CDROM_PACKET_SIZE, cbCmd));
+ cgc.buffer = (unsigned char *)pThis->Os.pbDoubleBuffer;
+ cgc.buflen = *pcbBuf;
+ cgc.stat = 0;
+ Assert(cbSense >= sizeof(struct request_sense));
+ cgc.sense = (struct request_sense *)pbSense;
+ cgc.data_direction = direction;
+ cgc.quiet = false;
+ cgc.timeout = cTimeoutMillies;
+ rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_SEND_PACKET, &cgc);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
+ cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
+ Log2(("%s: error status %d, rc=%Rrc\n", __FUNCTION__, cgc.stat, rc));
+ }
+ }
+ switch (enmTxDir)
+ {
+ case PDMMEDIATXDIR_FROM_DEVICE:
+ memcpy(pvBuf, pThis->Os.pbDoubleBuffer, *pcbBuf);
+ break;
+ default:
+ ;
+ }
+ Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
+ /* The value of cgc.buflen does not reliably reflect the actual amount
+ * of data transferred (for packet commands with little data transfer
+ * it's 0). So just assume that everything worked ok. */
+
+ return rc;
+}
+
+
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+
+ return LNX_SCSI_MAX_BUFFER_SIZE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ int rc = VERR_INVALID_STATE;
+
+ if (PDMMEDIATYPE_IS_FLOPPY(pThis->enmType))
+ {
+ rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDFLUSH);
+ if (rc)
+ {
+ rc = RTErrConvertFromErrno (errno);
+ Log(("DrvHostFloppy: FDFLUSH ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
+ return rc;
+ }
+
+ floppy_drive_struct DrvStat;
+ rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDGETDRVSTAT, &DrvStat);
+ if (rc)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ Log(("DrvHostFloppy: FDGETDRVSTAT ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
+ return rc;
+ }
+ pThis->fReadOnly = !(DrvStat.flags & FD_DISK_WRITABLE);
+ rc = RTFileSeek(pThis->Os.hFileDevice, 0, RTFILE_SEEK_END, pcb);
+ }
+ else if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ {
+ /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
+ ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT);
+ rc = RTFileSeek(pThis->Os.hFileDevice, 0, RTFILE_SEEK_END, pcb);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ return RTFileReadAt(pThis->Os.hFileDevice, off, pvBuf, cbRead, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ return RTFileWriteAt(pThis->Os.hFileDevice, off, pvBuf, cbWrite, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
+{
+ return RTFileFlush(pThis->Os.hFileDevice);
+}
+
+
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
+{
+ int rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_LOCKDOOR, (int)fLock);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_ACCESS_DENIED;
+ else if (errno == EDRIVE_CANT_DO_THIS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
+{
+ int rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROMEJECT, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
+{
+ int rc = VINF_SUCCESS;
+
+ if (PDMMEDIATYPE_IS_FLOPPY(pThis->enmType))
+ {
+ floppy_drive_struct DrvStat;
+ int rcLnx = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDPOLLDRVSTAT, &DrvStat);
+ if (!rcLnx)
+ {
+ bool fDiskIn = !(DrvStat.flags & (FD_VERIFY | FD_DISK_NEWCHANGE));
+ *pfMediaPresent = fDiskIn;
+
+ if (fDiskIn != pThis->Os.fPrevDiskIn)
+ *pfMediaChanged = true;
+
+ pThis->Os.fPrevDiskIn = fDiskIn;
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else
+ {
+ *pfMediaPresent = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
+ *pfMediaChanged = false;
+ if (pThis->fMediaPresent != *pfMediaPresent)
+ *pfMediaChanged = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
+{
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ pThis->Os.pbDoubleBuffer = NULL;
+ pThis->Os.fPrevDiskIn = false;
+}
+
+
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
+{
+ uint32_t fFlags = (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE) | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK;
+ return RTFileOpen(&pThis->Os.hFileDevice, pThis->pszDevice, fFlags);
+}
+
+
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Need to re-open the device because it will kill off any cached data
+ * that Linux for some peculiar reason thinks should survive a media change.
+ */
+ if (pThis->Os.hFileDevice != NIL_RTFILE)
+ {
+ RTFileClose(pThis->Os.hFileDevice);
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ }
+ int rc = drvHostBaseOpenOs(pThis, pThis->fReadOnlyConfig);
+ if (RT_FAILURE(rc))
+ {
+ if (!pThis->fReadOnlyConfig)
+ {
+ LogFlow(("%s-%d: drvHostBaseMediaRefreshOs: '%s' - retry readonly (%Rrc)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
+ rc = drvHostBaseOpenOs(pThis, true);
+ }
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("%s-%d: failed to open device '%s', rc=%Rrc\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
+ return rc;
+ }
+ pThis->fReadOnly = true;
+ }
+ else
+ pThis->fReadOnly = pThis->fReadOnlyConfig;
+
+ return rc;
+}
+
+
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+ return true; /* On Linux we always use media polling. */
+}
+
+
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+ if ( pThis->fLocked
+ && pThis->Os.hFileDevice != NIL_RTFILE
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ if (pThis->Os.pbDoubleBuffer)
+ {
+ RTMemFree(pThis->Os.pbDoubleBuffer);
+ pThis->Os.pbDoubleBuffer = NULL;
+ }
+
+ if (pThis->Os.hFileDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->Os.hFileDevice);
+ AssertRC(rc);
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ }
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase-solaris.cpp b/src/VBox/Devices/Storage/DrvHostBase-solaris.cpp
new file mode 100644
index 00000000..78ebabf6
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-solaris.cpp
@@ -0,0 +1,421 @@
+/* $Id: DrvHostBase-solaris.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Solaris specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#include <fcntl.h>
+#include <errno.h>
+#include <stropts.h>
+#include <malloc.h>
+#include <sys/dkio.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <syslog.h>
+#ifdef VBOX_WITH_SUID_WRAPPER
+# include <auth_attr.h>
+#endif
+#include <sys/sockio.h>
+#include <sys/scsi/scsi.h>
+
+extern "C" char *getfullblkname(char *);
+
+#include <VBox/err.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+/**
+ * Host backend specific data.
+ */
+typedef struct DRVHOSTBASEOS
+{
+ /** The filehandle of the device. */
+ RTFILE hFileDevice;
+ /** The raw filehandle of the device. */
+ RTFILE hFileRawDevice;
+ /** Device name of raw device (RTStrFree). */
+ char *pszRawDeviceOpen;
+} DRVHOSTBASEOS;
+/** Pointer to the host backend specific data. */
+typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
+
+//AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
+
+#define DRVHOSTBASE_OS_INT_DECLARED
+#include "DrvHostBase.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum buffer size we support, check whether darwin has some real upper limit. */
+#define SOL_SCSI_MAX_BUFFER_SIZE (100 * _1K)
+
+
+#ifdef VBOX_WITH_SUID_WRAPPER
+/* These functions would have to go into a separate solaris binary with
+ * the setuid permission set, which would run the user-SCSI ioctl and
+ * return the value. BUT... this might be prohibitively slow.
+ */
+
+/**
+ * Checks if the current user is authorized using Solaris' role-based access control.
+ * Made as a separate function with so that it need not be invoked each time we need
+ * to gain root access.
+ *
+ * @returns VBox error code.
+ */
+static int solarisCheckUserAuth()
+{
+ /* Uses Solaris' role-based access control (RBAC).*/
+ struct passwd *pPass = getpwuid(getuid());
+ if (pPass == NULL || chkauthattr("solaris.device.cdrw", pPass->pw_name) == 0)
+ return VERR_PERMISSION_DENIED;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Setuid wrapper to gain root access.
+ *
+ * @returns VBox error code.
+ * @param pEffUserID Pointer to effective user ID.
+ */
+static int solarisEnterRootMode(uid_t *pEffUserID)
+{
+ /* Increase privilege if required */
+ if (*pEffUserID != 0)
+ {
+ if (seteuid(0) == 0)
+ {
+ *pEffUserID = 0;
+ return VINF_SUCCESS;
+ }
+ return VERR_PERMISSION_DENIED;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Setuid wrapper to relinquish root access.
+ *
+ * @returns VBox error code.
+ * @param pEffUserID Pointer to effective user ID.
+ */
+static int solarisExitRootMode(uid_t *pEffUserID)
+{
+ /* Get back to user mode. */
+ if (*pEffUserID == 0)
+ {
+ uid_t realID = getuid();
+ if (seteuid(realID) == 0)
+ {
+ *pEffUserID = realID;
+ return VINF_SUCCESS;
+ }
+ return VERR_PERMISSION_DENIED;
+ }
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_WITH_SUID_WRAPPER */
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
+ Assert(pbSense || !cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1);
+
+ int rc = VERR_GENERAL_FAILURE;
+ struct uscsi_cmd usc;
+ union scsi_cdb scdb;
+ memset(&usc, 0, sizeof(struct uscsi_cmd));
+ memset(&scdb, 0, sizeof(scdb));
+
+ switch (enmTxDir)
+ {
+ case PDMMEDIATXDIR_NONE:
+ Assert(*pcbBuf == 0);
+ usc.uscsi_flags = USCSI_READ;
+ /* nothing to do */
+ break;
+
+ case PDMMEDIATXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pvBuf, '\0', *pcbBuf);
+ usc.uscsi_flags = USCSI_READ;
+ break;
+ case PDMMEDIATXDIR_TO_DEVICE:
+ Assert(*pcbBuf != 0);
+ usc.uscsi_flags = USCSI_WRITE;
+ break;
+ default:
+ AssertMsgFailedReturn(("%d\n", enmTxDir), VERR_INTERNAL_ERROR);
+ }
+ usc.uscsi_flags |= USCSI_RQENABLE;
+ usc.uscsi_rqbuf = (char *)pbSense;
+ usc.uscsi_rqlen = cbSense;
+ usc.uscsi_cdb = (caddr_t)&scdb;
+ usc.uscsi_cdblen = 12;
+ memcpy (usc.uscsi_cdb, pbCmd, usc.uscsi_cdblen);
+ usc.uscsi_bufaddr = (caddr_t)pvBuf;
+ usc.uscsi_buflen = *pcbBuf;
+ usc.uscsi_timeout = (cTimeoutMillies + 999) / 1000;
+
+ /* We need root privileges for user-SCSI under Solaris. */
+#ifdef VBOX_WITH_SUID_WRAPPER
+ uid_t effUserID = geteuid();
+ solarisEnterRootMode(&effUserID); /** @todo check return code when this really works. */
+#endif
+ rc = ioctl(RTFileToNative(pThis->Os.hFileRawDevice), USCSICMD, &usc);
+#ifdef VBOX_WITH_SUID_WRAPPER
+ solarisExitRootMode(&effUserID);
+#endif
+ if (rc < 0)
+ {
+ if (errno == EPERM)
+ return VERR_PERMISSION_DENIED;
+ if (usc.uscsi_status)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ Log2(("%s: error status. rc=%Rrc\n", __FUNCTION__, rc));
+ }
+ }
+ Log2(("%s: after ioctl: residual buflen=%d original buflen=%d\n", __FUNCTION__, usc.uscsi_resid, usc.uscsi_buflen));
+
+ return rc;
+}
+
+
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+
+ return SOL_SCSI_MAX_BUFFER_SIZE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ /*
+ * Sun docs suggests using DKIOCGGEOM instead of DKIOCGMEDIAINFO, but
+ * Sun themselves use DKIOCGMEDIAINFO for DVDs/CDs, and use DKIOCGGEOM
+ * for secondary storage devices.
+ */
+ struct dk_minfo MediaInfo;
+ if (ioctl(RTFileToNative(pThis->Os.hFileRawDevice), DKIOCGMEDIAINFO, &MediaInfo) == 0)
+ {
+ *pcb = MediaInfo.dki_capacity * (uint64_t)MediaInfo.dki_lbsize;
+ return VINF_SUCCESS;
+ }
+ return RTFileSeek(pThis->Os.hFileDevice, 0, RTFILE_SEEK_END, pcb);
+}
+
+
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ return RTFileReadAt(pThis->Os.hFileDevice, off, pvBuf, cbRead, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ return RTFileWriteAt(pThis->Os.hFileDevice, off, pvBuf, cbWrite, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
+{
+ return RTFileFlush(pThis->Os.hFileDevice);
+}
+
+
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
+{
+ int rc = ioctl(RTFileToNative(pThis->Os.hFileRawDevice), fLock ? DKIOCLOCK : DKIOCUNLOCK, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_ACCESS_DENIED;
+ else if (errno == ENOTSUP || errno == ENOSYS)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
+{
+ int rc = ioctl(RTFileToNative(pThis->Os.hFileRawDevice), DKIOCEJECT, 0);
+ if (rc < 0)
+ {
+ if (errno == EBUSY)
+ rc = VERR_PDM_MEDIA_LOCKED;
+ else if (errno == ENOSYS || errno == ENOTSUP)
+ rc = VERR_NOT_SUPPORTED;
+ else if (errno == ENODEV)
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
+{
+ *pfMediaPresent = false;
+ *pfMediaChanged = false;
+
+ /* Need to pass the previous state and DKIO_NONE for the first time. */
+ static dkio_state s_DeviceState = DKIO_NONE;
+ dkio_state PreviousState = s_DeviceState;
+ int rc = ioctl(RTFileToNative(pThis->Os.hFileRawDevice), DKIOCSTATE, &s_DeviceState);
+ if (rc == 0)
+ {
+ *pfMediaPresent = (s_DeviceState == DKIO_INSERTED);
+ if (PreviousState != s_DeviceState)
+ *pfMediaChanged = true;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
+{
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ pThis->Os.hFileRawDevice = NIL_RTFILE;
+ pThis->Os.pszRawDeviceOpen = NULL;
+}
+
+
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
+{
+#ifdef VBOX_WITH_SUID_WRAPPER /* Solaris setuid for Passthrough mode. */
+ if ( (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ && pThis->IMedia.pfnSendCmd)
+ {
+ rc = solarisCheckUserAuth();
+ if (RT_FAILURE(rc))
+ {
+ Log(("DVD: solarisCheckUserAuth failed. Permission denied!\n"));
+ return rc;
+ }
+ }
+#endif /* VBOX_WITH_SUID_WRAPPER */
+
+ char *pszBlockDevName = getfullblkname(pThis->pszDevice);
+ if (!pszBlockDevName)
+ return VERR_NO_MEMORY;
+ pThis->pszDeviceOpen = RTStrDup(pszBlockDevName); /* for RTStrFree() */
+ free(pszBlockDevName);
+ pThis->Os.pszRawDeviceOpen = RTStrDup(pThis->pszDevice);
+ if (!pThis->pszDeviceOpen || !pThis->Os.pszRawDeviceOpen)
+ return VERR_NO_MEMORY;
+
+ unsigned fFlags = (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE)
+ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK;
+ int rc = RTFileOpen(&pThis->Os.hFileDevice, pThis->pszDeviceOpen, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&pThis->Os.hFileRawDevice, pThis->Os.pszRawDeviceOpen, fFlags);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ LogRel(("DVD: failed to open device %s rc=%Rrc\n", pThis->Os.pszRawDeviceOpen, rc));
+ RTFileClose(pThis->Os.hFileDevice);
+ }
+ else
+ LogRel(("DVD: failed to open device %s rc=%Rrc\n", pThis->pszDeviceOpen, rc));
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
+{
+ if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ return true;
+
+ AssertMsgFailed(("Solaris supports only CD/DVD host drive access\n"));
+ return false;
+}
+
+
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+ if ( pThis->fLocked
+ && pThis->Os.hFileDevice != NIL_RTFILE
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ if (pThis->Os.hFileDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->Os.hFileDevice);
+ AssertRC(rc);
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ }
+
+ if (pThis->Os.hFileRawDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->Os.hFileRawDevice);
+ AssertRC(rc);
+ pThis->Os.hFileRawDevice = NIL_RTFILE;
+ }
+
+ if (pThis->Os.pszRawDeviceOpen)
+ {
+ RTStrFree(pThis->Os.pszRawDeviceOpen);
+ pThis->Os.pszRawDeviceOpen = NULL;
+ }
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase-win.cpp b/src/VBox/Devices/Storage/DrvHostBase-win.cpp
new file mode 100644
index 00000000..bfb19c77
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-win.cpp
@@ -0,0 +1,558 @@
+/* $Id: DrvHostBase-win.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Windows specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+#include <iprt/nt/nt-and-windows.h>
+#include <dbt.h>
+#include <ntddscsi.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/scsi.h>
+
+/**
+ * Host backend specific data.
+ */
+typedef struct DRVHOSTBASEOS
+{
+ /** The filehandle of the device. */
+ RTFILE hFileDevice;
+ /** Handle to the window we use to catch the device change broadcast messages. */
+ volatile HWND hwndDeviceChange;
+ /** The unit mask. */
+ DWORD fUnitMask;
+ /** Handle of the poller thread. */
+ RTTHREAD hThrdMediaChange;
+} DRVHOSTBASEOS;
+/** Pointer to the host backend specific data. */
+typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
+AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
+
+#define DRVHOSTBASE_OS_INT_DECLARED
+#include "DrvHostBase.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum buffer size we support, check whether darwin has some real upper limit. */
+#define WIN_SCSI_MAX_BUFFER_SIZE (100 * _1K)
+
+
+
+/**
+ * Window procedure for the invisible window used to catch the WM_DEVICECHANGE broadcasts.
+ */
+static LRESULT CALLBACK DeviceChangeWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ Log2(("DeviceChangeWindowProc: hwnd=%08x uMsg=%08x\n", hwnd, uMsg));
+ if (uMsg == WM_DESTROY)
+ {
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (pThis)
+ ASMAtomicXchgSize(&pThis->Os.hwndDeviceChange, NULL);
+ PostQuitMessage(0);
+ }
+
+ if (uMsg != WM_DEVICECHANGE)
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+
+ PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ Assert(pThis);
+ if (pThis == NULL)
+ return 0;
+
+ switch (wParam)
+ {
+ case DBT_DEVICEARRIVAL:
+ case DBT_DEVICEREMOVECOMPLETE:
+ // Check whether a CD or DVD was inserted into or removed from a drive.
+ if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
+ {
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
+ if ( (lpdbv->dbcv_flags & DBTF_MEDIA)
+ && (pThis->Os.fUnitMask & lpdbv->dbcv_unitmask))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ if (wParam == DBT_DEVICEARRIVAL)
+ {
+ int cRetries = 10;
+ int rc = DRVHostBaseMediaPresent(pThis);
+ while (RT_FAILURE(rc) && cRetries-- > 0)
+ {
+ RTThreadSleep(50);
+ rc = DRVHostBaseMediaPresent(pThis);
+ }
+ }
+ else
+ DRVHostBaseMediaNotPresent(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ }
+ }
+ break;
+ }
+ return TRUE;
+}
+
+
+/**
+ * This thread will wait for changed media notificatons.
+ *
+ * @returns Ignored.
+ * @param ThreadSelf Handle of this thread. Ignored.
+ * @param pvUser Pointer to the driver instance structure.
+ */
+static DECLCALLBACK(int) drvHostBaseMediaThreadWin(RTTHREAD ThreadSelf, void *pvUser)
+{
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)pvUser;
+ LogFlow(("%s-%d: drvHostBaseMediaThreadWin: ThreadSelf=%p pvUser=%p\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, ThreadSelf, pvUser));
+ static WNDCLASS s_classDeviceChange = {0};
+ static ATOM s_hAtomDeviceChange = 0;
+
+ /*
+ * Register custom window class.
+ */
+ if (s_hAtomDeviceChange == 0)
+ {
+ memset(&s_classDeviceChange, 0, sizeof(s_classDeviceChange));
+ s_classDeviceChange.lpfnWndProc = DeviceChangeWindowProc;
+ s_classDeviceChange.lpszClassName = "VBOX_DeviceChangeClass";
+ s_classDeviceChange.hInstance = GetModuleHandle("VBoxDD.dll");
+ Assert(s_classDeviceChange.hInstance);
+ s_hAtomDeviceChange = RegisterClassA(&s_classDeviceChange);
+ Assert(s_hAtomDeviceChange);
+ }
+
+ /*
+ * Create Window w/ the pThis as user data.
+ */
+ HWND hwnd = CreateWindow((LPCTSTR)s_hAtomDeviceChange, "", WS_POPUP, 0, 0, 0, 0, 0, 0, s_classDeviceChange.hInstance, 0);
+ AssertMsg(hwnd, ("CreateWindow failed with %d\n", GetLastError()));
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
+
+ /*
+ * Signal the waiting EMT thread that everything went fine.
+ */
+ ASMAtomicXchgPtr((void * volatile *)&pThis->Os.hwndDeviceChange, hwnd);
+ RTThreadUserSignal(ThreadSelf);
+ if (!hwnd)
+ {
+ LogFlow(("%s-%d: drvHostBaseMediaThreadWin: returns VERR_GENERAL_FAILURE\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ return VERR_GENERAL_FAILURE;
+ }
+ LogFlow(("%s-%d: drvHostBaseMediaThreadWin: Created hwndDeviceChange=%p\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, hwnd));
+
+ /*
+ * Message pump.
+ */
+ MSG Msg;
+ BOOL fRet;
+ while ((fRet = GetMessage(&Msg, NULL, 0, 0)) != FALSE)
+ {
+ if (fRet != -1)
+ {
+ TranslateMessage(&Msg);
+ DispatchMessage(&Msg);
+ }
+ //else: handle the error and possibly exit
+ }
+ Assert(!pThis->Os.hwndDeviceChange);
+ /* (Don't clear the thread handle here, the destructor thread is using it to wait.) */
+ LogFlow(("%s-%d: drvHostBaseMediaThreadWin: returns VINF_SUCCESS\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ /*
+ * Minimal input validation.
+ */
+ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ Assert(!pvBuf || pcbBuf);
+ Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
+ Assert(pbSense || !cbSense);
+ AssertPtr(pbCmd);
+ Assert(cbCmd <= 16 && cbCmd >= 1); RT_NOREF(cbCmd);
+
+ int rc = VERR_GENERAL_FAILURE;
+ int direction;
+ struct _REQ
+ {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ uint8_t aSense[64];
+ } Req;
+ DWORD cbReturned = 0;
+
+ switch (enmTxDir)
+ {
+ case PDMMEDIATXDIR_NONE:
+ direction = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case PDMMEDIATXDIR_FROM_DEVICE:
+ Assert(*pcbBuf != 0);
+ /* Make sure that the buffer is clear for commands reading
+ * data. The actually received data may be shorter than what
+ * we expect, and due to the unreliable feedback about how much
+ * data the ioctl actually transferred, it's impossible to
+ * prevent that. Returning previous buffer contents may cause
+ * security problems inside the guest OS, if users can issue
+ * commands to the CDROM device. */
+ memset(pvBuf, '\0', *pcbBuf);
+ direction = SCSI_IOCTL_DATA_IN;
+ break;
+ case PDMMEDIATXDIR_TO_DEVICE:
+ direction = SCSI_IOCTL_DATA_OUT;
+ break;
+ default:
+ AssertMsgFailed(("enmTxDir invalid!\n"));
+ direction = SCSI_IOCTL_DATA_UNSPECIFIED;
+ }
+ memset(&Req, '\0', sizeof(Req));
+ Req.spt.Length = sizeof(Req.spt);
+ Req.spt.CdbLength = 12;
+ memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
+ Req.spt.DataBuffer = pvBuf;
+ Req.spt.DataTransferLength = *pcbBuf;
+ Req.spt.DataIn = direction;
+ Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
+ Assert(cbSense <= sizeof(Req.aSense));
+ Req.spt.SenseInfoLength = (UCHAR)RT_MIN(sizeof(Req.aSense), cbSense);
+ Req.spt.SenseInfoOffset = RT_UOFFSETOF(struct _REQ, aSense);
+ if (DeviceIoControl((HANDLE)RTFileToNative(pThis->Os.hFileDevice), IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
+ {
+ if (cbReturned > RT_UOFFSETOF(struct _REQ, aSense))
+ memcpy(pbSense, Req.aSense, cbSense);
+ else
+ memset(pbSense, '\0', cbSense);
+ /* Windows shares the property of not properly reflecting the actually
+ * transferred data size. See above. Assume that everything worked ok.
+ * Except if there are sense information. */
+ rc = (pbSense[2] & 0x0f) == SCSI_SENSE_NONE
+ ? VINF_SUCCESS
+ : VERR_DEV_IO_ERROR;
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
+
+ return rc;
+}
+
+
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+
+ return WIN_SCSI_MAX_BUFFER_SIZE;
+}
+
+
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ if (PDMMEDIATYPE_IS_FLOPPY(pThis->enmType))
+ {
+ DISK_GEOMETRY geom;
+ DWORD cbBytesReturned;
+ int cbSectors;
+
+ memset(&geom, 0, sizeof(geom));
+ rc = DeviceIoControl((HANDLE)RTFileToNative(pThis->Os.hFileDevice), IOCTL_DISK_GET_DRIVE_GEOMETRY,
+ NULL, 0, &geom, sizeof(geom), &cbBytesReturned, NULL);
+ if (rc) {
+ cbSectors = geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack;
+ *pcb = cbSectors * geom.BytesPerSector;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ DWORD dwLastError;
+
+ dwLastError = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastError);
+ Log(("DrvHostFloppy: IOCTL_DISK_GET_DRIVE_GEOMETRY(%s) failed, LastError=%d rc=%Rrc\n",
+ pThis->pszDevice, dwLastError, rc));
+ return rc;
+ }
+ }
+ else
+ {
+ /* use NT api, retry a few times if the media is being verified. */
+ IO_STATUS_BLOCK IoStatusBlock = {0};
+ FILE_FS_SIZE_INFORMATION FsSize = {0};
+ NTSTATUS rcNt = NtQueryVolumeInformationFile((HANDLE)RTFileToNative(pThis->Os.hFileDevice), &IoStatusBlock,
+ &FsSize, sizeof(FsSize), FileFsSizeInformation);
+ int cRetries = 5;
+ while (rcNt == STATUS_VERIFY_REQUIRED && cRetries-- > 0)
+ {
+ RTThreadSleep(10);
+ rcNt = NtQueryVolumeInformationFile((HANDLE)RTFileToNative(pThis->Os.hFileDevice), &IoStatusBlock,
+ &FsSize, sizeof(FsSize), FileFsSizeInformation);
+ }
+ if (rcNt >= 0)
+ {
+ *pcb = FsSize.TotalAllocationUnits.QuadPart * FsSize.BytesPerSector;
+ return VINF_SUCCESS;
+ }
+
+ /* convert nt status code to VBox status code. */
+ /** @todo Make conversion function!. */
+ switch (rcNt)
+ {
+ case STATUS_NO_MEDIA_IN_DEVICE: rc = VERR_MEDIA_NOT_PRESENT; break;
+ case STATUS_VERIFY_REQUIRED: rc = VERR_TRY_AGAIN; break;
+ }
+ LogFlow(("drvHostBaseGetMediaSize: NtQueryVolumeInformationFile -> %#lx\n", rcNt, rc));
+ }
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ return RTFileReadAt(pThis->Os.hFileDevice, off, pvBuf, cbRead, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ return RTFileWriteAt(pThis->Os.hFileDevice, off, pvBuf, cbWrite, NULL);
+}
+
+
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
+{
+ return RTFileFlush(pThis->Os.hFileDevice);
+}
+
+
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
+{
+ PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
+ DWORD cbReturned;
+ int rc;
+ if (DeviceIoControl((HANDLE)RTFileToNative(pThis->Os.hFileDevice), IOCTL_STORAGE_MEDIA_REMOVAL,
+ &PreventMediaRemoval, sizeof(PreventMediaRemoval),
+ NULL, 0, &cbReturned,
+ NULL))
+ rc = VINF_SUCCESS;
+ else
+ /** @todo figure out the return codes for already locked. */
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
+{
+ int rc = VINF_SUCCESS;
+ RTFILE hFileDevice = pThis->Os.hFileDevice;
+ if (hFileDevice == NIL_RTFILE) /* obsolete crap */
+ rc = RTFileOpen(&hFileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ /* do ioctl */
+ DWORD cbReturned;
+ if (DeviceIoControl((HANDLE)RTFileToNative(hFileDevice), IOCTL_STORAGE_EJECT_MEDIA,
+ NULL, 0,
+ NULL, 0, &cbReturned,
+ NULL))
+ rc = VINF_SUCCESS;
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ /* clean up handle */
+ if (hFileDevice != pThis->Os.hFileDevice)
+ RTFileClose(hFileDevice);
+ }
+ else
+ AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
+{
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ pThis->Os.hwndDeviceChange = NULL;
+ pThis->Os.hThrdMediaChange = NIL_RTTHREAD;
+}
+
+
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
+{
+ UINT uDriveType = GetDriveType(pThis->pszDevice);
+ switch (pThis->enmType)
+ {
+ case PDMMEDIATYPE_FLOPPY_360:
+ case PDMMEDIATYPE_FLOPPY_720:
+ case PDMMEDIATYPE_FLOPPY_1_20:
+ case PDMMEDIATYPE_FLOPPY_1_44:
+ case PDMMEDIATYPE_FLOPPY_2_88:
+ case PDMMEDIATYPE_FLOPPY_FAKE_15_6:
+ case PDMMEDIATYPE_FLOPPY_FAKE_63_5:
+ if (uDriveType != DRIVE_REMOVABLE)
+ {
+ AssertMsgFailed(("Configuration error: '%s' is not a floppy (type=%d)\n",
+ pThis->pszDevice, uDriveType));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+ case PDMMEDIATYPE_CDROM:
+ case PDMMEDIATYPE_DVD:
+ if (uDriveType != DRIVE_CDROM)
+ {
+ AssertMsgFailed(("Configuration error: '%s' is not a cdrom (type=%d)\n",
+ pThis->pszDevice, uDriveType));
+ return VERR_INVALID_PARAMETER;
+ }
+ break;
+ case PDMMEDIATYPE_HARD_DISK:
+ default:
+ AssertMsgFailed(("enmType=%d\n", pThis->enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ int iBit = RT_C_TO_UPPER(pThis->pszDevice[0]) - 'A';
+ if ( iBit > 'Z' - 'A'
+ || pThis->pszDevice[1] != ':'
+ || pThis->pszDevice[2])
+ {
+ AssertMsgFailed(("Configuration error: Invalid drive specification: '%s'\n", pThis->pszDevice));
+ return VERR_INVALID_PARAMETER;
+ }
+ pThis->Os.fUnitMask = 1 << iBit;
+ RTStrAPrintf(&pThis->pszDeviceOpen, "\\\\.\\%s", pThis->pszDevice);
+ if (!pThis->pszDeviceOpen)
+ return VERR_NO_MEMORY;
+
+ uint32_t fFlags = (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE) | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
+ int rc = RTFileOpen(&pThis->Os.hFileDevice, pThis->pszDeviceOpen, fFlags);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the thread which will wait for the media change events.
+ */
+ rc = RTThreadCreate(&pThis->Os.hThrdMediaChange, drvHostBaseMediaThreadWin, pThis, 0,
+ RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "DVDMEDIA");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to create poller thread. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Wait for the thread to start up (!w32:) and do one detection loop.
+ */
+ rc = RTThreadUserWait(pThis->Os.hThrdMediaChange, 10000);
+ AssertRC(rc);
+
+ if (!pThis->Os.hwndDeviceChange)
+ return VERR_GENERAL_FAILURE;
+
+ DRVHostBaseMediaPresent(pThis);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
+{
+ RT_NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
+{
+ RT_NOREF3(pThis, pfMediaChanged, pfMediaPresent); /* We don't support the polling method. */
+ return VERR_NOT_SUPPORTED;
+}
+
+
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
+{
+ /* For Windows we alwys use an internal approach. */
+ RT_NOREF(pThis);
+ return false;
+}
+
+
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
+{
+ /*
+ * Terminate the thread.
+ */
+ if (pThis->Os.hThrdMediaChange != NIL_RTTHREAD)
+ {
+ int rc;
+ int cTimes = 50;
+ do
+ {
+ if (pThis->Os.hwndDeviceChange)
+ PostMessage(pThis->Os.hwndDeviceChange, WM_CLOSE, 0, 0); /* default win proc will destroy the window */
+
+ rc = RTThreadWait(pThis->Os.hThrdMediaChange, 100, NULL);
+ } while (cTimes-- > 0 && rc == VERR_TIMEOUT);
+
+ if (RT_SUCCESS(rc))
+ pThis->Os.hThrdMediaChange = NIL_RTTHREAD;
+ }
+
+ /*
+ * Unlock the drive if we've locked it or we're in passthru mode.
+ */
+ if ( pThis->fLocked
+ && pThis->Os.hFileDevice != NIL_RTFILE
+ && pThis->pfnDoLock)
+ {
+ int rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ if (pThis->Os.hwndDeviceChange)
+ {
+ if (SetWindowLongPtr(pThis->Os.hwndDeviceChange, GWLP_USERDATA, 0) == (LONG_PTR)pThis)
+ PostMessage(pThis->Os.hwndDeviceChange, WM_CLOSE, 0, 0); /* default win proc will destroy the window */
+ pThis->Os.hwndDeviceChange = NULL;
+ }
+
+ if (pThis->Os.hFileDevice != NIL_RTFILE)
+ {
+ int rc = RTFileClose(pThis->Os.hFileDevice);
+ AssertRC(rc);
+ pThis->Os.hFileDevice = NIL_RTFILE;
+ }
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase.cpp b/src/VBox/Devices/Storage/DrvHostBase.cpp
new file mode 100644
index 00000000..ba4e6880
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.cpp
@@ -0,0 +1,1571 @@
+/* $Id: DrvHostBase.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+
+#include "DrvHostBase.h"
+
+
+
+
+/* -=-=-=-=- IBlock -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMEDIA,pfnRead} */
+static DECLCALLBACK(int) drvHostBaseRead(PPDMIMEDIA pInterface, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: drvHostBaseRead: off=%#llx pvBuf=%p cbRead=%#x (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, pvBuf, cbRead, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsRead);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+ if (pThis->fMediaPresent)
+ {
+ /*
+ * Seek and read.
+ */
+ rc = drvHostBaseReadOs(pThis, off, pvBuf, cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("%s-%d: drvHostBaseReadOs: off=%#llx cbRead=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbRead, cbRead, pvBuf));
+ }
+ else
+ Log(("%s-%d: drvHostBaseRead: drvHostBaseReadOs(%#llx, %p, %#x) -> %Rrc ('%s')\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance,
+ off, pvBuf, cbRead, rc, pThis->pszDevice));
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, cbRead);
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseRead: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnWrite} */
+static DECLCALLBACK(int) drvHostBaseWrite(PPDMIMEDIA pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: drvHostBaseWrite: off=%#llx pvBuf=%p cbWrite=%#x (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, pvBuf, cbWrite, pThis->pszDevice));
+ Log2(("%s-%d: drvHostBaseWrite: off=%#llx cbWrite=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbWrite, cbWrite, pvBuf));
+ RTCritSectEnter(&pThis->CritSect);
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsWrite);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+ if (!pThis->fReadOnly)
+ {
+ if (pThis->fMediaPresent)
+ {
+ /*
+ * Seek and write.
+ */
+ rc = drvHostBaseWriteOs(pThis, off, pvBuf, cbWrite);
+ if (RT_FAILURE(rc))
+ Log(("%s-%d: drvHostBaseWrite: drvHostBaseWriteOs(%#llx, %p, %#x) -> %Rrc ('%s')\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance,
+ off, pvBuf, cbWrite, rc, pThis->pszDevice));
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, cbWrite);
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseWrite: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnFlush} */
+static DECLCALLBACK(int) drvHostBaseFlush(PPDMIMEDIA pInterface)
+{
+ int rc;
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: drvHostBaseFlush: (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFlush);
+
+ if (pThis->fMediaPresent)
+ rc = drvHostBaseFlushOs(pThis);
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ if (RT_SUCCESS(rc))
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseFlush: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsReadOnly} */
+static DECLCALLBACK(bool) drvHostBaseIsReadOnly(PPDMIMEDIA pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ return pThis->fReadOnly;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvHostBaseIsNonRotational(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return false;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSize} */
+static DECLCALLBACK(uint64_t) drvHostBaseGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ RTCritSectEnter(&pThis->CritSect);
+
+ uint64_t cb = 0;
+ if (pThis->fMediaPresent)
+ cb = pThis->cbSize;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseGetSize: returns %llu\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, cb));
+ return cb;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetType} */
+static DECLCALLBACK(PDMMEDIATYPE) drvHostBaseGetType(PPDMIMEDIA pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: drvHostBaseGetType: returns %d\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->enmType));
+ return pThis->enmType;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetUuid} */
+static DECLCALLBACK(int) drvHostBaseGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+
+ *pUuid = pThis->Uuid;
+
+ LogFlow(("%s-%d: drvHostBaseGetUuid: returns VINF_SUCCESS *pUuid=%RTuuid\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pUuid));
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetPCHSGeometry} */
+static DECLCALLBACK(int) drvHostBaseGetPCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ if ( pThis->PCHSGeometry.cCylinders > 0
+ && pThis->PCHSGeometry.cHeads > 0
+ && pThis->PCHSGeometry.cSectors > 0)
+ {
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ }
+ else
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: %s: returns %Rrc CHS={%d,%d,%d}\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, __FUNCTION__, rc,
+ pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetPCHSGeometry} */
+static DECLCALLBACK(int) drvHostBaseSetPCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: %s: cCylinders=%d cHeads=%d cSectors=%d\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, __FUNCTION__,
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ pThis->PCHSGeometry = *pPCHSGeometry;
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetLCHSGeometry} */
+static DECLCALLBACK(int) drvHostBaseGetLCHSGeometry(PPDMIMEDIA pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ if ( pThis->LCHSGeometry.cCylinders > 0
+ && pThis->LCHSGeometry.cHeads > 0
+ && pThis->LCHSGeometry.cSectors > 0)
+ {
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ }
+ else
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ else
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: %s: returns %Rrc CHS={%d,%d,%d}\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, __FUNCTION__, rc,
+ pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetLCHSGeometry} */
+static DECLCALLBACK(int) drvHostBaseSetLCHSGeometry(PPDMIMEDIA pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ LogFlow(("%s-%d: %s: cCylinders=%d cHeads=%d cSectors=%d\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, __FUNCTION__,
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent)
+ {
+ pThis->LCHSGeometry = *pLCHSGeometry;
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ rc = VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvHostBaseIsVisible(PPDMIMEDIA pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ return pThis->fBiosVisible;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetRegionCount} */
+static DECLCALLBACK(uint32_t) drvHostBaseGetRegionCount(PPDMIMEDIA pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+
+ LogFlowFunc(("\n"));
+ uint32_t cRegions = pThis->fMediaPresent ? 1 : 0;
+
+ /* For now just return one region for all devices. */
+ /** @todo Handle CD/DVD passthrough properly. */
+
+ LogFlowFunc(("returns %u\n", cRegions));
+ return cRegions;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionProperties} */
+static DECLCALLBACK(int) drvHostBaseQueryRegionProperties(PPDMIMEDIA pInterface, uint32_t uRegion, uint64_t *pu64LbaStart,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ LogFlowFunc(("\n"));
+ int rc = VINF_SUCCESS;
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+
+ if (uRegion < 1 && pThis->fMediaPresent)
+ {
+ uint64_t cbMedia;
+ rc = drvHostBaseGetMediaSizeOs(pThis, &cbMedia);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbBlock = 0;
+
+ if (pThis->enmType == PDMMEDIATYPE_DVD)
+ cbBlock = 2048;
+ else
+ cbBlock = 512; /* Floppy. */
+
+ if (pu64LbaStart)
+ *pu64LbaStart = 0;
+ if (pcBlocks)
+ *pcBlocks = cbMedia / cbBlock;
+ if (pcbBlock)
+ *pcbBlock = cbBlock;
+ if (penmDataForm)
+ *penmDataForm = VDREGIONDATAFORM_RAW;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionPropertiesForLba} */
+static DECLCALLBACK(int) drvHostBaseQueryRegionPropertiesForLba(PPDMIMEDIA pInterface, uint64_t u64LbaStart,
+ uint32_t *puRegion, uint64_t *pcBlocks,
+ uint64_t *pcbBlock, PVDREGIONDATAFORM penmDataForm)
+{
+ LogFlowFunc(("\n"));
+ int rc = VINF_SUCCESS;
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ uint64_t cbMedia;
+ uint64_t cbBlock = 0;
+
+ if (pThis->enmType == PDMMEDIATYPE_DVD)
+ cbBlock = 2048;
+ else
+ cbBlock = 512; /* Floppy. */
+
+ rc = drvHostBaseGetMediaSizeOs(pThis, &cbMedia);
+ if ( RT_SUCCESS(rc)
+ && u64LbaStart < cbMedia / cbBlock)
+ {
+ if (puRegion)
+ *puRegion = 0;
+ if (pcBlocks)
+ *pcBlocks = cbMedia / cbBlock;
+ if (pcbBlock)
+ *pcbBlock = cbBlock;
+ if (penmDataForm)
+ *penmDataForm = VDREGIONDATAFORM_RAW;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+
+/* -=-=-=-=- IMediaEx -=-=-=-=- */
+
+DECLHIDDEN(int) drvHostBaseBufferRetain(PDRVHOSTBASE pThis, PDRVHOSTBASEREQ pReq, size_t cbBuf, bool fWrite, void **ppvBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->cbBuf < cbBuf)
+ {
+ RTMemFree(pThis->pvBuf);
+ pThis->cbBuf = 0;
+ pThis->pvBuf = RTMemAlloc(cbBuf);
+ if (pThis->pvBuf)
+ pThis->cbBuf = cbBuf;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc) && fWrite)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.pvSeg = pThis->pvBuf;
+ Seg.cbSeg = cbBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, (PDMMEDIAEXIOREQ)pReq,
+ &pReq->abAlloc[0], 0, &SgBuf, cbBuf);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppvBuf = pThis->pvBuf;
+
+ return rc;
+}
+
+DECLHIDDEN(int) drvHostBaseBufferRelease(PDRVHOSTBASE pThis, PDRVHOSTBASEREQ pReq, size_t cbBuf, bool fWrite, void *pvBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!fWrite)
+ {
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.pvSeg = pvBuf;
+ Seg.cbSeg = cbBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyFromBuf(pThis->pDrvMediaExPort, (PDMMEDIAEXIOREQ)pReq,
+ &pReq->abAlloc[0], 0, &SgBuf, cbBuf);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures} */
+static DECLCALLBACK(int) drvHostBaseQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+
+ *pfFeatures = pThis->IMediaEx.pfnIoReqSendScsiCmd ? PDMIMEDIAEX_FEATURE_F_RAWSCSICMD : 0;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnNotifySuspend} */
+static DECLCALLBACK(void) drvHostBaseNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF(pInterface); /* Nothing to do here. */
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet} */
+static DECLCALLBACK(int) drvHostBaseIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+
+ pThis->cbIoReqAlloc = RT_UOFFSETOF_DYN(DRVHOSTBASEREQ, abAlloc[cbIoReqAlloc]);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqAlloc} */
+static DECLCALLBACK(int) drvHostBaseIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ RT_NOREF2(uIoReqId, fFlags);
+
+ int rc = VINF_SUCCESS;
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)RTMemAllocZ(pThis->cbIoReqAlloc);
+ if (RT_LIKELY(pReq))
+ {
+ pReq->cbReq = 0;
+ pReq->cbResidual = 0;
+ *phIoReq = (PDMMEDIAEXIOREQ)pReq;
+ *ppvIoReqAlloc = &pReq->abAlloc[0];
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqFree} */
+static DECLCALLBACK(int) drvHostBaseIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF1(pInterface);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+
+ RTMemFree(pReq);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryResidual} */
+static DECLCALLBACK(int) drvHostBaseIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ RT_NOREF1(pInterface);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+
+ *pcbResidual = pReq->cbResidual;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize} */
+static DECLCALLBACK(int) drvHostBaseIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ RT_NOREF1(pInterface);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+
+ *pcbXfer = pReq->cbReq;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll} */
+static DECLCALLBACK(int) drvHostBaseIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel} */
+static DECLCALLBACK(int) drvHostBaseIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ RT_NOREF2(pInterface, uIoReqId);
+ return VERR_PDM_MEDIAEX_IOREQID_NOT_FOUND;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead} */
+static DECLCALLBACK(int) drvHostBaseIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+ LogFlow(("%s-%d: drvHostBaseIoReqRead: off=%#llx cbRead=%#x (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbRead, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ pReq->cbReq = cbRead;
+ pReq->cbResidual = cbRead;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsRead);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+ if (pThis->fMediaPresent)
+ {
+ void *pvBuf;
+ rc = drvHostBaseBufferRetain(pThis, pReq, cbRead, false, &pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Seek and read.
+ */
+ rc = drvHostBaseReadOs(pThis, off, pvBuf, cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("%s-%d: drvHostBaseReadOs: off=%#llx cbRead=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbRead, cbRead, pvBuf));
+
+ pReq->cbResidual = 0;
+ }
+ else
+ Log(("%s-%d: drvHostBaseIoReqRead: drvHostBaseReadOs(%#llx, %p, %#x) -> %Rrc ('%s')\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance,
+ off, pvBuf, cbRead, rc, pThis->pszDevice));
+
+ rc = drvHostBaseBufferRelease(pThis, pReq, cbRead, false, pvBuf);
+ }
+ else
+ Log(("%s-%d: drvHostBaseIoReqRead: drvHostBaseBufferRetain(%#llx, %p, %#x) -> %Rrc ('%s')\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance,
+ off, pvBuf, cbRead, rc, pThis->pszDevice));
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_INC(&pThis->StatBytesRead);
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseIoReqRead: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite} */
+static DECLCALLBACK(int) drvHostBaseIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+ LogFlow(("%s-%d: drvHostBaseIoReqWrite: off=%#llx cbWrite=%#x (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbWrite, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ pReq->cbReq = cbWrite;
+ pReq->cbResidual = cbWrite;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsWrite);
+
+ /*
+ * Check the state.
+ */
+ int rc;
+ if (!pThis->fReadOnly)
+ {
+ if (pThis->fMediaPresent)
+ {
+ void *pvBuf;
+ rc = drvHostBaseBufferRetain(pThis, pReq, cbWrite, true, &pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("%s-%d: drvHostBaseIoReqWrite: off=%#llx cbWrite=%#x\n"
+ "%16.*Rhxd\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, off, cbWrite, cbWrite, pvBuf));
+ /*
+ * Seek and write.
+ */
+ rc = drvHostBaseWriteOs(pThis, off, pvBuf, cbWrite);
+ if (RT_FAILURE(rc))
+ Log(("%s-%d: drvHostBaseIoReqWrite: drvHostBaseWriteOs(%#llx, %p, %#x) -> %Rrc ('%s')\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance,
+ off, pvBuf, cbWrite, rc, pThis->pszDevice));
+ else
+ pReq->cbResidual = 0;
+
+ rc = drvHostBaseBufferRelease(pThis, pReq, cbWrite, true, pvBuf);
+ }
+ }
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_INC(&pThis->StatBytesWritten);
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseIoReqWrite: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush} */
+static DECLCALLBACK(int) drvHostBaseIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF1(hIoReq);
+
+ int rc;
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
+ LogFlow(("%s-%d: drvHostBaseIoReqFlush: (%s)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice));
+ RTCritSectEnter(&pThis->CritSect);
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFlush);
+
+ if (pThis->fMediaPresent)
+ rc = drvHostBaseFlushOs(pThis);
+ else
+ rc = VERR_MEDIA_NOT_PRESENT;
+
+ if (RT_SUCCESS(rc))
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseFlush: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard} */
+static DECLCALLBACK(int) drvHostBaseIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ RT_NOREF3(pInterface, hIoReq, cRangesMax);
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount} */
+static DECLCALLBACK(uint32_t) drvHostBaseIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return 0;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount} */
+static DECLCALLBACK(uint32_t) drvHostBaseIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return 0;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart} */
+static DECLCALLBACK(int) drvHostBaseIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc)
+{
+ RT_NOREF3(pInterface, phIoReq, ppvIoReqAlloc);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext} */
+static DECLCALLBACK(int) drvHostBaseIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ RT_NOREF4(pInterface, hIoReq, phIoReqNext, ppvIoReqAllocNext);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave} */
+static DECLCALLBACK(int) drvHostBaseIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF3(pInterface, pSSM, hIoReq);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad} */
+static DECLCALLBACK(int) drvHostBaseIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF3(pInterface, pSSM, hIoReq);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+
+/* -=-=-=-=- IMount -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMOUNT,pfnUnmount} */
+static DECLCALLBACK(int) drvHostBaseUnmount(PPDMIMOUNT pInterface, bool fForce, bool fEject)
+{
+ RT_NOREF(fEject);
+ /* While we're not mountable (see drvHostBaseMount), we're unmountable. */
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMount);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Validate state.
+ */
+ int rc = VINF_SUCCESS;
+ if (!pThis->fLocked || fForce)
+ {
+ /* Unlock drive if necessary. */
+ if (pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+
+ if (fEject)
+ {
+ /*
+ * Eject the disc.
+ */
+ rc = drvHostBaseEjectOs(pThis);
+ }
+
+ /*
+ * Media is no longer present.
+ */
+ DRVHostBaseMediaNotPresent(pThis);
+ }
+ else
+ {
+ Log(("drvHostBaseUnmount: Locked\n"));
+ rc = VERR_PDM_MEDIA_LOCKED;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("drvHostBaseUnmount: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsMounted} */
+static DECLCALLBACK(bool) drvHostBaseIsMounted(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMount);
+ RTCritSectEnter(&pThis->CritSect);
+
+ bool fRc = pThis->fMediaPresent;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return fRc;
+}
+
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsLocked} */
+static DECLCALLBACK(int) drvHostBaseLock(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMount);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, true);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = true;
+ }
+ else
+ LogFlow(("%s-%d: drvHostBaseLock: already locked\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseLock: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsLocked} */
+static DECLCALLBACK(int) drvHostBaseUnlock(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMount);
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, false);
+ if (RT_SUCCESS(rc))
+ pThis->fLocked = false;
+ }
+ else
+ LogFlow(("%s-%d: drvHostBaseUnlock: not locked\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+
+ RTCritSectLeave(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseUnlock: returns %Rrc\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsLocked} */
+static DECLCALLBACK(bool) drvHostBaseIsLocked(PPDMIMOUNT pInterface)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMount);
+ RTCritSectEnter(&pThis->CritSect);
+
+ bool fRc = pThis->fLocked;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return fRc;
+}
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, &pThis->IMedia);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, &pThis->IMount);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, pThis->pDrvMediaExPort ? &pThis->IMediaEx : NULL);
+ return NULL;
+}
+
+
+/* -=-=-=-=- poller thread -=-=-=-=- */
+
+
+/**
+ * Media present.
+ * Query the size and notify the above driver / device.
+ *
+ * @param pThis The instance data.
+ */
+DECLHIDDEN(int) DRVHostBaseMediaPresent(PDRVHOSTBASE pThis)
+{
+ /*
+ * Open the drive.
+ */
+ int rc = drvHostBaseMediaRefreshOs(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Determine the size.
+ */
+ uint64_t cb;
+ rc = drvHostBaseGetMediaSizeOs(pThis, &cb);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("%s-%d: failed to figure media size of %s, rc=%Rrc\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
+ return rc;
+ }
+
+ /*
+ * Update the data and inform the unit.
+ */
+ pThis->cbSize = cb;
+ pThis->fMediaPresent = true;
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+ LogFlow(("%s-%d: drvHostBaseMediaPresent: cbSize=%lld (%#llx)\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->cbSize, pThis->cbSize));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Media no longer present.
+ * @param pThis The instance data.
+ */
+DECLHIDDEN(void) DRVHostBaseMediaNotPresent(PDRVHOSTBASE pThis)
+{
+ pThis->fMediaPresent = false;
+ pThis->fLocked = false;
+ pThis->PCHSGeometry.cCylinders = 0;
+ pThis->PCHSGeometry.cHeads = 0;
+ pThis->PCHSGeometry.cSectors = 0;
+ pThis->LCHSGeometry.cCylinders = 0;
+ pThis->LCHSGeometry.cHeads = 0;
+ pThis->LCHSGeometry.cSectors = 0;
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+}
+
+
+static int drvHostBaseMediaPoll(PDRVHOSTBASE pThis)
+{
+ /*
+ * Poll for media change.
+ */
+ bool fMediaPresent = false;
+ bool fMediaChanged = false;
+ drvHostBaseQueryMediaStatusOs(pThis, &fMediaChanged, &fMediaPresent);
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fMediaPresent != fMediaPresent)
+ {
+ LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
+ pThis->fMediaPresent = false;
+ if (fMediaPresent)
+ rc = DRVHostBaseMediaPresent(pThis);
+ else
+ DRVHostBaseMediaNotPresent(pThis);
+ }
+ else if (fMediaPresent)
+ {
+ /*
+ * Poll for media change.
+ */
+ if (fMediaChanged)
+ {
+ LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
+ DRVHostBaseMediaNotPresent(pThis);
+ rc = DRVHostBaseMediaPresent(pThis);
+ }
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * This thread will periodically poll the device for media presence.
+ *
+ * @returns Ignored.
+ * @param ThreadSelf Handle of this thread. Ignored.
+ * @param pvUser Pointer to the driver instance structure.
+ */
+static DECLCALLBACK(int) drvHostBaseMediaThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ PDRVHOSTBASE pThis = (PDRVHOSTBASE)pvUser;
+ LogFlow(("%s-%d: drvHostBaseMediaThread: ThreadSelf=%p pvUser=%p\n",
+ pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, ThreadSelf, pvUser));
+ bool fFirst = true;
+ int cRetries = 10;
+ while (!pThis->fShutdownPoller)
+ {
+ /*
+ * Perform the polling (unless we've run out of 50ms retries).
+ */
+ if (cRetries-- > 0)
+ {
+
+ int rc = drvHostBaseMediaPoll(pThis);
+ if (RT_FAILURE(rc))
+ {
+ RTSemEventWait(pThis->EventPoller, 50);
+ continue;
+ }
+ }
+
+ /*
+ * Signal EMT after the first go.
+ */
+ if (fFirst)
+ {
+ RTThreadUserSignal(ThreadSelf);
+ fFirst = false;
+ }
+
+ /*
+ * Sleep.
+ */
+ int rc = RTSemEventWait(pThis->EventPoller, pThis->cMilliesPoller);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TIMEOUT)
+ {
+ AssertMsgFailed(("rc=%Rrc\n", rc));
+ pThis->ThreadPoller = NIL_RTTHREAD;
+ LogFlow(("drvHostBaseMediaThread: returns %Rrc\n", rc));
+ return rc;
+ }
+ cRetries = 10;
+ }
+
+ /* (Don't clear the thread handle here, the destructor thread is using it to wait.) */
+ LogFlow(("%s-%d: drvHostBaseMediaThread: returns VINF_SUCCESS\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers statistics associated with the given media driver.
+ *
+ * @returns VBox status code.
+ * @param pThis The media driver instance.
+ */
+static int drvHostBaseStatsRegister(PDRVHOSTBASE pThis)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns;
+ uint32_t iInstance, iLUN;
+ const char *pcszController;
+
+ int rc = pThis->pDrvMediaPort->pfnQueryDeviceLocation(pThis->pDrvMediaPort, &pcszController,
+ &iInstance, &iLUN);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCtrlUpper = RTStrDup(pcszController);
+ if (pszCtrlUpper)
+ {
+ RTStrToUpper(pszCtrlUpper);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data read.", "/Devices/%s%u/Port%u/ReadBytes", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data written.", "/Devices/%s%u/Port%u/WrittenBytes", pszCtrlUpper, iInstance, iLUN);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsSubmitted", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests failed.", "/Devices/%s%u/Port%u/ReqsFailed", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSucceeded, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests succeeded.", "/Devices/%s%u/Port%u/ReqsSucceeded", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFlush, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of flush I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsFlush", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsWrite, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of write I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsWrite", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of read I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsRead", pszCtrlUpper, iInstance, iLUN);
+
+ RTStrFree(pszCtrlUpper);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Deregisters statistics associated with the given media driver.
+ *
+ * @returns nothing.
+ * @param pThis The media driver instance.
+ */
+static void drvhostBaseStatsDeregister(PDRVHOSTBASE pThis)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns;
+
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatBytesRead);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatBytesWritten);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsSubmitted);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsFailed);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsSucceeded);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsFlush);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsWrite);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsRead);
+}
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+
+/**
+ * Done state load operation.
+ *
+ * @returns VBox load code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) drvHostBaseLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: drvHostBaseMediaThread:\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Tell the device/driver above us that the media status is uncertain.
+ */
+ if (pThis->pDrvMountNotify)
+ {
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+ if (pThis->fMediaPresent)
+ pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc FNPDMDRVDESTRUCT */
+DECLCALLBACK(void) DRVHostBaseDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: drvHostBaseDestruct: iInstance=%d\n", pDrvIns->pReg->szName, pDrvIns->iInstance, pDrvIns->iInstance));
+
+ /*
+ * Terminate the thread.
+ */
+ if (pThis->ThreadPoller != NIL_RTTHREAD)
+ {
+ pThis->fShutdownPoller = true;
+ int rc;
+ int cTimes = 50;
+ do
+ {
+ RTSemEventSignal(pThis->EventPoller);
+ rc = RTThreadWait(pThis->ThreadPoller, 100, NULL);
+ } while (cTimes-- > 0 && rc == VERR_TIMEOUT);
+
+ if (!rc)
+ pThis->ThreadPoller = NIL_RTTHREAD;
+ }
+
+ /*
+ * Cleanup the other resources.
+ */
+ drvHostBaseDestructOs(pThis);
+
+ if (pThis->EventPoller != NULL)
+ {
+ RTSemEventDestroy(pThis->EventPoller);
+ pThis->EventPoller = NULL;
+ }
+
+ if (pThis->pszDevice)
+ {
+ MMR3HeapFree(pThis->pszDevice);
+ pThis->pszDevice = NULL;
+ }
+
+ if (pThis->pszDeviceOpen)
+ {
+ RTStrFree(pThis->pszDeviceOpen);
+ pThis->pszDeviceOpen = NULL;
+ }
+
+ if (pThis->pvBuf)
+ {
+ RTMemFree(pThis->pvBuf);
+ pThis->pvBuf = NULL;
+ pThis->cbBuf = 0;
+ }
+
+ /* Forget about the notifications. */
+ pThis->pDrvMountNotify = NULL;
+
+ drvhostBaseStatsDeregister(pThis);
+
+ /* Leave the instance operational if this is just a cleanup of the state
+ * after an attach error happened. So don't destroy the critsect then. */
+ if (!pThis->fKeepInstance && RTCritSectIsInitialized(&pThis->CritSect))
+ RTCritSectDelete(&pThis->CritSect);
+ LogFlow(("%s-%d: drvHostBaseDestruct completed\n", pDrvIns->pReg->szName, pDrvIns->iInstance));
+}
+
+
+/**
+ * Initializes the instance data .
+ *
+ * On failure call DRVHostBaseDestruct().
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance.
+ * @param pszCfgValid Pointer to a string of valid CFGM options.
+ * @param pCfg Configuration handle.
+ * @param enmType Device type.
+ */
+DECLHIDDEN(int) DRVHostBaseInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, const char *pszCfgValid, PDMMEDIATYPE enmType)
+{
+ int src = VINF_SUCCESS;
+ PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
+ LogFlow(("%s-%d: DRVHostBaseInit: iInstance=%d\n", pDrvIns->pReg->szName, pDrvIns->iInstance, pDrvIns->iInstance));
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->fKeepInstance = false;
+ pThis->ThreadPoller = NIL_RTTHREAD;
+ pThis->enmType = enmType;
+ pThis->fAttachFailError = true; /* It's an error until we've read the config. */
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvHostBaseQueryInterface;
+
+ /* IMedia. */
+ pThis->IMedia.pfnRead = drvHostBaseRead;
+ pThis->IMedia.pfnWrite = drvHostBaseWrite;
+ pThis->IMedia.pfnFlush = drvHostBaseFlush;
+ pThis->IMedia.pfnIsReadOnly = drvHostBaseIsReadOnly;
+ pThis->IMedia.pfnIsNonRotational = drvHostBaseIsNonRotational;
+ pThis->IMedia.pfnGetSize = drvHostBaseGetSize;
+ pThis->IMedia.pfnGetType = drvHostBaseGetType;
+ pThis->IMedia.pfnGetUuid = drvHostBaseGetUuid;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvHostBaseGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvHostBaseSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvHostBaseGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvHostBaseSetLCHSGeometry;
+ pThis->IMedia.pfnBiosIsVisible = drvHostBaseIsVisible;
+ pThis->IMedia.pfnGetRegionCount = drvHostBaseGetRegionCount;
+ pThis->IMedia.pfnQueryRegionProperties = drvHostBaseQueryRegionProperties;
+ pThis->IMedia.pfnQueryRegionPropertiesForLba = drvHostBaseQueryRegionPropertiesForLba;
+
+ /* IMediaEx */
+ pThis->IMediaEx.pfnQueryFeatures = drvHostBaseQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvHostBaseNotifySuspend;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvHostBaseIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvHostBaseIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvHostBaseIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvHostBaseIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvHostBaseIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvHostBaseIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvHostBaseIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvHostBaseIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvHostBaseIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvHostBaseIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvHostBaseIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvHostBaseIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvHostBaseIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvHostBaseIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvHostBaseIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvHostBaseIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvHostBaseIoReqSuspendedLoad;
+
+ /* IMount. */
+ pThis->IMount.pfnUnmount = drvHostBaseUnmount;
+ pThis->IMount.pfnIsMounted = drvHostBaseIsMounted;
+ pThis->IMount.pfnLock = drvHostBaseLock;
+ pThis->IMount.pfnUnlock = drvHostBaseUnlock;
+ pThis->IMount.pfnIsLocked = drvHostBaseIsLocked;
+
+ drvHostBaseInitOs(pThis);
+
+ if (!CFGMR3AreValuesValid(pCfg, pszCfgValid))
+ {
+ pThis->fAttachFailError = true;
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+ }
+
+ /*
+ * Get the IMediaPort & IMountNotify interfaces of the above driver/device.
+ */
+ pThis->pDrvMediaPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAPORT);
+ if (!pThis->pDrvMediaPort)
+ {
+ AssertMsgFailed(("Configuration error: No media port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+ pThis->pDrvMediaExPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAEXPORT);
+ pThis->pDrvMountNotify = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMOUNTNOTIFY);
+
+ /*
+ * Query configuration.
+ */
+ /* Device */
+ int rc = CFGMR3QueryStringAlloc(pCfg, "Path", &pThis->pszDevice);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: query for \"Path\" string returned %Rra.\n", rc));
+ return rc;
+ }
+
+ /* Mountable */
+ uint32_t u32;
+ rc = CFGMR3QueryU32Def(pCfg, "Interval", &u32, 1000);
+ if (RT_SUCCESS(rc))
+ pThis->cMilliesPoller = u32;
+ else
+ {
+ AssertMsgFailed(("Configuration error: Query \"Mountable\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* ReadOnly - passthrough mode requires read/write access in any case. */
+ if ( (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
+ && pThis->IMedia.pfnSendCmd)
+ pThis->fReadOnlyConfig = false;
+ else
+ {
+ rc = CFGMR3QueryBoolDef(pCfg, "ReadOnly", &pThis->fReadOnlyConfig,
+ enmType == PDMMEDIATYPE_DVD || enmType == PDMMEDIATYPE_CDROM
+ ? true
+ : false);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"ReadOnly\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+ }
+
+ /* Locked */
+ rc = CFGMR3QueryBoolDef(pCfg, "Locked", &pThis->fLocked, false);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"Locked\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* BIOS visible */
+ rc = CFGMR3QueryBoolDef(pCfg, "BIOSVisible", &pThis->fBiosVisible, true);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"BIOSVisible\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* Uuid */
+ char *psz;
+ rc = CFGMR3QueryStringAlloc(pCfg, "Uuid", &psz);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ RTUuidClear(&pThis->Uuid);
+ else if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidFromStr(&pThis->Uuid, psz);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Uuid from string failed on \"%s\", rc=%Rrc.\n", psz, rc));
+ MMR3HeapFree(psz);
+ return rc;
+ }
+ MMR3HeapFree(psz);
+ }
+ else
+ {
+ AssertMsgFailed(("Configuration error: Failed to obtain the uuid, rc=%Rrc.\n", rc));
+ return rc;
+ }
+
+ /* Define whether attach failure is an error (default) or not. */
+ bool fAttachFailError = true;
+ rc = CFGMR3QueryBoolDef(pCfg, "AttachFailError", &fAttachFailError, true);
+ pThis->fAttachFailError = fAttachFailError;
+
+ /* log config summary */
+ Log(("%s-%d: pszDevice='%s' (%s) cMilliesPoller=%d fReadOnlyConfig=%d fLocked=%d fBIOSVisible=%d Uuid=%RTuuid\n",
+ pDrvIns->pReg->szName, pDrvIns->iInstance, pThis->pszDevice, pThis->pszDeviceOpen, pThis->cMilliesPoller,
+ pThis->fReadOnlyConfig, pThis->fLocked, pThis->fBiosVisible, &pThis->Uuid));
+
+ /*
+ * Check that there are no drivers below us.
+ */
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Register saved state.
+ */
+ rc = PDMDrvHlpSSMRegisterLoadDone(pDrvIns, drvHostBaseLoadDone);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Initialize the critical section used for serializing the access to the media.
+ */
+ rc = RTCritSectInit(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Open the device.
+ */
+ rc = drvHostBaseOpenOs(pThis, pThis->fReadOnlyConfig);
+ if (RT_FAILURE(rc))
+ {
+ char *pszDevice = pThis->pszDevice;
+#ifndef RT_OS_DARWIN
+ char szPathReal[256];
+ if ( RTPathExists(pszDevice)
+ && RT_SUCCESS(RTPathReal(pszDevice, szPathReal, sizeof(szPathReal))))
+ pszDevice = szPathReal;
+#endif
+
+ /*
+ * Disable CD/DVD passthrough in case it was enabled. Would cause
+ * weird failures later when the guest issues commands. These would
+ * all fail because of the invalid file handle. So use the normal
+ * virtual CD/DVD code, which deals more gracefully with unavailable
+ * "media" - actually a complete drive in this case.
+ */
+ pThis->IMedia.pfnSendCmd = NULL;
+ AssertMsgFailed(("Could not open host device %s, rc=%Rrc\n", pszDevice, rc));
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+#ifdef RT_OS_LINUX
+ N_("Cannot open host device '%s' for %s access. Check the permissions "
+ "of that device ('/bin/ls -l %s'): Most probably you need to be member "
+ "of the device group. Make sure that you logout/login after changing "
+ "the group settings of the current user"),
+#else
+ N_("Cannot open host device '%s' for %s access. Check the permissions "
+ "of that device"),
+#endif
+ pszDevice, pThis->fReadOnlyConfig ? "readonly" : "read/write",
+ pszDevice);
+ default:
+ {
+ if (pThis->fAttachFailError)
+ return rc;
+ int erc = PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/,
+ "DrvHost_MOUNTFAIL",
+ N_("Cannot attach to host device '%s'"), pszDevice);
+ AssertRC(erc);
+ src = rc;
+ }
+ }
+ }
+
+ /*
+ * Lock the drive if that's required by the configuration.
+ */
+ if (pThis->fLocked)
+ {
+ if (pThis->pfnDoLock)
+ rc = pThis->pfnDoLock(pThis, true);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to lock the dvd drive. rc=%Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ if (RT_SUCCESS(src) && drvHostBaseIsMediaPollingRequiredOs(pThis))
+ {
+ /*
+ * Create the event semaphore which the poller thread will wait on.
+ */
+ rc = RTSemEventCreate(&pThis->EventPoller);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Start the thread which will poll for the media.
+ */
+ rc = RTThreadCreate(&pThis->ThreadPoller, drvHostBaseMediaThread, pThis, 0,
+ RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "DVDMEDIA");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to create poller thread. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Wait for the thread to start up (!w32:) and do one detection loop.
+ */
+ rc = RTThreadUserWait(pThis->ThreadPoller, 10000);
+ AssertRC(rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ drvHostBaseStatsRegister(pThis);
+
+ if (RT_FAILURE(rc))
+ {
+ if (!pThis->fAttachFailError)
+ {
+ /* Suppressing the attach failure error must not affect the normal
+ * DRVHostBaseDestruct, so reset this flag below before leaving. */
+ pThis->fKeepInstance = true;
+ rc = VINF_SUCCESS;
+ }
+ DRVHostBaseDestruct(pDrvIns);
+ pThis->fKeepInstance = false;
+ }
+
+ if (RT_FAILURE(src))
+ return src;
+
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Storage/DrvHostBase.h b/src/VBox/Devices/Storage/DrvHostBase.h
new file mode 100644
index 00000000..3dd92e57
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.h
@@ -0,0 +1,192 @@
+/* $Id: DrvHostBase.h $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_DrvHostBase_h
+#define VBOX_INCLUDED_SRC_Storage_DrvHostBase_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/critsect.h>
+#include <iprt/log.h>
+#include <iprt/semaphore.h>
+#include <VBox/cdefs.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+
+RT_C_DECLS_BEGIN
+
+
+/** Pointer to host base drive access driver instance data. */
+typedef struct DRVHOSTBASE *PDRVHOSTBASE;
+/**
+ * Host base drive access driver instance data.
+ *
+ * @implements PDMIMOUNT
+ * @implements PDMIMEDIA
+ */
+typedef struct DRVHOSTBASE
+{
+ /** Critical section used to serialize access to the handle and other
+ * members of this struct. */
+ RTCRITSECT CritSect;
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Drive type. */
+ PDMMEDIATYPE enmType;
+ /** Visible to the BIOS. */
+ bool fBiosVisible;
+ /** The configuration readonly value. */
+ bool fReadOnlyConfig;
+ /** The current readonly status. */
+ bool fReadOnly;
+ /** Flag whether failure to attach is an error or not. */
+ bool fAttachFailError;
+ /** Flag whether to keep instance working (as unmounted though). */
+ bool fKeepInstance;
+ /** Device name (MMHeap). */
+ char *pszDevice;
+ /** Device name to open (RTStrFree). */
+ char *pszDeviceOpen;
+ /** Uuid of the drive. */
+ RTUUID Uuid;
+
+ /** Pointer to the media port interface above us. */
+ PPDMIMEDIAPORT pDrvMediaPort;
+ /** Pointer to the extended media port interface above us. */
+ PPDMIMEDIAEXPORT pDrvMediaExPort;
+ /** Pointer to the mount notify interface above us. */
+ PPDMIMOUNTNOTIFY pDrvMountNotify;
+ /** Our media interface. */
+ PDMIMEDIA IMedia;
+ /** Our extended media interface. */
+ PDMIMEDIAEX IMediaEx;
+ /** Our mountable interface. */
+ PDMIMOUNT IMount;
+
+ /** Media present indicator. */
+ bool volatile fMediaPresent;
+ /** Locked indicator. */
+ bool fLocked;
+ /** The size of the media currently in the drive.
+ * This is invalid if no drive is in the drive. */
+ uint64_t volatile cbSize;
+
+ /** Handle of the poller thread. */
+ RTTHREAD ThreadPoller;
+ /** Event semaphore the thread will wait on. */
+ RTSEMEVENT EventPoller;
+ /** The poller interval. */
+ RTMSINTERVAL cMilliesPoller;
+ /** The shutdown indicator. */
+ bool volatile fShutdownPoller;
+
+ /** BIOS PCHS geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** BIOS LCHS geometry. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+
+ /** Pointer to the current buffer holding data. */
+ void *pvBuf;
+ /** Size of the buffer. */
+ size_t cbBuf;
+ /** Size of the I/O request to allocate. */
+ size_t cbIoReqAlloc;
+
+ /** Release statistics: number of bytes written. */
+ STAMCOUNTER StatBytesWritten;
+ /** Release statistics: number of bytes read. */
+ STAMCOUNTER StatBytesRead;
+ /** Release statistics: Number of requests submitted. */
+ STAMCOUNTER StatReqsSubmitted;
+ /** Release statistics: Number of requests failed. */
+ STAMCOUNTER StatReqsFailed;
+ /** Release statistics: Number of requests succeeded. */
+ STAMCOUNTER StatReqsSucceeded;
+ /** Release statistics: Number of flush requests. */
+ STAMCOUNTER StatReqsFlush;
+ /** Release statistics: Number of write requests. */
+ STAMCOUNTER StatReqsWrite;
+ /** Release statistics: Number of read requests. */
+ STAMCOUNTER StatReqsRead;
+
+ /**
+ * Performs the locking / unlocking of the device.
+ *
+ * This callback pointer should be set to NULL if the device doesn't support this action.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the instance data.
+ * @param fLock Set if locking, clear if unlocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnDoLock)(PDRVHOSTBASE pThis, bool fLock);
+
+ union
+ {
+#ifdef DRVHOSTBASE_OS_INT_DECLARED
+ DRVHOSTBASEOS Os;
+#endif
+ uint8_t abPadding[64];
+ };
+} DRVHOSTBASE;
+
+
+/**
+ * Request structure fo a request.
+ */
+typedef struct DRVHOSTBASEREQ
+{
+ /** Transfer size. */
+ size_t cbReq;
+ /** Amount of residual data. */
+ size_t cbResidual;
+ /** Start of the request data for the device above us. */
+ uint8_t abAlloc[1];
+} DRVHOSTBASEREQ;
+/** Pointer to a request structure. */
+typedef DRVHOSTBASEREQ *PDRVHOSTBASEREQ;
+
+DECLHIDDEN(int) DRVHostBaseInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, const char *pszCfgValid, PDMMEDIATYPE enmType);
+DECLHIDDEN(int) DRVHostBaseMediaPresent(PDRVHOSTBASE pThis);
+DECLHIDDEN(void) DRVHostBaseMediaNotPresent(PDRVHOSTBASE pThis);
+DECLCALLBACK(void) DRVHostBaseDestruct(PPDMDRVINS pDrvIns);
+
+DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
+ void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies);
+DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis);
+DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb);
+DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead);
+DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite);
+DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis);
+DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock);
+DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis);
+
+DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis);
+DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly);
+DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis);
+DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent);
+DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis);
+DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis);
+
+DECLHIDDEN(int) drvHostBaseBufferRetain(PDRVHOSTBASE pThis, PDRVHOSTBASEREQ pReq, size_t cbBuf, bool fWrite, void **ppvBuf);
+DECLHIDDEN(int) drvHostBaseBufferRelease(PDRVHOSTBASE pThis, PDRVHOSTBASEREQ pReq, size_t cbBuf, bool fWrite, void *pvBuf);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_DrvHostBase_h */
diff --git a/src/VBox/Devices/Storage/DrvHostDVD.cpp b/src/VBox/Devices/Storage/DrvHostDVD.cpp
new file mode 100644
index 00000000..02d63b2f
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostDVD.cpp
@@ -0,0 +1,525 @@
+/* $Id: DrvHostDVD.cpp $ */
+/** @file
+ * DrvHostDVD - Host DVD block driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
+#include <iprt/asm.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/critsect.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+
+#include "VBoxDD.h"
+#include "DrvHostBase.h"
+#include "ATAPIPassthrough.h"
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+/** Size of an ATAPI packet. */
+#define ATAPI_PACKET_SIZE 12
+
+/**
+ * Host DVD driver instance data.
+ */
+typedef struct DRVHOSTDVD
+{
+ /** Base drivr data. */
+ DRVHOSTBASE Core;
+ /** The current tracklist of the loaded medium if passthrough is used. */
+ PTRACKLIST pTrackList;
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** Flag whether to overwrite the inquiry data with our emulated settings. */
+ bool fInquiryOverwrite;
+} DRVHOSTDVD;
+/** Pointer to the host DVD driver instance data. */
+typedef DRVHOSTDVD *PDRVHOSTDVD;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+static uint8_t drvHostDvdCmdOK(PDRVHOSTDVD pThis)
+{
+ memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
+ pThis->abATAPISense[0] = 0x70;
+ pThis->abATAPISense[7] = 10;
+ return SCSI_STATUS_OK;
+}
+
+static uint8_t drvHostDvdCmdError(PDRVHOSTDVD pThis, const uint8_t *pabATAPISense, size_t cbATAPISense)
+{
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
+ pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
+ memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
+ memcpy(pThis->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(pThis->abATAPISense)));
+ return SCSI_STATUS_CHECK_CONDITION;
+}
+
+/** @todo deprecated function - doesn't provide enough info. Replace by direct
+ * calls to drvHostDvdCmdError() with full data. */
+static uint8_t drvHostDvdCmdErrorSimple(PDRVHOSTDVD pThis, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ memset(abATAPISense, '\0', sizeof(abATAPISense));
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = uATAPISenseKey & 0x0f;
+ abATAPISense[7] = 10;
+ abATAPISense[12] = uATAPIASC;
+ return drvHostDvdCmdError(pThis, abATAPISense, sizeof(abATAPISense));
+}
+
+
+/**
+ * Parse the CDB and check whether it can be passed through safely.
+ *
+ * @returns Flag whether to passthrough to the device is considered safe.
+ * @param pThis The host DVD driver instance.
+ * @param pReq The request.
+ * @param pbCdb The CDB to parse.
+ * @param cbCdb Size of the CDB in bytes.
+ * @param cbBuf Size of the guest buffer.
+ * @param penmTxDir Where to store the transfer direction (guest to host or vice versa).
+ * @param pcbXfer Where to store the transfer size encoded in the CDB.
+ * @param pcbSector Where to store the sector size used for the transfer.
+ * @param pu8ScsiSts Where to store the SCSI status code.
+ */
+static bool drvHostDvdParseCdb(PDRVHOSTDVD pThis, PDRVHOSTBASEREQ pReq,
+ const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
+ PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
+ size_t *pcbSector, uint8_t *pu8ScsiSts)
+{
+ bool fPassthrough = false;
+
+ if ( pbCdb[0] == SCSI_REQUEST_SENSE
+ && (pThis->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
+ {
+ /* Handle the command here and copy sense data over. */
+ void *pvBuf = NULL;
+ int rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbBuf, false /*fWrite*/, &pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pvBuf, &pThis->abATAPISense[0], RT_MIN(sizeof(pThis->abATAPISense), cbBuf));
+ rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, false /* fWrite */, pvBuf);
+ AssertRC(rc);
+ drvHostDvdCmdOK(pThis);
+ *pu8ScsiSts = SCSI_STATUS_OK;
+ }
+ }
+ else
+ fPassthrough = ATAPIPassthroughParseCdb(pbCdb, cbCdb, cbBuf, pThis->pTrackList,
+ &pThis->abATAPISense[0], sizeof(pThis->abATAPISense),
+ penmTxDir, pcbXfer, pcbSector, pu8ScsiSts);
+
+ return fPassthrough;
+}
+
+
+/**
+ * Locks or unlocks the drive.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance data.
+ * @param fLock True if the request is to lock the drive, false if to unlock.
+ */
+static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
+{
+ int rc = drvHostBaseDoLockOs(pThis, fLock);
+
+ LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnSendCmd} */
+static DECLCALLBACK(int) drvHostDvdSendCmd(PPDMIMEDIA pInterface, const uint8_t *pbCdb, size_t cbCdb,
+ PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf,
+ uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
+{
+ PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
+ int rc;
+ LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCdb[0], enmTxDir, *pcbBuf, cTimeoutMillies));
+
+ RTCritSectEnter(&pThis->CritSect);
+ /* Pass the request on to the internal scsi command interface. */
+ if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
+ rc = drvHostBaseScsiCmdOs(pThis, pbCdb, cbCdb, enmTxDir, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
+ if (rc == VERR_UNRESOLVED_ERROR)
+ /* sense information set */
+ rc = VERR_DEV_IO_ERROR;
+
+ if (pbCdb[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
+ {
+ uint8_t *pbBuf = (uint8_t*)pvBuf;
+ Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
+ if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
+ Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
+ }
+ RTCritSectLeave(&pThis->CritSect);
+
+ LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd} */
+static DECLCALLBACK(int) drvHostDvdIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
+ const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
+ uint32_t cTimeoutMillies)
+{
+ RT_NOREF3(uLun, cTimeoutMillies, enmTxDir);
+
+ PDRVHOSTDVD pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDVD, Core.IMediaEx);
+ PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
+ int rc = VINF_SUCCESS;
+
+ LogFlow(("%s: pbCdb[0]=%#04x{%s} enmTxDir=%d cbBuf=%zu timeout=%u\n",
+ __FUNCTION__, pbCdb[0], SCSICmdText(pbCdb[0]), enmTxDir, cbBuf, cTimeoutMillies));
+
+ RTCritSectEnter(&pThis->Core.CritSect);
+
+ /*
+ * Parse the command first to fend off any illegal or dangerous commands we don't want the guest
+ * to execute on the host drive.
+ */
+ PDMMEDIATXDIR enmXferDir = PDMMEDIATXDIR_NONE;
+ size_t cbXfer = 0;
+ size_t cbSector = 0;
+ size_t cbScsiCmdBufLimit = drvHostBaseScsiCmdGetBufLimitOs(&pThis->Core);
+ bool fPassthrough = drvHostDvdParseCdb(pThis, pReq, pbCdb, cbCdb, cbBuf,
+ &enmXferDir, &cbXfer, &cbSector, pu8ScsiSts);
+ if (fPassthrough)
+ {
+ void *pvBuf = NULL;
+ size_t cbXferCur = cbXfer;
+
+ pReq->cbReq = cbXfer;
+ pReq->cbResidual = cbXfer;
+
+ if (cbXfer)
+ rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, &pvBuf);
+
+ if (cbXfer > cbScsiCmdBufLimit)
+ {
+ /* Linux accepts commands with up to 100KB of data, but expects
+ * us to handle commands with up to 128KB of data. The usual
+ * imbalance of powers. */
+ uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
+ uint32_t iATAPILBA, cSectors;
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+
+ switch (pbCdb[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U16(pbCdb + 7);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ iATAPILBA = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U32(pbCdb + 6);
+ break;
+ case SCSI_READ_CD:
+ iATAPILBA = scsiBE2H_U32(pbCdb + 2);
+ cSectors = scsiBE2H_U24(pbCdb + 6);
+ break;
+ case SCSI_READ_CD_MSF:
+ iATAPILBA = scsiMSF2LBA(pbCdb + 3);
+ cSectors = scsiMSF2LBA(pbCdb + 6) - iATAPILBA;
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", pbCdb[0]));
+ LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough split error\n", pThis->Core.pDrvIns->iInstance));
+ *pu8ScsiSts = drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
+ RTCritSectLeave(&pThis->Core.CritSect);
+ return VINF_SUCCESS;
+ }
+ memcpy(aATAPICmd, pbCdb, RT_MIN(cbCdb, ATAPI_PACKET_SIZE));
+ uint32_t cReqSectors = 0;
+ for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
+ {
+ if (i * cbSector > cbScsiCmdBufLimit)
+ cReqSectors = (uint32_t)(cbScsiCmdBufLimit / cbSector);
+ else
+ cReqSectors = i;
+ uint32_t cbCurrTX = (uint32_t)cbSector * cReqSectors;
+ switch (pbCdb[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(aATAPICmd + 7, cReqSectors);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(aATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(aATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(aATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
+ break;
+ }
+ rc = drvHostBaseScsiCmdOs(&pThis->Core, aATAPICmd, sizeof(aATAPICmd),
+ enmXferDir, pbBuf, &cbCurrTX,
+ &pThis->abATAPISense[0], sizeof(pThis->abATAPISense),
+ cTimeoutMillies /**< @todo timeout */);
+ if (rc != VINF_SUCCESS)
+ break;
+
+ pReq->cbResidual -= cbCurrTX;
+ iATAPILBA += cReqSectors;
+ pbBuf += cbSector * cReqSectors;
+ }
+ }
+ else
+ {
+ uint32_t cbXferTmp = (uint32_t)cbXferCur;
+ rc = drvHostBaseScsiCmdOs(&pThis->Core, pbCdb, cbCdb, enmXferDir, pvBuf, &cbXferTmp,
+ &pThis->abATAPISense[0], sizeof(pThis->abATAPISense), cTimeoutMillies);
+ if (RT_SUCCESS(rc))
+ pReq->cbResidual -= cbXferTmp;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Do post processing for certain commands. */
+ switch (pbCdb[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ if (!pThis->pTrackList)
+ rc = ATAPIPassthroughTrackListCreateEmpty(&pThis->pTrackList);
+
+ if (RT_SUCCESS(rc))
+ rc = ATAPIPassthroughTrackListUpdate(pThis->pTrackList, pbCdb, pvBuf);
+
+ if (RT_FAILURE(rc))
+ LogRelMax(10, ("HostDVD#%u: Error (%Rrc) while updating the tracklist during %s, burning the disc might fail\n",
+ pThis->Core.pDrvIns->iInstance, rc,
+ pbCdb[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
+ break;
+ }
+ case SCSI_SYNCHRONIZE_CACHE:
+ {
+ if (pThis->pTrackList)
+ ATAPIPassthroughTrackListClear(pThis->pTrackList);
+ break;
+ }
+ }
+
+ if (enmXferDir == PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ Assert(cbXferCur <= cbXfer);
+
+ if ( pbCdb[0] == SCSI_INQUIRY
+ && pThis->fInquiryOverwrite)
+ {
+ const char *pszInqVendorId = "VBOX";
+ const char *pszInqProductId = "CD-ROM";
+ const char *pszInqRevision = "1.0";
+
+ if (pThis->Core.pDrvMediaPort->pfnQueryScsiInqStrings)
+ {
+ rc = pThis->Core.pDrvMediaPort->pfnQueryScsiInqStrings(pThis->Core.pDrvMediaPort, &pszInqVendorId,
+ &pszInqProductId, &pszInqRevision);
+ AssertRC(rc);
+ }
+ /* Make sure that the real drive cannot be identified.
+ * Motivation: changing the VM configuration should be as
+ * invisible as possible to the guest. */
+ if (cbXferCur >= 8 + 8)
+ scsiPadStr((uint8_t *)pvBuf + 8, pszInqVendorId, 8);
+ if (cbXferCur >= 16 + 16)
+ scsiPadStr((uint8_t *)pvBuf + 16, pszInqProductId, 16);
+ if (cbXferCur >= 32 + 4)
+ scsiPadStr((uint8_t *)pvBuf + 32, pszInqRevision, 4);
+ }
+
+ if (cbXferCur)
+ Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbXferCur, cbXferCur, (uint8_t *)pvBuf));
+ }
+
+ *pu8ScsiSts = drvHostDvdCmdOK(pThis);
+ }
+ else
+ {
+ do
+ {
+ /* don't log superfluous errors */
+ if ( rc == VERR_DEV_IO_ERROR
+ && ( pbCdb[0] == SCSI_TEST_UNIT_READY
+ || pbCdb[0] == SCSI_READ_CAPACITY
+ || pbCdb[0] == SCSI_READ_DVD_STRUCTURE
+ || pbCdb[0] == SCSI_READ_TOC_PMA_ATIP))
+ break;
+ LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
+ pThis->Core.pDrvIns->iInstance, pbCdb[0], pThis->abATAPISense[2] & 0x0f,
+ pThis->abATAPISense[12], pThis->abATAPISense[13], rc));
+ } while (0);
+ *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
+ rc = VINF_SUCCESS;
+ }
+
+ if (cbXfer)
+ rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
+ }
+
+ /*
+ * We handled the command, check the status code and copy over the sense data if
+ * it is CHECK CONDITION.
+ */
+ if ( *pu8ScsiSts == SCSI_STATUS_CHECK_CONDITION
+ && VALID_PTR(pabSense)
+ && cbSense > 0)
+ memcpy(pabSense, &pThis->abATAPISense[0], RT_MIN(cbSense, sizeof(pThis->abATAPISense)));
+
+ RTCritSectLeave(&pThis->Core.CritSect);
+
+ LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+
+/** @interface_method_impl{PDMDRVREG,pfnDestruct} */
+static DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
+
+ if (pThis->pTrackList)
+ {
+ ATAPIPassthroughTrackListDestroy(pThis->pTrackList);
+ pThis->pTrackList = NULL;
+ }
+
+ DRVHostBaseDestruct(pDrvIns);
+}
+
+/**
+ * Construct a host dvd drive driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
+ LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ int rc = CFGMR3QueryBoolDef(pCfg, "InquiryOverwrite", &pThis->fInquiryOverwrite, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("HostDVD configuration error: failed to read \"InquiryOverwrite\" as boolean"));
+
+ bool fPassthrough;
+ rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
+ if (RT_SUCCESS(rc) && fPassthrough)
+ {
+ pThis->Core.IMedia.pfnSendCmd = drvHostDvdSendCmd;
+ pThis->Core.IMediaEx.pfnIoReqSendScsiCmd = drvHostDvdIoReqSendScsiCmd;
+ /* Passthrough requires opening the device in R/W mode. */
+ pThis->Core.fReadOnlyConfig = false;
+ }
+
+ pThis->Core.pfnDoLock = drvHostDvdDoLock;
+
+ /*
+ * Init instance data.
+ */
+ rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0InquiryOverwrite\0",
+ PDMMEDIATYPE_DVD);
+ LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvHostDVD =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "HostDVD",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Host DVD Block Driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTDVD),
+ /* pfnConstruct */
+ drvHostDvdConstruct,
+ /* pfnDestruct */
+ drvHostDvdDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/DrvHostFloppy.cpp b/src/VBox/Devices/Storage/DrvHostFloppy.cpp
new file mode 100644
index 00000000..7d28a038
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostFloppy.cpp
@@ -0,0 +1,110 @@
+/* $Id: DrvHostFloppy.cpp $ */
+/** @file
+ *
+ * VBox storage devices:
+ * Host floppy block driver
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_FLOPPY
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+
+#include "VBoxDD.h"
+#include "DrvHostBase.h"
+
+
+
+/**
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostFloppyConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ LogFlow(("drvHostFloppyConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Init instance data.
+ */
+ int rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0ReadOnly\0Interval\0Locked\0BIOSVisible\0",
+ PDMMEDIATYPE_FLOPPY_1_44);
+ LogFlow(("drvHostFloppyConstruct: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvHostFloppy =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "HostFloppy",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Host Floppy Block Driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTBASE),
+ /* pfnConstruct */
+ drvHostFloppyConstruct,
+ /* pfnDestruct */
+ DRVHostBaseDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/DrvRamDisk.cpp b/src/VBox/Devices/Storage/DrvRamDisk.cpp
new file mode 100644
index 00000000..9d397949
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvRamDisk.cpp
@@ -0,0 +1,1843 @@
+/* $Id: DrvRamDisk.cpp $ */
+/** @file
+ * VBox storage devices: RAM disk driver.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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.
+ *
+ * @returns nothing.
+ * @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_RUNNING_FT
+ || 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,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);
+ int rc = VINF_SUCCESS;
+ uint32_t cbIoBufMax;
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+ LogFlow(("drvdiskintConstruct: iInstance=%d\n", pDrvIns->iInstance));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "Size\0"
+ "PreAlloc\0"
+ "IoBufMax\0"
+ "SectorSize\0"
+ "NonRotational\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ rc = CFGMR3QueryU64(pCfg, "Size", &pThis->cbDisk);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying the media size"));
+ rc = CFGMR3QueryBoolDef(pCfg, "PreAlloc", &pThis->fPreallocRamDisk, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"PreAlloc\""));
+ rc = CFGMR3QueryBoolDef(pCfg, "NonRotational", &pThis->fNonRotational, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"NonRotational\""));
+ rc = CFGMR3QueryU32Def(pCfg, "IoBufMax", &cbIoBufMax, 5 * _1M);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IoBufMax\" from the config"));
+ rc = CFGMR3QueryU32Def(pCfg, "SectorSize", &pThis->cbSector, 512);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"SectorSize\" from the config"));
+
+ /*
+ * 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.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;
+
+ /* 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
+};
+
diff --git a/src/VBox/Devices/Storage/DrvSCSI.cpp b/src/VBox/Devices/Storage/DrvSCSI.cpp
new file mode 100644
index 00000000..b1d3e6ac
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvSCSI.cpp
@@ -0,0 +1,1532 @@
+/* $Id: DrvSCSI.cpp $ */
+/** @file
+ * VBox storage drivers: Generic SCSI command parser and execution driver
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DRV_SCSI
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmifs.h>
+#include <VBox/vmm/pdmqueue.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmthread.h>
+#include <VBox/vscsi.h>
+#include <VBox/scsi.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/req.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/**
+ * Eject state.
+ */
+typedef struct DRVSCSIEJECTSTATE
+{
+ /** The item core for the PDM queue. */
+ PDMQUEUEITEMCORE Core;
+ /** Event semaphore to signal when complete. */
+ RTSEMEVENT hSemEvt;
+ /** Status of the eject operation. */
+ int rcReq;
+} DRVSCSIEJECTSTATE;
+typedef DRVSCSIEJECTSTATE *PDRVSCSIEJECTSTATE;
+
+/**
+ * SCSI driver private per request data.
+ */
+typedef struct DRVSCSIREQ
+{
+ /** Size of the guest buffer. */
+ size_t cbBuf;
+ /** Temporary buffer holding the data. */
+ void *pvBuf;
+ /** Data segment. */
+ RTSGSEG Seg;
+ /** Transfer direction. */
+ PDMMEDIAEXIOREQSCSITXDIR enmXferDir;
+ /** The VSCSI request handle. */
+ VSCSIREQ hVScsiReq;
+ /** Where to store the SCSI status code. */
+ uint8_t *pu8ScsiSts;
+ /** Transfer size determined by the VSCSI layer. */
+ size_t cbXfer;
+ /** Start of the request data for the device above us. */
+ uint8_t abAlloc[1];
+} DRVSCSIREQ;
+/** Pointer to the driver private per request data. */
+typedef DRVSCSIREQ *PDRVSCSIREQ;
+
+/**
+ * SCSI driver instance data.
+ *
+ * @implements PDMIMEDIAEXPORT
+ * @implements PDMIMEDIAEX
+ * @implements PDMIMOUNTNOTIFY
+ */
+typedef struct DRVSCSI
+{
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+
+ /** Pointer to the attached driver's base interface. */
+ PPDMIBASE pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ PPDMIMEDIA pDrvMedia;
+ /** Pointer to the attached driver's extended media interface. */
+ PPDMIMEDIAEX pDrvMediaEx;
+ /** Pointer to the attached driver's mount interface. */
+ PPDMIMOUNT pDrvMount;
+ /** Pointer to the extended media port interface of the device above. */
+ PPDMIMEDIAEXPORT pDevMediaExPort;
+ /** Pointer to the media port interface of the device above. */
+ PPDMIMEDIAPORT pDevMediaPort;
+ /** pointer to the Led port interface of the dveice above. */
+ PPDMILEDPORTS pLedPort;
+ /** The media interface for the device above. */
+ PDMIMEDIA IMedia;
+ /** The extended media interface for the device above. */
+ PDMIMEDIAEX IMediaEx;
+ /** The media port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The optional extended media port interface. */
+ PDMIMEDIAEXPORT IPortEx;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+ /** Fallback status LED state for this drive.
+ * This is used in case the device doesn't has a LED interface. */
+ PDMLED Led;
+ /** Pointer to the status LED for this drive. */
+ PPDMLED pLed;
+
+ /** VSCSI device handle. */
+ VSCSIDEVICE hVScsiDevice;
+ /** VSCSI LUN handle. */
+ VSCSILUN hVScsiLun;
+ /** I/O callbacks. */
+ VSCSILUNIOCALLBACKS VScsiIoCallbacks;
+
+ /** Indicates whether PDMDrvHlpAsyncNotificationCompleted should be called by
+ * any of the dummy functions. */
+ bool volatile fDummySignal;
+ /** Current I/O depth. */
+ volatile uint32_t StatIoDepth;
+ /** Errors printed in the release log. */
+ unsigned cErrors;
+
+ /** Size of the I/O request to allocate. */
+ size_t cbIoReqAlloc;
+ /** Size of a VSCSI I/O request. */
+ size_t cbVScsiIoReqAlloc;
+ /** Queue to defer unmounting to EMT. */
+ PPDMQUEUE pQueue;
+} DRVSCSI, *PDRVSCSI;
+
+/** Convert a VSCSI I/O request handle to the associated PDMIMEDIAEX I/O request. */
+#define DRVSCSI_VSCSIIOREQ_2_PDMMEDIAEXIOREQ(a_hVScsiIoReq) (*(PPDMMEDIAEXIOREQ)((uint8_t *)(a_hVScsiIoReq) - sizeof(PDMMEDIAEXIOREQ)))
+/** Convert a PDMIMEDIAEX I/O additional request memory to a VSCSI I/O request. */
+#define DRVSCSI_PDMMEDIAEXIOREQ_2_VSCSIIOREQ(a_pvIoReqAlloc) ((VSCSIIOREQ)((uint8_t *)(a_pvIoReqAlloc) + sizeof(PDMMEDIAEXIOREQ)))
+
+/**
+ * Returns whether the given status code indicates a non fatal error.
+ *
+ * @returns True if the error can be fixed by the user after the VM was suspended.
+ * False if not and the error should be reported to the guest.
+ * @param rc The status code to check.
+ */
+DECLINLINE(bool) drvscsiIsRedoPossible(int rc)
+{
+ if ( rc == VERR_DISK_FULL
+ || rc == VERR_FILE_TOO_BIG
+ || rc == VERR_BROKEN_PIPE
+ || rc == VERR_NET_CONNECTION_REFUSED
+ || rc == VERR_VD_DEK_MISSING)
+ return true;
+
+ return false;
+}
+
+/* -=-=-=-=- VScsiIoCallbacks -=-=-=-=- */
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunReqAllocSizeSet}
+ */
+static DECLCALLBACK(int) drvscsiReqAllocSizeSet(VSCSILUN hVScsiLun, void *pvScsiLunUser, size_t cbVScsiIoReqAlloc)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ /* We need to store the I/O request handle so we can get it when VSCSI queues an I/O request. */
+ int rc = pThis->pDrvMediaEx->pfnIoReqAllocSizeSet(pThis->pDrvMediaEx, cbVScsiIoReqAlloc + sizeof(PDMMEDIAEXIOREQ));
+ if (RT_SUCCESS(rc))
+ pThis->cbVScsiIoReqAlloc = cbVScsiIoReqAlloc + sizeof(PDMMEDIAEXIOREQ);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunReqAlloc}
+ */
+static DECLCALLBACK(int) drvscsiReqAlloc(VSCSILUN hVScsiLun, void *pvScsiLunUser,
+ uint64_t u64Tag, PVSCSIIOREQ phVScsiIoReq)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+ PDMMEDIAEXIOREQ hIoReq;
+ void *pvIoReqAlloc;
+ int rc = pThis->pDrvMediaEx->pfnIoReqAlloc(pThis->pDrvMediaEx, &hIoReq, &pvIoReqAlloc, u64Tag,
+ PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMMEDIAEXIOREQ phIoReq = (PPDMMEDIAEXIOREQ)pvIoReqAlloc;
+
+ *phIoReq = hIoReq;
+ *phVScsiIoReq = (VSCSIIOREQ)(phIoReq + 1);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunReqFree}
+ */
+static DECLCALLBACK(int) drvscsiReqFree(VSCSILUN hVScsiLun, void *pvScsiLunUser, VSCSIIOREQ hVScsiIoReq)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+ PDMMEDIAEXIOREQ hIoReq = DRVSCSI_VSCSIIOREQ_2_PDMMEDIAEXIOREQ(hVScsiIoReq);
+
+ return pThis->pDrvMediaEx->pfnIoReqFree(pThis->pDrvMediaEx, hIoReq);
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunMediumGetRegionCount}
+ */
+static DECLCALLBACK(uint32_t) drvscsiGetRegionCount(VSCSILUN hVScsiLun, void *pvScsiLunUser)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ return pThis->pDrvMedia->pfnGetRegionCount(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunMediumQueryRegionProperties} */
+static DECLCALLBACK(int) drvscsiQueryRegionProperties(VSCSILUN hVScsiLun, void *pvScsiLunUser,
+ uint32_t uRegion, uint64_t *pu64LbaStart,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ return pThis->pDrvMedia->pfnQueryRegionProperties(pThis->pDrvMedia, uRegion, pu64LbaStart,
+ pcBlocks, pcbBlock, penmDataForm);
+}
+
+/** @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunMediumQueryRegionPropertiesForLba} */
+static DECLCALLBACK(int) drvscsiQueryRegionPropertiesForLba(VSCSILUN hVScsiLun, void *pvScsiLunUser,
+ uint64_t u64LbaStart, uint32_t *puRegion,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ return pThis->pDrvMedia->pfnQueryRegionPropertiesForLba(pThis->pDrvMedia, u64LbaStart, puRegion,
+ pcBlocks, pcbBlock, penmDataForm);
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunMediumSetLock}
+ */
+static DECLCALLBACK(int) drvscsiSetLock(VSCSILUN hVScsiLun, void *pvScsiLunUser, bool fLocked)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ if (fLocked)
+ pThis->pDrvMount->pfnLock(pThis->pDrvMount);
+ else
+ pThis->pDrvMount->pfnUnlock(pThis->pDrvMount);
+
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunMediumEject} */
+static DECLCALLBACK(int) drvscsiEject(VSCSILUN hVScsiLun, void *pvScsiLunUser)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+ int rc = VINF_SUCCESS;
+ RTSEMEVENT hSemEvt = NIL_RTSEMEVENT;
+
+ /* This must be done from EMT. */
+ rc = RTSemEventCreate(&hSemEvt);
+ if (RT_SUCCESS(rc))
+ {
+ PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)PDMQueueAlloc(pThis->pQueue);
+ if (pEjectState)
+ {
+ pEjectState->hSemEvt = hSemEvt;
+ PDMQueueInsert(pThis->pQueue, &pEjectState->Core);
+
+ /* Wait for completion. */
+ rc = RTSemEventWait(pEjectState->hSemEvt, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ rc = pEjectState->rcReq;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTSemEventDestroy(pEjectState->hSemEvt);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunReqTransferEnqueue}
+ */
+static DECLCALLBACK(int) drvscsiReqTransferEnqueue(VSCSILUN hVScsiLun, void *pvScsiLunUser, VSCSIIOREQ hVScsiIoReq)
+{
+ RT_NOREF(hVScsiLun);
+ int rc = VINF_SUCCESS;
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+ PDMMEDIAEXIOREQ hIoReq = DRVSCSI_VSCSIIOREQ_2_PDMMEDIAEXIOREQ(hVScsiIoReq);
+
+ LogFlowFunc(("Enqueuing hVScsiIoReq=%#p\n", hVScsiIoReq));
+
+ VSCSIIOREQTXDIR enmTxDir = VSCSIIoReqTxDirGet(hVScsiIoReq);
+ switch (enmTxDir)
+ {
+ case VSCSIIOREQTXDIR_FLUSH:
+ {
+ rc = pThis->pDrvMediaEx->pfnIoReqFlush(pThis->pDrvMediaEx, hIoReq);
+ if ( RT_FAILURE(rc)
+ && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("SCSI#%u: Flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ case VSCSIIOREQTXDIR_UNMAP:
+ {
+ PCRTRANGE paRanges;
+ unsigned cRanges;
+
+ rc = VSCSIIoReqUnmapParamsGet(hVScsiIoReq, &paRanges, &cRanges);
+ AssertRC(rc);
+
+ pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
+ rc = pThis->pDrvMediaEx->pfnIoReqDiscard(pThis->pDrvMediaEx, hIoReq, cRanges);
+ if ( RT_FAILURE(rc)
+ && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("SCSI#%u: Discard returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ case VSCSIIOREQTXDIR_READ:
+ case VSCSIIOREQTXDIR_WRITE:
+ {
+ uint64_t uOffset = 0;
+ size_t cbTransfer = 0;
+ size_t cbSeg = 0;
+ PCRTSGSEG paSeg = NULL;
+ unsigned cSeg = 0;
+
+ rc = VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer,
+ &cSeg, &cbSeg, &paSeg);
+ AssertRC(rc);
+
+ if (enmTxDir == VSCSIIOREQTXDIR_READ)
+ {
+ pThis->pLed->Asserted.s.fReading = pThis->pLed->Actual.s.fReading = 1;
+ rc = pThis->pDrvMediaEx->pfnIoReqRead(pThis->pDrvMediaEx, hIoReq, uOffset, cbTransfer);
+ }
+ else
+ {
+ pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
+ rc = pThis->pDrvMediaEx->pfnIoReqWrite(pThis->pDrvMediaEx, hIoReq, uOffset, cbTransfer);
+ }
+
+ if ( RT_FAILURE(rc)
+ && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("SCSI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ enmTxDir == VSCSIIOREQTXDIR_READ
+ ? "Read"
+ : "Write",
+ uOffset,
+ cbTransfer, rc));
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid transfer direction %u\n", enmTxDir));
+ }
+
+ if (rc == VINF_SUCCESS)
+ {
+ if (enmTxDir == VSCSIIOREQTXDIR_READ)
+ pThis->pLed->Actual.s.fReading = 0;
+ else if (enmTxDir == VSCSIIOREQTXDIR_WRITE)
+ pThis->pLed->Actual.s.fWriting = 0;
+ else
+ AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
+
+ VSCSIIoReqCompleted(hVScsiIoReq, VINF_SUCCESS, false);
+ rc = VINF_SUCCESS;
+ }
+ else if (rc == VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ {
+ if (enmTxDir == VSCSIIOREQTXDIR_READ)
+ pThis->pLed->Actual.s.fReading = 0;
+ else if (enmTxDir == VSCSIIOREQTXDIR_WRITE)
+ pThis->pLed->Actual.s.fWriting = 0;
+ else
+ AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
+
+ VSCSIIoReqCompleted(hVScsiIoReq, rc, drvscsiIsRedoPossible(rc));
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertMsgFailed(("Invalid return code rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunGetFeatureFlags}
+ */
+static DECLCALLBACK(int) drvscsiGetFeatureFlags(VSCSILUN hVScsiLun, void *pvScsiLunUser, uint64_t *pfFeatures)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ *pfFeatures = 0;
+
+ uint32_t fFeatures = 0;
+ int rc = pThis->pDrvMediaEx->pfnQueryFeatures(pThis->pDrvMediaEx, &fFeatures);
+ if (RT_SUCCESS(rc) && (fFeatures & PDMIMEDIAEX_FEATURE_F_DISCARD))
+ *pfFeatures |= VSCSI_LUN_FEATURE_UNMAP;
+
+ if ( pThis->pDrvMedia
+ && pThis->pDrvMedia->pfnIsNonRotational(pThis->pDrvMedia))
+ *pfFeatures |= VSCSI_LUN_FEATURE_NON_ROTATIONAL;
+
+ if (pThis->pDrvMedia->pfnIsReadOnly(pThis->pDrvMedia))
+ *pfFeatures |= VSCSI_LUN_FEATURE_READONLY;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VSCSILUNIOCALLBACKS,pfnVScsiLunQueryInqStrings}
+ */
+static DECLCALLBACK(int) drvscsiQueryInqStrings(VSCSILUN hVScsiLun, void *pvScsiLunUser, const char **ppszVendorId,
+ const char **ppszProductId, const char **ppszProductLevel)
+{
+ RT_NOREF(hVScsiLun);
+ PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
+
+ if (pThis->pDevMediaPort->pfnQueryScsiInqStrings)
+ return pThis->pDevMediaPort->pfnQueryScsiInqStrings(pThis->pDevMediaPort, ppszVendorId,
+ ppszProductId, ppszProductLevel);
+
+ return VERR_NOT_FOUND;
+}
+
+/* -=-=-=-=- IPortEx -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) drvscsiIoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF1(hIoReq);
+
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IPortEx);
+ VSCSIIOREQ hVScsiIoReq = DRVSCSI_PDMMEDIAEXIOREQ_2_VSCSIIOREQ(pvIoReqAlloc);
+ VSCSIIOREQTXDIR enmTxDir = VSCSIIoReqTxDirGet(hVScsiIoReq);
+
+ LogFlowFunc(("Request hVScsiIoReq=%#p completed\n", hVScsiIoReq));
+
+ if (enmTxDir == VSCSIIOREQTXDIR_READ)
+ pThis->pLed->Actual.s.fReading = 0;
+ else if ( enmTxDir == VSCSIIOREQTXDIR_WRITE
+ || enmTxDir == VSCSIIOREQTXDIR_UNMAP)
+ pThis->pLed->Actual.s.fWriting = 0;
+ else
+ AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
+
+ if (RT_SUCCESS(rcReq))
+ VSCSIIoReqCompleted(hVScsiIoReq, rcReq, false /* fRedoPossible */);
+ else
+ {
+ pThis->cErrors++;
+ if (pThis->cErrors < MAX_LOG_REL_ERRORS)
+ {
+ if (enmTxDir == VSCSIIOREQTXDIR_FLUSH)
+ LogRel(("SCSI#%u: Flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else if (enmTxDir == VSCSIIOREQTXDIR_UNMAP)
+ LogRel(("SCSI#%u: Unmap returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ {
+ uint64_t uOffset = 0;
+ size_t cbTransfer = 0;
+ size_t cbSeg = 0;
+ PCRTSGSEG paSeg = NULL;
+ unsigned cSeg = 0;
+
+ VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer,
+ &cSeg, &cbSeg, &paSeg);
+
+ LogRel(("SCSI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ enmTxDir == VSCSIIOREQTXDIR_READ
+ ? "Read"
+ : "Write",
+ uOffset,
+ cbTransfer, rcReq));
+ }
+ }
+
+ VSCSIIoReqCompleted(hVScsiIoReq, rcReq, drvscsiIsRedoPossible(rcReq));
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) drvscsiIoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+
+ VSCSIIOREQ hVScsiIoReq = DRVSCSI_PDMMEDIAEXIOREQ_2_VSCSIIOREQ(pvIoReqAlloc);
+ uint64_t uOffset = 0;
+ size_t cbTransfer = 0;
+ size_t cbSeg = 0;
+ PCRTSGSEG paSeg = NULL;
+ unsigned cSeg = 0;
+ size_t cbCopied = 0;
+
+ int rc = VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer, &cSeg, &cbSeg, &paSeg);
+ if (RT_SUCCESS(rc))
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, paSeg, cSeg);
+
+ RTSgBufAdvance(&SgBuf, offDst);
+ cbCopied = RTSgBufCopy(&SgBuf, pSgBuf, cbCopy);
+ }
+
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) drvscsiIoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+
+ VSCSIIOREQ hVScsiIoReq = DRVSCSI_PDMMEDIAEXIOREQ_2_VSCSIIOREQ(pvIoReqAlloc);
+ uint64_t uOffset = 0;
+ size_t cbTransfer = 0;
+ size_t cbSeg = 0;
+ PCRTSGSEG paSeg = NULL;
+ unsigned cSeg = 0;
+ size_t cbCopied = 0;
+
+ int rc = VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer, &cSeg, &cbSeg, &paSeg);
+ if (RT_SUCCESS(rc))
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, paSeg, cSeg);
+
+ RTSgBufAdvance(&SgBuf, offSrc);
+ cbCopied = RTSgBufCopy(pSgBuf, &SgBuf, cbCopy);
+ }
+
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryDiscardRanges}
+ */
+static DECLCALLBACK(int) drvscsiIoReqQueryDiscardRanges(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t idxRangeStart,
+ uint32_t cRanges, PRTRANGE paRanges,
+ uint32_t *pcRanges)
+{
+ RT_NOREF2(pInterface, hIoReq);
+
+ VSCSIIOREQ hVScsiIoReq = DRVSCSI_PDMMEDIAEXIOREQ_2_VSCSIIOREQ(pvIoReqAlloc);
+ PCRTRANGE paRangesVScsi;
+ unsigned cRangesVScsi;
+
+ int rc = VSCSIIoReqUnmapParamsGet(hVScsiIoReq, &paRangesVScsi, &cRangesVScsi);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRangesCopy = RT_MIN(cRangesVScsi - idxRangeStart, cRanges);
+ Assert( idxRangeStart < cRangesVScsi
+ && (idxRangeStart + cRanges) <= cRangesVScsi);
+
+ memcpy(paRanges, &paRangesVScsi[idxRangeStart], cRangesCopy * sizeof(RTRANGE));
+ *pcRanges = cRangesCopy;
+ }
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) drvscsiIoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF2(hIoReq, pvIoReqAlloc);
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IPortEx);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ /* Make sure the request is not accounted for so the VM can suspend successfully. */
+ uint32_t cTasksActive = ASMAtomicDecU32(&pThis->StatIoDepth);
+ if (!cTasksActive && pThis->fDummySignal)
+ PDMDrvHlpAsyncNotificationCompleted(pThis->pDrvIns);
+ break;
+ }
+ case PDMMEDIAEXIOREQSTATE_ACTIVE:
+ /* Make sure the request is accounted for so the VM suspends only when the request is complete. */
+ ASMAtomicIncU32(&pThis->StatIoDepth);
+ break;
+ default:
+ AssertMsgFailed(("Invalid request state given %u\n", enmState));
+ }
+
+ pThis->pDevMediaExPort->pfnIoReqStateChanged(pThis->pDevMediaExPort, hIoReq, pvIoReqAlloc, enmState);
+}
+
+
+/* -=-=-=-=- IMedia -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSize} */
+static DECLCALLBACK(uint64_t) drvscsiGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnGetSize(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSectorSize} */
+static DECLCALLBACK(uint32_t) drvscsiGetSectorSize(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnGetSectorSize(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsReadOnly} */
+static DECLCALLBACK(bool) drvscsiIsReadOnly(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnIsReadOnly(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvscsiIsNonRotational(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnIsNonRotational(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetPCHSGeometry} */
+static DECLCALLBACK(int) drvscsiBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnBiosGetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetPCHSGeometry} */
+static DECLCALLBACK(int) drvscsiBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnBiosSetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetLCHSGeometry} */
+static DECLCALLBACK(int) drvscsiBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnBiosGetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetLCHSGeometry} */
+static DECLCALLBACK(int) drvscsiBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnBiosSetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvscsiBiosIsVisible(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ return pThis->pDrvMedia->pfnBiosIsVisible(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetType} */
+static DECLCALLBACK(PDMMEDIATYPE) drvscsiGetType(PPDMIMEDIA pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+ VSCSILUNTYPE enmLunType;
+ PDMMEDIATYPE enmMediaType = PDMMEDIATYPE_ERROR;
+
+ int rc = VSCSIDeviceLunQueryType(pThis->hVScsiDevice, 0, &enmLunType);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmLunType)
+ {
+ case VSCSILUNTYPE_SBC:
+ enmMediaType = PDMMEDIATYPE_HARD_DISK;
+ break;
+ case VSCSILUNTYPE_MMC:
+ enmMediaType = PDMMEDIATYPE_CDROM;
+ break;
+ default:
+ enmMediaType = PDMMEDIATYPE_ERROR;
+ break;
+ }
+ }
+
+ return enmMediaType;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetUuid} */
+static DECLCALLBACK(int) drvscsiGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMedia);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->pDrvMedia)
+ rc = pThis->pDrvMedia->pfnGetUuid(pThis->pDrvMedia, pUuid);
+ else
+ RTUuidClear(pUuid);
+
+ return rc;
+}
+
+/* -=-=-=-=- IMediaEx -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures} */
+static DECLCALLBACK(int) drvscsiQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ RT_NOREF1(pInterface);
+
+ *pfFeatures = PDMIMEDIAEX_FEATURE_F_RAWSCSICMD;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnNotifySuspend} */
+static DECLCALLBACK(void) drvscsiNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMediaEx);
+
+ pThis->pDrvMediaEx->pfnNotifySuspend(pThis->pDrvMediaEx);
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet} */
+static DECLCALLBACK(int) drvscsiIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMediaEx);
+
+ pThis->cbIoReqAlloc = RT_UOFFSETOF_DYN(DRVSCSIREQ, abAlloc[cbIoReqAlloc]);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqAlloc} */
+static DECLCALLBACK(int) drvscsiIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ RT_NOREF2(uIoReqId, fFlags);
+
+ int rc = VINF_SUCCESS;
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMediaEx);
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)RTMemAllocZ(pThis->cbIoReqAlloc);
+ if (RT_LIKELY(pReq))
+ {
+ *phIoReq = (PDMMEDIAEXIOREQ)pReq;
+ *ppvIoReqAlloc = &pReq->abAlloc[0];
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqFree} */
+static DECLCALLBACK(int) drvscsiIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF1(pInterface);
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)hIoReq;
+
+ RTMemFree(pReq);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryResidual} */
+static DECLCALLBACK(int) drvscsiIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ RT_NOREF1(pInterface);
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)hIoReq;
+
+ if (pReq->cbXfer && pReq->cbXfer <= pReq->cbBuf)
+ *pcbResidual = pReq->cbBuf - pReq->cbXfer;
+ else
+ *pcbResidual = 0; /* Overflow/Underrun error or no data transfers. */
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize} */
+static DECLCALLBACK(int) drvscsiIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ RT_NOREF1(pInterface);
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)hIoReq;
+
+ *pcbXfer = pReq->cbXfer;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll} */
+static DECLCALLBACK(int) drvscsiIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel} */
+static DECLCALLBACK(int) drvscsiIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ RT_NOREF2(pInterface, uIoReqId);
+ return VERR_PDM_MEDIAEX_IOREQID_NOT_FOUND;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead} */
+static DECLCALLBACK(int) drvscsiIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ RT_NOREF4(pInterface, hIoReq, off, cbRead);
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite} */
+static DECLCALLBACK(int) drvscsiIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ RT_NOREF4(pInterface, hIoReq, off, cbWrite);
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush} */
+static DECLCALLBACK(int) drvscsiIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF2(pInterface, hIoReq);
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard} */
+static DECLCALLBACK(int) drvscsiIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ RT_NOREF3(pInterface, hIoReq, cRangesMax);
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd} */
+static DECLCALLBACK(int) drvscsiIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
+ const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
+ uint32_t cTimeoutMillies)
+{
+ RT_NOREF1(cTimeoutMillies);
+
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMediaEx);
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)hIoReq;
+ int rc = VINF_SUCCESS;
+
+ Log(("Dump for pReq=%#p Command: %s\n", pReq, SCSICmdText(pbCdb[0])));
+ Log(("cbCdb=%u\n", cbCdb));
+ for (uint32_t i = 0; i < cbCdb; i++)
+ Log(("pbCdb[%u]=%#x\n", i, pbCdb[i]));
+ Log(("cbBuf=%zu\n", cbBuf));
+
+ pReq->enmXferDir = enmTxDir;
+ pReq->cbBuf = cbBuf;
+ pReq->pu8ScsiSts = pu8ScsiSts;
+
+ /* Allocate and sync buffers if a data transfer is indicated. */
+ if (cbBuf)
+ {
+ pReq->pvBuf = RTMemAlloc(cbBuf);
+ if (RT_UNLIKELY(!pReq->pvBuf))
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pReq->Seg.pvSeg = pReq->pvBuf;
+ pReq->Seg.cbSeg = cbBuf;
+
+ if ( cbBuf
+ && ( enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN
+ || enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE))
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pReq->Seg, 1);
+ rc = pThis->pDevMediaExPort->pfnIoReqCopyToBuf(pThis->pDevMediaExPort, hIoReq, &pReq->abAlloc[0],
+ 0, &SgBuf, cbBuf);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VSCSIDeviceReqCreate(pThis->hVScsiDevice, &pReq->hVScsiReq,
+ uLun, (uint8_t *)pbCdb, cbCdb, cbBuf, 1, &pReq->Seg,
+ pabSense, cbSense, pReq);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicIncU32(&pThis->StatIoDepth);
+ rc = VSCSIDeviceReqEnqueue(pThis->hVScsiDevice, pReq->hVScsiReq);
+ if (RT_SUCCESS(rc))
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount} */
+static DECLCALLBACK(uint32_t) drvscsiIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMediaEx);
+ return pThis->StatIoDepth;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount} */
+static DECLCALLBACK(uint32_t) drvscsiIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return 0;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart} */
+static DECLCALLBACK(int) drvscsiIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc)
+{
+ RT_NOREF3(pInterface, phIoReq, ppvIoReqAlloc);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext} */
+static DECLCALLBACK(int) drvscsiIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ RT_NOREF4(pInterface, hIoReq, phIoReqNext, ppvIoReqAllocNext);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave} */
+static DECLCALLBACK(int) drvscsiIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF3(pInterface, pSSM, hIoReq);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad} */
+static DECLCALLBACK(int) drvscsiIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ RT_NOREF3(pInterface, pSSM, hIoReq);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+static DECLCALLBACK(void) drvscsiIoReqVScsiReqCompleted(VSCSIDEVICE hVScsiDevice, void *pVScsiDeviceUser,
+ void *pVScsiReqUser, int rcScsiCode, bool fRedoPossible,
+ int rcReq, size_t cbXfer)
+{
+ RT_NOREF2(hVScsiDevice, fRedoPossible);
+ PDRVSCSI pThis = (PDRVSCSI)pVScsiDeviceUser;
+ PDRVSCSIREQ pReq = (PDRVSCSIREQ)pVScsiReqUser;
+
+ ASMAtomicDecU32(&pThis->StatIoDepth);
+
+ /* Sync buffers. */
+ if ( RT_SUCCESS(rcReq)
+ && pReq->cbBuf
+ && ( pReq->enmXferDir == PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN
+ || pReq->enmXferDir == PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE))
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pReq->Seg, 1);
+ int rcCopy = pThis->pDevMediaExPort->pfnIoReqCopyFromBuf(pThis->pDevMediaExPort, (PDMMEDIAEXIOREQ)pReq,
+ &pReq->abAlloc[0], 0, &SgBuf, pReq->cbBuf);
+ if (RT_FAILURE(rcCopy))
+ rcReq = rcCopy;
+ }
+
+ if (pReq->pvBuf)
+ {
+ RTMemFree(pReq->pvBuf);
+ pReq->pvBuf = NULL;
+ }
+
+ *pReq->pu8ScsiSts = (uint8_t)rcScsiCode;
+ pReq->cbXfer = cbXfer;
+ int rc = pThis->pDevMediaExPort->pfnIoReqCompleteNotify(pThis->pDevMediaExPort, (PDMMEDIAEXIOREQ)pReq,
+ &pReq->abAlloc[0], rcReq);
+ AssertRC(rc); RT_NOREF(rc);
+
+ if (RT_UNLIKELY(pThis->fDummySignal) && !pThis->StatIoDepth)
+ PDMDrvHlpAsyncNotificationCompleted(pThis->pDrvIns);
+}
+
+/**
+ * Consumer for the queue
+ *
+ * @returns Success indicator.
+ * If false the item will not be removed and the flushing will stop.
+ * @param pDrvIns The driver instance.
+ * @param pItem The item to consume. Upon return this item will be freed.
+ */
+static DECLCALLBACK(bool) drvscsiR3NotifyQueueConsumer(PPDMDRVINS pDrvIns, PPDMQUEUEITEMCORE pItem)
+{
+ PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)pItem;
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ int rc = pThis->pDrvMount->pfnUnmount(pThis->pDrvMount, false/*=fForce*/, true/*=fEject*/);
+ Assert(RT_SUCCESS(rc) || rc == VERR_PDM_MEDIA_LOCKED || rc == VERR_PDM_MEDIA_NOT_MOUNTED);
+ if (RT_SUCCESS(rc))
+ pThis->pDevMediaExPort->pfnMediumEjected(pThis->pDevMediaExPort);
+
+ pEjectState->rcReq = rc;
+ RTSemEventSignal(pEjectState->hSemEvt);
+ return true;
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvscsiQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, pThis->pDrvMount);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, pThis->pDevMediaExPort ? &pThis->IMediaEx : NULL);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, pThis->pDrvMedia ? &pThis->IMedia : NULL);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pThis->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pThis->IMountNotify);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pThis->IPortEx);
+ return NULL;
+}
+
+static DECLCALLBACK(int) drvscsiQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IPort);
+
+ return pThis->pDevMediaPort->pfnQueryDeviceLocation(pThis->pDevMediaPort, ppcszController,
+ piInstance, piLUN);
+}
+
+/**
+ * Called when media is mounted.
+ *
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) drvscsiMountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMountNotify);
+ LogFlowFunc(("mounting LUN#%p\n", pThis->hVScsiLun));
+
+ /* Ignore the call if we're called while being attached. */
+ if (!pThis->pDrvMedia)
+ return;
+
+ /* Let the LUN know that a medium was mounted. */
+ VSCSILunMountNotify(pThis->hVScsiLun);
+}
+
+/**
+ * Called when media is unmounted
+ *
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ */
+static DECLCALLBACK(void) drvscsiUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PDRVSCSI pThis = RT_FROM_MEMBER(pInterface, DRVSCSI, IMountNotify);
+ LogFlowFunc(("unmounting LUN#%p\n", pThis->hVScsiLun));
+
+ /* Let the LUN know that the medium was unmounted. */
+ VSCSILunUnmountNotify(pThis->hVScsiLun);
+}
+
+/**
+ * Worker for drvscsiReset, drvscsiSuspend and drvscsiPowerOff.
+ *
+ * @param pDrvIns The driver instance.
+ * @param pfnAsyncNotify The async callback.
+ */
+static void drvscsiR3ResetOrSuspendOrPowerOff(PPDMDRVINS pDrvIns, PFNPDMDRVASYNCNOTIFY pfnAsyncNotify)
+{
+ RT_NOREF1(pfnAsyncNotify);
+
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ if (pThis->StatIoDepth > 0)
+ ASMAtomicWriteBool(&pThis->fDummySignal, true);
+}
+
+/**
+ * Callback employed by drvscsiSuspend and drvscsiPowerOff.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDrvIns The driver instance.
+ */
+static DECLCALLBACK(bool) drvscsiIsAsyncSuspendOrPowerOffDone(PPDMDRVINS pDrvIns)
+{
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ if (pThis->StatIoDepth > 0)
+ return false;
+ else
+ return true;
+}
+
+/**
+ * @copydoc FNPDMDRVPOWEROFF
+ */
+static DECLCALLBACK(void) drvscsiPowerOff(PPDMDRVINS pDrvIns)
+{
+ drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
+}
+
+/**
+ * @copydoc FNPDMDRVSUSPEND
+ */
+static DECLCALLBACK(void) drvscsiSuspend(PPDMDRVINS pDrvIns)
+{
+ drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
+}
+
+/**
+ * Callback employed by drvscsiReset.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDrvIns The driver instance.
+ */
+static DECLCALLBACK(bool) drvscsiIsAsyncResetDone(PPDMDRVINS pDrvIns)
+{
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ if (pThis->StatIoDepth > 0)
+ return false;
+ else
+ return true;
+}
+
+/** @copydoc FNPDMDRVATTACH */
+static DECLCALLBACK(int) drvscsiAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ LogFlowFunc(("pDrvIns=%#p fFlags=%#x\n", pDrvIns, fFlags));
+
+ AssertMsgReturn((fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG),
+ ("SCSI: Hotplugging is not supported\n"),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Try attach driver below and query it's media interface.
+ */
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pThis->pDrvBase);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Attaching driver below failed rc=%Rrc\n", rc), rc);
+
+ /*
+ * Query the media interface.
+ */
+ pThis->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pThis->pDrvMedia), ("VSCSI configuration error: No media interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Query the extended media interface. */
+ pThis->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pThis->pDrvMediaEx), ("VSCSI configuration error: No extended media interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ pThis->pDrvMount = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMOUNT);
+
+ if (pThis->cbVScsiIoReqAlloc)
+ {
+ rc = pThis->pDrvMediaEx->pfnIoReqAllocSizeSet(pThis->pDrvMediaEx, pThis->cbVScsiIoReqAlloc);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Setting the I/O request allocation size failed with rc=%Rrc\n", rc), rc);
+ }
+
+ if (pThis->pDrvMount)
+ {
+ if (pThis->pDrvMount->pfnIsMounted(pThis->pDrvMount))
+ {
+ rc = VINF_SUCCESS; VSCSILunMountNotify(pThis->hVScsiLun);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being mounted\n"), rc);
+ }
+ else
+ {
+ rc = VINF_SUCCESS; VSCSILunUnmountNotify(pThis->hVScsiLun);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being unmounted\n"), rc);
+ }
+ }
+
+ return rc;
+}
+
+/** @copydoc FNPDMDRVDETACH */
+static DECLCALLBACK(void) drvscsiDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+
+ RT_NOREF(fFlags);
+ LogFlowFunc(("pDrvIns=%#p fFlags=%#x\n", pDrvIns, fFlags));
+
+ /*
+ * Zero some important members.
+ */
+ pThis->pDrvBase = NULL;
+ pThis->pDrvMedia = NULL;
+ pThis->pDrvMediaEx = NULL;
+ pThis->pDrvMount = NULL;
+
+ VSCSILunUnmountNotify(pThis->hVScsiLun);
+}
+
+/**
+ * @copydoc FNPDMDRVRESET
+ */
+static DECLCALLBACK(void) drvscsiReset(PPDMDRVINS pDrvIns)
+{
+ drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncResetDone);
+}
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvscsiDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ /* Free the VSCSI device and LUN handle. */
+ if (pThis->hVScsiDevice)
+ {
+ VSCSILUN hVScsiLun;
+ int rc = VSCSIDeviceLunDetach(pThis->hVScsiDevice, 0, &hVScsiLun);
+ AssertRC(rc);
+
+ Assert(hVScsiLun == pThis->hVScsiLun);
+ rc = VSCSILunDestroy(hVScsiLun);
+ AssertRC(rc);
+ rc = VSCSIDeviceDestroy(pThis->hVScsiDevice);
+ AssertRC(rc);
+
+ pThis->hVScsiDevice = NULL;
+ pThis->hVScsiLun = NULL;
+ }
+}
+
+/**
+ * Construct a block driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvscsiConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+ LogFlowFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Initialize the instance data.
+ */
+ pThis->pDrvIns = pDrvIns;
+
+ pDrvIns->IBase.pfnQueryInterface = drvscsiQueryInterface;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = NULL;
+ pThis->IMedia.pfnReadPcBios = NULL;
+ pThis->IMedia.pfnWrite = NULL;
+ pThis->IMedia.pfnFlush = NULL;
+ pThis->IMedia.pfnSendCmd = NULL;
+ pThis->IMedia.pfnMerge = NULL;
+ pThis->IMedia.pfnSetSecKeyIf = NULL;
+ pThis->IMedia.pfnGetSize = drvscsiGetSize;
+ pThis->IMedia.pfnGetSectorSize = drvscsiGetSectorSize;
+ pThis->IMedia.pfnIsReadOnly = drvscsiIsReadOnly;
+ pThis->IMedia.pfnIsNonRotational = drvscsiIsNonRotational;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvscsiBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvscsiBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvscsiBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvscsiBiosSetLCHSGeometry;
+ pThis->IMedia.pfnBiosIsVisible = drvscsiBiosIsVisible;
+ pThis->IMedia.pfnGetType = drvscsiGetType;
+ pThis->IMedia.pfnGetUuid = drvscsiGetUuid;
+ pThis->IMedia.pfnDiscard = NULL;
+
+ /* IMediaEx */
+ pThis->IMediaEx.pfnQueryFeatures = drvscsiQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvscsiNotifySuspend;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvscsiIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvscsiIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvscsiIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvscsiIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvscsiIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvscsiIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvscsiIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvscsiIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvscsiIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvscsiIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvscsiIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqSendScsiCmd = drvscsiIoReqSendScsiCmd;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvscsiIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvscsiIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvscsiIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvscsiIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvscsiIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvscsiIoReqSuspendedLoad;
+
+ pThis->IMountNotify.pfnMountNotify = drvscsiMountNotify;
+ pThis->IMountNotify.pfnUnmountNotify = drvscsiUnmountNotify;
+ pThis->IPort.pfnQueryDeviceLocation = drvscsiQueryDeviceLocation;
+ pThis->IPortEx.pfnIoReqCompleteNotify = drvscsiIoReqCompleteNotify;
+ pThis->IPortEx.pfnIoReqCopyFromBuf = drvscsiIoReqCopyFromBuf;
+ pThis->IPortEx.pfnIoReqCopyToBuf = drvscsiIoReqCopyToBuf;
+ pThis->IPortEx.pfnIoReqQueryBuf = NULL;
+ pThis->IPortEx.pfnIoReqQueryDiscardRanges = drvscsiIoReqQueryDiscardRanges;
+ pThis->IPortEx.pfnIoReqStateChanged = drvscsiIoReqStateChanged;
+
+ /* Query the optional media port interface above. */
+ pThis->pDevMediaPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAPORT);
+
+ /* Query the optional extended media port interface above. */
+ pThis->pDevMediaExPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAEXPORT);
+
+ AssertMsgReturn(pThis->pDevMediaExPort,
+ ("Missing extended media port interface above\n"), VERR_PDM_MISSING_INTERFACE);
+
+ /* Query the optional LED interface above. */
+ pThis->pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
+ if (pThis->pLedPort != NULL)
+ {
+ /* Get The Led. */
+ rc = pThis->pLedPort->pfnQueryStatusLed(pThis->pLedPort, 0, &pThis->pLed);
+ if (RT_FAILURE(rc))
+ pThis->pLed = &pThis->Led;
+ }
+ else
+ pThis->pLed = &pThis->Led;
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, ""))
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("SCSI configuration error: unknown option specified"));
+
+ /*
+ * Try attach driver below and query it's media interface.
+ */
+ rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pThis->pDrvBase);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Query the media interface.
+ */
+ pThis->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(VALID_PTR(pThis->pDrvMedia), ("VSCSI configuration error: No media interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Query the extended media interface. */
+ pThis->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(VALID_PTR(pThis->pDrvMediaEx), ("VSCSI configuration error: No extended media interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ pThis->pDrvMount = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMOUNT);
+
+ PDMMEDIATYPE enmType = pThis->pDrvMedia->pfnGetType(pThis->pDrvMedia);
+ VSCSILUNTYPE enmLunType;
+ switch (enmType)
+ {
+ case PDMMEDIATYPE_HARD_DISK:
+ enmLunType = VSCSILUNTYPE_SBC;
+ break;
+ case PDMMEDIATYPE_CDROM:
+ case PDMMEDIATYPE_DVD:
+ enmLunType = VSCSILUNTYPE_MMC;
+ break;
+ default:
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_UNSUPPORTED_BLOCK_TYPE, RT_SRC_POS,
+ N_("Only hard disks and CD/DVD-ROMs are currently supported as SCSI devices (enmType=%d)"),
+ enmType);
+ }
+ if ( ( enmType == PDMMEDIATYPE_DVD
+ || enmType == PDMMEDIATYPE_CDROM)
+ && !pThis->pDrvMount)
+ {
+ AssertMsgFailed(("Internal error: cdrom without a mountable interface\n"));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ /* Create VSCSI device and LUN. */
+ pThis->VScsiIoCallbacks.pfnVScsiLunReqAllocSizeSet = drvscsiReqAllocSizeSet;
+ pThis->VScsiIoCallbacks.pfnVScsiLunReqAlloc = drvscsiReqAlloc;
+ pThis->VScsiIoCallbacks.pfnVScsiLunReqFree = drvscsiReqFree;
+ pThis->VScsiIoCallbacks.pfnVScsiLunMediumGetRegionCount = drvscsiGetRegionCount;
+ pThis->VScsiIoCallbacks.pfnVScsiLunMediumQueryRegionProperties = drvscsiQueryRegionProperties;
+ pThis->VScsiIoCallbacks.pfnVScsiLunMediumQueryRegionPropertiesForLba = drvscsiQueryRegionPropertiesForLba;
+ pThis->VScsiIoCallbacks.pfnVScsiLunMediumEject = drvscsiEject;
+ pThis->VScsiIoCallbacks.pfnVScsiLunReqTransferEnqueue = drvscsiReqTransferEnqueue;
+ pThis->VScsiIoCallbacks.pfnVScsiLunGetFeatureFlags = drvscsiGetFeatureFlags;
+ pThis->VScsiIoCallbacks.pfnVScsiLunMediumSetLock = drvscsiSetLock;
+ pThis->VScsiIoCallbacks.pfnVScsiLunQueryInqStrings = drvscsiQueryInqStrings;
+
+ rc = VSCSIDeviceCreate(&pThis->hVScsiDevice, drvscsiIoReqVScsiReqCompleted, pThis);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI device rc=%Rrc\n", rc), rc);
+ rc = VSCSILunCreate(&pThis->hVScsiLun, enmLunType, &pThis->VScsiIoCallbacks,
+ pThis);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI LUN rc=%Rrc\n", rc), rc);
+ rc = VSCSIDeviceLunAttach(pThis->hVScsiDevice, pThis->hVScsiLun, 0);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to attached the LUN to the SCSI device\n"), rc);
+
+ /// @todo This is a very hacky way of telling the LUN whether a medium was mounted.
+ // The mount/unmount interface doesn't work in a very sensible manner!
+ if (pThis->pDrvMount)
+ {
+ if (pThis->pDrvMount->pfnIsMounted(pThis->pDrvMount))
+ {
+ rc = VINF_SUCCESS; VSCSILunMountNotify(pThis->hVScsiLun);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being mounted\n"), rc);
+ }
+ else
+ {
+ rc = VINF_SUCCESS; VSCSILunUnmountNotify(pThis->hVScsiLun);
+ AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being unmounted\n"), rc);
+ }
+ }
+
+ uint32_t fFeatures = 0;
+ rc = pThis->pDrvMediaEx->pfnQueryFeatures(pThis->pDrvMediaEx, &fFeatures);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("VSCSI configuration error: Failed to query features of device"));
+ if (fFeatures & PDMIMEDIAEX_FEATURE_F_DISCARD)
+ LogRel(("SCSI#%d: Enabled UNMAP support\n", pDrvIns->iInstance));
+
+ rc = PDMDrvHlpQueueCreate(pDrvIns, sizeof(DRVSCSIEJECTSTATE), 1, 0, drvscsiR3NotifyQueueConsumer,
+ "SCSI-Eject", &pThis->pQueue);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("VSCSI configuration error: Failed to create notification queue"));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * SCSI driver registration record.
+ */
+const PDMDRVREG g_DrvSCSI =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "SCSI",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Generic SCSI driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_SCSI,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVSCSI),
+ /* pfnConstruct */
+ drvscsiConstruct,
+ /* pfnDestruct */
+ drvscsiDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ drvscsiReset,
+ /* pfnSuspend */
+ drvscsiSuspend,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ drvscsiAttach,
+ /* pfnDetach */
+ drvscsiDetach,
+ /* pfnPowerOff */
+ drvscsiPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Devices/Storage/DrvVD.cpp b/src/VBox/Devices/Storage/DrvVD.cpp
new file mode 100644
index 00000000..45ac1bc4
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvVD.cpp
@@ -0,0 +1,6028 @@
+/* $Id: DrvVD.cpp $ */
+/** @file
+ * DrvVD - Generic VBox disk media driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/vd.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmasynccompletion.h>
+#include <VBox/vmm/pdmblkcache.h>
+#include <VBox/vmm/ssm.h>
+#include <iprt/asm.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/semaphore.h>
+#include <iprt/sg.h>
+#include <iprt/poll.h>
+#include <iprt/pipe.h>
+#include <iprt/system.h>
+#include <iprt/memsafer.h>
+#include <iprt/memcache.h>
+#include <iprt/list.h>
+
+#ifdef VBOX_WITH_INIP
+/* All lwip header files are not C++ safe. So hack around this. */
+RT_C_DECLS_BEGIN
+#include <lwip/opt.h>
+#include <lwip/inet.h>
+#include <lwip/tcp.h>
+#include <lwip/sockets.h>
+# if LWIP_IPV6
+# include <lwip/inet6.h>
+# endif
+RT_C_DECLS_END
+#endif /* VBOX_WITH_INIP */
+
+#include "HBDMgmt.h"
+#include "IOBufMgmt.h"
+
+#include "VBoxDD.h"
+
+#ifdef VBOX_WITH_INIP
+/* Small hack to get at lwIP initialized status */
+extern bool DevINIPConfigured(void);
+#endif /* VBOX_WITH_INIP */
+
+
+/** @def VBOX_PERIODIC_FLUSH
+ * Enable support for periodically flushing the VDI to disk. This may prove
+ * useful for those nasty problems with the ultra-slow host filesystems.
+ * If this is enabled, it can be configured via the CFGM key
+ * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/FlushInterval". @verbatim<x>@endverbatim
+ * must be replaced with the correct LUN number of the disk that should
+ * do the periodic flushes. The value of the key is the number of bytes
+ * written between flushes. A value of 0 (the default) denotes no flushes. */
+#define VBOX_PERIODIC_FLUSH
+
+/** @def VBOX_IGNORE_FLUSH
+ * Enable support for ignoring VDI flush requests. This can be useful for
+ * filesystems that show bad guest IDE write performance (especially with
+ * Windows guests). NOTE that this does not disable the flushes caused by
+ * the periodic flush cache feature above.
+ * If this feature is enabled, it can be configured via the CFGM key
+ * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/IgnoreFlush". @verbatim<x>@endverbatim
+ * must be replaced with the correct LUN number of the disk that should
+ * ignore flush requests. The value of the key is a boolean. The default
+ * is to ignore flushes, i.e. true. */
+#define VBOX_IGNORE_FLUSH
+
+
+/*********************************************************************************************************************************
+* Defined types, constants and macros *
+*********************************************************************************************************************************/
+
+/** Converts a pointer to VBOXDISK::IMedia to a PVBOXDISK. */
+#define PDMIMEDIA_2_VBOXDISK(pInterface) \
+ ( (PVBOXDISK)((uintptr_t)pInterface - RT_UOFFSETOF(VBOXDISK, IMedia)) )
+
+/** Saved state version of an I/O request .*/
+#define DRVVD_IOREQ_SAVED_STATE_VERSION UINT32_C(1)
+/** Maximum number of request errors in the release log before muting. */
+#define DRVVD_MAX_LOG_REL_ERRORS 100
+
+/** Forward declaration for the dis kcontainer. */
+typedef struct VBOXDISK *PVBOXDISK;
+
+/**
+ * VBox disk container, image information, private part.
+ */
+
+typedef struct VBOXIMAGE
+{
+ /** Pointer to next image. */
+ struct VBOXIMAGE *pNext;
+ /** Pointer to list of VD interfaces. Per-image. */
+ PVDINTERFACE pVDIfsImage;
+ /** Configuration information interface. */
+ VDINTERFACECONFIG VDIfConfig;
+ /** TCP network stack interface. */
+ VDINTERFACETCPNET VDIfTcpNet;
+ /** I/O interface. */
+ VDINTERFACEIO VDIfIo;
+} VBOXIMAGE, *PVBOXIMAGE;
+
+/**
+ * Storage backend data.
+ */
+typedef struct DRVVDSTORAGEBACKEND
+{
+ /** PDM async completion end point. */
+ PPDMASYNCCOMPLETIONENDPOINT pEndpoint;
+ /** The template. */
+ PPDMASYNCCOMPLETIONTEMPLATE pTemplate;
+ /** Event semaphore for synchronous operations. */
+ RTSEMEVENT EventSem;
+ /** Flag whether a synchronous operation is currently pending. */
+ volatile bool fSyncIoPending;
+ /** Return code of the last completed request. */
+ int rcReqLast;
+ /** Callback routine */
+ PFNVDCOMPLETED pfnCompleted;
+} DRVVDSTORAGEBACKEND, *PDRVVDSTORAGEBACKEND;
+
+/**
+ * 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. */
+ PVBOXDISK 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;
+ /** Pointer to the S/G buffer. */
+ PRTSGBUF pSgBuf;
+ /** Flag whether the pointer is a direct buffer or
+ * was allocated by us. */
+ bool fDirectBuf;
+ /** Buffer management data based on the fDirectBuf flag. */
+ union
+ {
+ /** Direct buffer. */
+ struct
+ {
+ /** Segment for the data buffer. */
+ RTSGSEG Seg;
+ /** S/G buffer structure. */
+ RTSGBUF SgBuf;
+ } Direct;
+ /** 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
+
+/**
+ * VBox disk container media main structure, private part.
+ *
+ * @implements PDMIMEDIA
+ * @implements PDMIMEDIAEX
+ * @implements PDMIMOUNT
+ * @implements VDINTERFACEERROR
+ * @implements VDINTERFACETCPNET
+ * @implements VDINTERFACEASYNCIO
+ * @implements VDINTERFACECONFIG
+ */
+typedef struct VBOXDISK
+{
+ /** The VBox disk container. */
+ PVDISK pDisk;
+ /** The media interface. */
+ PDMIMEDIA IMedia;
+ /** Media port. */
+ PPDMIMEDIAPORT pDrvMediaPort;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Flag whether suspend has changed image open mode to read only. */
+ bool fTempReadOnly;
+ /** Flag whether to use the runtime (true) or startup error facility. */
+ bool fErrorUseRuntime;
+ /** Pointer to list of VD interfaces. Per-disk. */
+ PVDINTERFACE pVDIfsDisk;
+ /** Error interface. */
+ VDINTERFACEERROR VDIfError;
+ /** Thread synchronization interface. */
+ VDINTERFACETHREADSYNC VDIfThreadSync;
+
+ /** Flag whether opened disk supports async I/O operations. */
+ bool fAsyncIOSupported;
+ /** Pointer to the list of data we need to keep per image. */
+ PVBOXIMAGE pImages;
+ /** Flag whether the media should allow concurrent open for writing. */
+ bool fShareable;
+ /** Flag whether a merge operation has been set up. */
+ bool fMergePending;
+ /** Synchronization to prevent destruction before merge finishes. */
+ RTSEMFASTMUTEX MergeCompleteMutex;
+ /** Synchronization between merge and other image accesses. */
+ RTSEMRW MergeLock;
+ /** Source image index for merging. */
+ unsigned uMergeSource;
+ /** Target image index for merging. */
+ unsigned uMergeTarget;
+
+ /** Flag whether boot acceleration is enabled. */
+ bool fBootAccelEnabled;
+ /** Flag whether boot acceleration is currently active. */
+ bool fBootAccelActive;
+ /** Size of the disk, used for read truncation. */
+ uint64_t cbDisk;
+ /** Size of the configured buffer. */
+ size_t cbBootAccelBuffer;
+ /** Start offset for which the buffer holds data. */
+ uint64_t offDisk;
+ /** Number of valid bytes in the buffer. */
+ size_t cbDataValid;
+ /** The disk buffer. */
+ uint8_t *pbData;
+ /** Bandwidth group the disk is assigned to. */
+ char *pszBwGroup;
+ /** Flag whether async I/O using the host cache is enabled. */
+ bool fAsyncIoWithHostCache;
+
+ /** I/O interface for a cache image. */
+ VDINTERFACEIO VDIfIoCache;
+ /** Interface list for the cache image. */
+ PVDINTERFACE pVDIfsCache;
+
+ /** The block cache handle if configured. */
+ PPDMBLKCACHE pBlkCache;
+ /** Host block device manager. */
+ HBDMGR hHbdMgr;
+
+ /** Drive type. */
+ PDMMEDIATYPE enmType;
+ /** Locked indicator. */
+ bool fLocked;
+ /** Mountable indicator. */
+ bool fMountable;
+ /** Visible to the BIOS. */
+ bool fBiosVisible;
+ /** Flag whether this medium should be presented as non rotational. */
+ bool fNonRotational;
+ /** Flag whether a suspend is in progress right now. */
+ volatile bool fSuspending;
+#ifdef VBOX_PERIODIC_FLUSH
+ /** HACK: Configuration value for number of bytes written after which to flush. */
+ uint32_t cbFlushInterval;
+ /** HACK: Current count for the number of bytes written since the last flush. */
+ uint32_t cbDataWritten;
+#endif /* VBOX_PERIODIC_FLUSH */
+#ifdef VBOX_IGNORE_FLUSH
+ /** HACK: Disable flushes for this drive. */
+ bool fIgnoreFlush;
+ /** Disable async flushes for this drive. */
+ bool fIgnoreFlushAsync;
+#endif /* VBOX_IGNORE_FLUSH */
+ /** Our mountable interface. */
+ PDMIMOUNT IMount;
+ /** Pointer to the mount notify interface above us. */
+ PPDMIMOUNTNOTIFY pDrvMountNotify;
+ /** Uuid of the drive. */
+ RTUUID Uuid;
+ /** BIOS PCHS Geometry. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** BIOS LCHS Geometry. */
+ PDMMEDIAGEOMETRY LCHSGeometry;
+ /** Region list. */
+ PVDREGIONLIST pRegionList;
+
+ /** Cryptographic support
+ * @{ */
+ /** Pointer to the CFGM node containing the config of the crypto filter
+ * if enable. */
+ PCFGMNODE pCfgCrypto;
+ /** Config interface for the encryption filter. */
+ VDINTERFACECONFIG VDIfCfg;
+ /** Crypto interface for the encryption filter. */
+ VDINTERFACECRYPTO VDIfCrypto;
+ /** The secret key interface used to retrieve keys. */
+ PPDMISECKEY pIfSecKey;
+ /** The secret key helper interface used to notify about missing keys. */
+ PPDMISECKEYHLP pIfSecKeyHlp;
+ /** @} */
+
+ /** @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;
+ /** @} */
+
+ /** @name Statistics.
+ * @{ */
+ /** How many attempts were made to query a direct buffer pointer from the
+ * device/driver above. */
+ STAMCOUNTER StatQueryBufAttempts;
+ /** How many attempts to query a direct buffer pointer succeeded. */
+ STAMCOUNTER StatQueryBufSuccess;
+ /** Release statistics: number of bytes written. */
+ STAMCOUNTER StatBytesWritten;
+ /** Release statistics: number of bytes read. */
+ STAMCOUNTER StatBytesRead;
+ /** Release statistics: Number of requests submitted. */
+ STAMCOUNTER StatReqsSubmitted;
+ /** Release statistics: Number of requests failed. */
+ STAMCOUNTER StatReqsFailed;
+ /** Release statistics: Number of requests succeeded. */
+ STAMCOUNTER StatReqsSucceeded;
+ /** Release statistics: Number of flush requests. */
+ STAMCOUNTER StatReqsFlush;
+ /** Release statistics: Number of write requests. */
+ STAMCOUNTER StatReqsWrite;
+ /** Release statistics: Number of read requests. */
+ STAMCOUNTER StatReqsRead;
+ /** Release statistics: Number of discard requests. */
+ STAMCOUNTER StatReqsDiscard;
+ /** Release statistics: Number of I/O requests processed per second. */
+ STAMCOUNTER StatReqsPerSec;
+ /** @} */
+} VBOXDISK;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(void) drvvdMediaExIoReqComplete(void *pvUser1, void *pvUser2, int rcReq);
+static void drvvdPowerOffOrDestructOrUnmount(PPDMDRVINS pDrvIns);
+DECLINLINE(void) drvvdMediaExIoReqBufFree(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq);
+static int drvvdMediaExIoReqCompleteWorker(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, int rcReq, bool fUpNotify);
+static int drvvdMediaExIoReqReadWriteProcess(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fUpNotify);
+
+/**
+ * Internal: allocate new image descriptor and put it in the list
+ */
+static PVBOXIMAGE drvvdNewImage(PVBOXDISK pThis)
+{
+ AssertPtr(pThis);
+ PVBOXIMAGE pImage = (PVBOXIMAGE)RTMemAllocZ(sizeof(VBOXIMAGE));
+ if (pImage)
+ {
+ pImage->pVDIfsImage = NULL;
+ PVBOXIMAGE *pp = &pThis->pImages;
+ while (*pp != NULL)
+ pp = &(*pp)->pNext;
+ *pp = pImage;
+ pImage->pNext = NULL;
+ }
+
+ return pImage;
+}
+
+/**
+ * Internal: free the list of images descriptors.
+ */
+static void drvvdFreeImages(PVBOXDISK pThis)
+{
+ while (pThis->pImages != NULL)
+ {
+ PVBOXIMAGE p = pThis->pImages;
+ pThis->pImages = pThis->pImages->pNext;
+ RTMemFree(p);
+ }
+}
+
+
+/**
+ * Make the image temporarily read-only.
+ *
+ * @returns VBox status code.
+ * @param pThis The driver instance data.
+ */
+static int drvvdSetReadonly(PVBOXDISK pThis)
+{
+ int rc = VINF_SUCCESS;
+ if ( pThis->pDisk
+ && !VDIsReadOnly(pThis->pDisk))
+ {
+ unsigned uOpenFlags;
+ rc = VDGetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, &uOpenFlags);
+ AssertRC(rc);
+ uOpenFlags |= VD_OPEN_FLAGS_READONLY;
+ rc = VDSetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, uOpenFlags);
+ AssertRC(rc);
+ pThis->fTempReadOnly = true;
+ }
+ return rc;
+}
+
+
+/**
+ * Undo the temporary read-only status of the image.
+ *
+ * @returns VBox status code.
+ * @param pThis The driver instance data.
+ */
+static int drvvdSetWritable(PVBOXDISK pThis)
+{
+ int rc = VINF_SUCCESS;
+ if (pThis->fTempReadOnly)
+ {
+ unsigned uOpenFlags;
+ rc = VDGetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, &uOpenFlags);
+ AssertRC(rc);
+ uOpenFlags &= ~VD_OPEN_FLAGS_READONLY;
+ rc = VDSetOpenFlags(pThis->pDisk, VD_LAST_IMAGE, uOpenFlags);
+ if (RT_SUCCESS(rc))
+ pThis->fTempReadOnly = false;
+ else
+ AssertRC(rc);
+ }
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Error reporting callback *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(void) drvvdErrorCallback(void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszFormat, va_list va)
+{
+ PPDMDRVINS pDrvIns = (PPDMDRVINS)pvUser;
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ if (pThis->fErrorUseRuntime)
+ /* We must not pass VMSETRTERR_FLAGS_FATAL as it could lead to a
+ * deadlock: We are probably executed in a thread context != EMT
+ * and the EM thread would wait until every thread is suspended
+ * but we would wait for the EM thread ... */
+
+ PDMDrvHlpVMSetRuntimeErrorV(pDrvIns, /* fFlags=*/ 0, "DrvVD", pszFormat, va);
+ else
+ PDMDrvHlpVMSetErrorV(pDrvIns, rc, RT_SRC_POS_ARGS, pszFormat, va);
+}
+
+
+/*********************************************************************************************************************************
+* VD Async I/O interface implementation *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_PDM_ASYNC_COMPLETION
+
+static DECLCALLBACK(void) drvvdAsyncTaskCompleted(PPDMDRVINS pDrvIns, void *pvTemplateUser, void *pvUser, int rcReq)
+{
+ RT_NOREF(pDrvIns);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pvTemplateUser;
+
+ LogFlowFunc(("pDrvIns=%#p pvTemplateUser=%#p pvUser=%#p rcReq=%d\n",
+ pDrvIns, pvTemplateUser, pvUser, rcReq));
+
+ if (pStorageBackend->fSyncIoPending)
+ {
+ Assert(!pvUser);
+ pStorageBackend->rcReqLast = rcReq;
+ ASMAtomicWriteBool(&pStorageBackend->fSyncIoPending, false);
+ RTSemEventSignal(pStorageBackend->EventSem);
+ }
+ else
+ {
+ int rc;
+
+ AssertPtr(pvUser);
+
+ AssertPtr(pStorageBackend->pfnCompleted);
+ rc = pStorageBackend->pfnCompleted(pvUser, rcReq);
+ AssertRC(rc);
+ }
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOOpen(void *pvUser, const char *pszLocation,
+ uint32_t fOpen,
+ PFNVDCOMPLETED pfnCompleted,
+ void **ppStorage)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+ PDRVVDSTORAGEBACKEND pStorageBackend = NULL;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Check whether the backend wants to open a block device and try to prepare it
+ * if we didn't claim it yet.
+ *
+ * We only create a block device manager on demand to not waste any resources.
+ */
+ if (HBDMgrIsBlockDevice(pszLocation))
+ {
+ if (pThis->hHbdMgr == NIL_HBDMGR)
+ rc = HBDMgrCreate(&pThis->hHbdMgr);
+
+ if ( RT_SUCCESS(rc)
+ && !HBDMgrIsBlockDeviceClaimed(pThis->hHbdMgr, pszLocation))
+ rc = HBDMgrClaimBlockDevice(pThis->hHbdMgr, pszLocation);
+
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pStorageBackend = (PDRVVDSTORAGEBACKEND)RTMemAllocZ(sizeof(DRVVDSTORAGEBACKEND));
+ if (pStorageBackend)
+ {
+ pStorageBackend->fSyncIoPending = false;
+ pStorageBackend->rcReqLast = VINF_SUCCESS;
+ pStorageBackend->pfnCompleted = pfnCompleted;
+
+ rc = RTSemEventCreate(&pStorageBackend->EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ rc = PDMDrvHlpAsyncCompletionTemplateCreate(pThis->pDrvIns, &pStorageBackend->pTemplate,
+ drvvdAsyncTaskCompleted, pStorageBackend, "AsyncTaskCompleted");
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFlags = (fOpen & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ
+ ? PDMACEP_FILE_FLAGS_READ_ONLY
+ : 0;
+ if (pThis->fShareable)
+ {
+ Assert((fOpen & RTFILE_O_DENY_MASK) == RTFILE_O_DENY_NONE);
+
+ fFlags |= PDMACEP_FILE_FLAGS_DONT_LOCK;
+ }
+ if (pThis->fAsyncIoWithHostCache)
+ fFlags |= PDMACEP_FILE_FLAGS_HOST_CACHE_ENABLED;
+
+ rc = PDMR3AsyncCompletionEpCreateForFile(&pStorageBackend->pEndpoint,
+ pszLocation, fFlags,
+ pStorageBackend->pTemplate);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pszBwGroup)
+ rc = PDMR3AsyncCompletionEpSetBwMgr(pStorageBackend->pEndpoint, pThis->pszBwGroup);
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("drvvdAsyncIOOpen: Successfully opened '%s'; fOpen=%#x pStorage=%p\n",
+ pszLocation, fOpen, pStorageBackend));
+ *ppStorage = pStorageBackend;
+ return VINF_SUCCESS;
+ }
+
+ PDMR3AsyncCompletionEpClose(pStorageBackend->pEndpoint);
+ }
+
+ PDMR3AsyncCompletionTemplateDestroy(pStorageBackend->pTemplate);
+ }
+ RTSemEventDestroy(pStorageBackend->EventSem);
+ }
+ RTMemFree(pStorageBackend);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOClose(void *pvUser, void *pStorage)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ /*
+ * We don't unclaim any block devices on purpose here because they
+ * might get reopened shortly (switching to readonly during suspend)
+ *
+ * Block devices will get unclaimed during destruction of the driver.
+ */
+
+ PDMR3AsyncCompletionEpClose(pStorageBackend->pEndpoint);
+ PDMR3AsyncCompletionTemplateDestroy(pStorageBackend->pTemplate);
+ RTSemEventDestroy(pStorageBackend->EventSem);
+ RTMemFree(pStorageBackend);
+ return VINF_SUCCESS;;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOReadSync(void *pvUser, void *pStorage, uint64_t uOffset,
+ void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+ RTSGSEG DataSeg;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+ DataSeg.cbSeg = cbRead;
+ DataSeg.pvSeg = pvBuf;
+
+ int rc = PDMR3AsyncCompletionEpRead(pStorageBackend->pEndpoint, uOffset, &DataSeg, 1, cbRead, NULL, &pTask);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (rc == VINF_AIO_TASK_PENDING)
+ {
+ /* Wait */
+ rc = RTSemEventWait(pStorageBackend->EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ else
+ ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, false);
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+
+ return pStorageBackend->rcReqLast;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOWriteSync(void *pvUser, void *pStorage, uint64_t uOffset,
+ const void *pvBuf, size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+ RTSGSEG DataSeg;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+ DataSeg.cbSeg = cbWrite;
+ DataSeg.pvSeg = (void *)pvBuf;
+
+ int rc = PDMR3AsyncCompletionEpWrite(pStorageBackend->pEndpoint, uOffset, &DataSeg, 1, cbWrite, NULL, &pTask);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (rc == VINF_AIO_TASK_PENDING)
+ {
+ /* Wait */
+ rc = RTSemEventWait(pStorageBackend->EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ else
+ ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, false);
+
+ if (pcbWritten)
+ *pcbWritten = cbWrite;
+
+ return pStorageBackend->rcReqLast;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOFlushSync(void *pvUser, void *pStorage)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ LogFlowFunc(("pvUser=%#p pStorage=%#p\n", pvUser, pStorage));
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+
+ int rc = PDMR3AsyncCompletionEpFlush(pStorageBackend->pEndpoint, NULL, &pTask);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (rc == VINF_AIO_TASK_PENDING)
+ {
+ /* Wait */
+ LogFlowFunc(("Waiting for flush to complete\n"));
+ rc = RTSemEventWait(pStorageBackend->EventSem, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ else
+ ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, false);
+
+ return pStorageBackend->rcReqLast;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOReadAsync(void *pvUser, void *pStorage, uint64_t uOffset,
+ PCRTSGSEG paSegments, size_t cSegments,
+ size_t cbRead, void *pvCompletion,
+ void **ppTask)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ int rc = PDMR3AsyncCompletionEpRead(pStorageBackend->pEndpoint, uOffset, paSegments, (unsigned)cSegments, cbRead,
+ pvCompletion, (PPPDMASYNCCOMPLETIONTASK)ppTask);
+ if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOWriteAsync(void *pvUser, void *pStorage, uint64_t uOffset,
+ PCRTSGSEG paSegments, size_t cSegments,
+ size_t cbWrite, void *pvCompletion,
+ void **ppTask)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ int rc = PDMR3AsyncCompletionEpWrite(pStorageBackend->pEndpoint, uOffset, paSegments, (unsigned)cSegments, cbWrite,
+ pvCompletion, (PPPDMASYNCCOMPLETIONTASK)ppTask);
+ if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOFlushAsync(void *pvUser, void *pStorage,
+ void *pvCompletion, void **ppTask)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ int rc = PDMR3AsyncCompletionEpFlush(pStorageBackend->pEndpoint, pvCompletion,
+ (PPPDMASYNCCOMPLETIONTASK)ppTask);
+ if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOGetSize(void *pvUser, void *pStorage, uint64_t *pcbSize)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ return PDMR3AsyncCompletionEpGetSize(pStorageBackend->pEndpoint, pcbSize);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+
+ return PDMR3AsyncCompletionEpSetSize(pStorageBackend->pEndpoint, cbSize);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOSetAllocationSize(void *pvUser, void *pvStorage, uint64_t cbSize, uint32_t fFlags)
+{
+ RT_NOREF(pvUser, pvStorage, cbSize, fFlags);
+ return VERR_NOT_SUPPORTED;
+}
+
+#endif /* VBOX_WITH_PDM_ASYNC_COMPLETION */
+
+
+/*********************************************************************************************************************************
+* VD Thread Synchronization interface implementation *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) drvvdThreadStartRead(void *pvUser)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+
+ return RTSemRWRequestRead(pThis->MergeLock, RT_INDEFINITE_WAIT);
+}
+
+static DECLCALLBACK(int) drvvdThreadFinishRead(void *pvUser)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+
+ return RTSemRWReleaseRead(pThis->MergeLock);
+}
+
+static DECLCALLBACK(int) drvvdThreadStartWrite(void *pvUser)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+
+ return RTSemRWRequestWrite(pThis->MergeLock, RT_INDEFINITE_WAIT);
+}
+
+static DECLCALLBACK(int) drvvdThreadFinishWrite(void *pvUser)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+
+ return RTSemRWReleaseWrite(pThis->MergeLock);
+}
+
+
+/*********************************************************************************************************************************
+* VD Configuration interface implementation *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(bool) drvvdCfgAreKeysValid(void *pvUser, const char *pszzValid)
+{
+ return CFGMR3AreValuesValid((PCFGMNODE)pvUser, pszzValid);
+}
+
+static DECLCALLBACK(int) drvvdCfgQuerySize(void *pvUser, const char *pszName, size_t *pcb)
+{
+ return CFGMR3QuerySize((PCFGMNODE)pvUser, pszName, pcb);
+}
+
+static DECLCALLBACK(int) drvvdCfgQuery(void *pvUser, const char *pszName, char *pszString, size_t cchString)
+{
+ return CFGMR3QueryString((PCFGMNODE)pvUser, pszName, pszString, cchString);
+}
+
+static DECLCALLBACK(int) drvvdCfgQueryBytes(void *pvUser, const char *pszName, void *ppvData, size_t cbData)
+{
+ return CFGMR3QueryBytes((PCFGMNODE)pvUser, pszName, ppvData, cbData);
+}
+
+
+/*******************************************************************************
+* VD Crypto interface implementation for the encryption support *
+*******************************************************************************/
+
+static DECLCALLBACK(int) drvvdCryptoKeyRetain(void *pvUser, const char *pszId, const uint8_t **ppbKey, size_t *pcbKey)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pIfSecKey);
+ if (pThis->pIfSecKey)
+ rc = pThis->pIfSecKey->pfnKeyRetain(pThis->pIfSecKey, pszId, ppbKey, pcbKey);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdCryptoKeyRelease(void *pvUser, const char *pszId)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pIfSecKey);
+ if (pThis->pIfSecKey)
+ rc = pThis->pIfSecKey->pfnKeyRelease(pThis->pIfSecKey, pszId);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pIfSecKey);
+ if (pThis->pIfSecKey)
+ rc = pThis->pIfSecKey->pfnPasswordRetain(pThis->pIfSecKey, pszId, ppszPassword);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvvdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser;
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pIfSecKey);
+ if (pThis->pIfSecKey)
+ rc = pThis->pIfSecKey->pfnPasswordRelease(pThis->pIfSecKey, pszId);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+#ifdef VBOX_WITH_INIP
+
+
+/*********************************************************************************************************************************
+* VD TCP network stack interface implementation - INIP case *
+*********************************************************************************************************************************/
+
+/**
+ * vvl: this structure duplicate meaning of sockaddr,
+ * perhaps it'd be better to get rid of it.
+ */
+typedef union INIPSOCKADDRUNION
+{
+ struct sockaddr Addr;
+ struct sockaddr_in Ipv4;
+#if LWIP_IPV6
+ struct sockaddr_in6 Ipv6;
+#endif
+} INIPSOCKADDRUNION;
+
+typedef struct INIPSOCKET
+{
+ int hSock;
+} INIPSOCKET, *PINIPSOCKET;
+
+static DECLCALLBACK(int) drvvdINIPFlush(VDSOCKET Sock);
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketCreate} */
+static DECLCALLBACK(int) drvvdINIPSocketCreate(uint32_t fFlags, PVDSOCKET pSock)
+{
+ PINIPSOCKET pSocketInt = NULL;
+
+ /*
+ * The extended select method is not supported because it is impossible to wakeup
+ * the thread.
+ */
+ if (fFlags & VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT)
+ return VERR_NOT_SUPPORTED;
+
+ pSocketInt = (PINIPSOCKET)RTMemAllocZ(sizeof(INIPSOCKET));
+ if (pSocketInt)
+ {
+ pSocketInt->hSock = INT32_MAX;
+ *pSock = (VDSOCKET)pSocketInt;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NO_MEMORY;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketCreate} */
+static DECLCALLBACK(int) drvvdINIPSocketDestroy(VDSOCKET Sock)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ RTMemFree(pSocketInt);
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientConnect} */
+static DECLCALLBACK(int) drvvdINIPClientConnect(VDSOCKET Sock, const char *pszAddress, uint32_t uPort,
+ RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+ int iInetFamily = PF_INET;
+ struct in_addr ip;
+#if LWIP_IPV6
+ ip6_addr_t ip6;
+ RT_ZERO(ip6);
+#endif
+
+ NOREF(cMillies); /* LwIP doesn't support connect timeout. */
+ RT_ZERO(ip); /* Shut up MSC. */
+
+ /* Check whether lwIP is set up in this VM instance. */
+ if (!DevINIPConfigured())
+ {
+ LogRelFunc(("no IP stack\n"));
+ return VERR_NET_HOST_UNREACHABLE;
+ }
+ /* Resolve hostname. As there is no standard resolver for lwIP yet,
+ * just accept numeric IP addresses for now. */
+#if LWIP_IPV6
+ if (inet6_aton(pszAddress, &ip6))
+ iInetFamily = PF_INET6;
+ else /* concatination with if */
+#endif
+ if (!lwip_inet_aton(pszAddress, &ip))
+ {
+ LogRelFunc(("cannot resolve IP %s\n", pszAddress));
+ return VERR_NET_HOST_UNREACHABLE;
+ }
+ /* Create socket and connect. */
+ int iSock = lwip_socket(iInetFamily, SOCK_STREAM, 0);
+ if (iSock != -1)
+ {
+ struct sockaddr *pSockAddr = NULL;
+ struct sockaddr_in InAddr = {0};
+#if LWIP_IPV6
+ struct sockaddr_in6 In6Addr = {0};
+#endif
+ if (iInetFamily == PF_INET)
+ {
+ InAddr.sin_family = AF_INET;
+ InAddr.sin_port = htons(uPort);
+ InAddr.sin_addr = ip;
+ InAddr.sin_len = sizeof(InAddr);
+ pSockAddr = (struct sockaddr *)&InAddr;
+ }
+#if LWIP_IPV6
+ else
+ {
+ In6Addr.sin6_family = AF_INET6;
+ In6Addr.sin6_port = htons(uPort);
+ memcpy(&In6Addr.sin6_addr, &ip6, sizeof(ip6));
+ In6Addr.sin6_len = sizeof(In6Addr);
+ pSockAddr = (struct sockaddr *)&In6Addr;
+ }
+#endif
+ if ( pSockAddr
+ && !lwip_connect(iSock, pSockAddr, pSockAddr->sa_len))
+ {
+ pSocketInt->hSock = iSock;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+ lwip_close(iSock);
+ }
+ else
+ rc = VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientClose} */
+static DECLCALLBACK(int) drvvdINIPClientClose(VDSOCKET Sock)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ lwip_close(pSocketInt->hSock);
+ pSocketInt->hSock = INT32_MAX;
+ return VINF_SUCCESS; /** @todo real solution needed */
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnIsClientConnected} */
+static DECLCALLBACK(bool) drvvdINIPIsClientConnected(VDSOCKET Sock)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ return pSocketInt->hSock != INT32_MAX;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOne} */
+static DECLCALLBACK(int) drvvdINIPSelectOne(VDSOCKET Sock, RTMSINTERVAL cMillies)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+ fd_set fdsetR;
+ FD_ZERO(&fdsetR);
+ FD_SET((uintptr_t)pSocketInt->hSock, &fdsetR);
+ fd_set fdsetE = fdsetR;
+
+ int rc;
+ if (cMillies == RT_INDEFINITE_WAIT)
+ rc = lwip_select(pSocketInt->hSock + 1, &fdsetR, NULL, &fdsetE, NULL);
+ else
+ {
+ struct timeval timeout;
+ timeout.tv_sec = cMillies / 1000;
+ timeout.tv_usec = (cMillies % 1000) * 1000;
+ rc = lwip_select(pSocketInt->hSock + 1, &fdsetR, NULL, &fdsetE, &timeout);
+ }
+ if (rc > 0)
+ return VINF_SUCCESS;
+ if (rc == 0)
+ return VERR_TIMEOUT;
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnRead} */
+static DECLCALLBACK(int) drvvdINIPRead(VDSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ /* Do params checking */
+ if (!pvBuffer || !cbBuffer)
+ {
+ AssertMsgFailed(("Invalid params\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Read loop.
+ * If pcbRead is NULL we have to fill the entire buffer!
+ */
+ size_t cbRead = 0;
+ size_t cbToRead = cbBuffer;
+ for (;;)
+ {
+ /** @todo this clipping here is just in case (the send function
+ * needed it, so I added it here, too). Didn't investigate if this
+ * really has issues. Better be safe than sorry. */
+ ssize_t cbBytesRead = lwip_recv(pSocketInt->hSock, (char *)pvBuffer + cbRead,
+ RT_MIN(cbToRead, 32768), 0);
+ if (cbBytesRead < 0)
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution */
+ if (cbBytesRead == 0 && errno) /** @todo r=bird: lwip_recv will not touch errno on Windows. This may apply to other hosts as well */
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution */
+ if (pcbRead)
+ {
+ /* return partial data */
+ *pcbRead = cbBytesRead;
+ break;
+ }
+
+ /* read more? */
+ cbRead += cbBytesRead;
+ if (cbRead == cbBuffer)
+ break;
+
+ /* next */
+ cbToRead = cbBuffer - cbRead;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnWrite} */
+static DECLCALLBACK(int) drvvdINIPWrite(VDSOCKET Sock, const void *pvBuffer, size_t cbBuffer)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ do
+ {
+ /** @todo lwip send only supports up to 65535 bytes in a single
+ * send (stupid limitation buried in the code), so make sure we
+ * don't get any wraparounds. This should be moved to DevINIP
+ * stack interface once that's implemented. */
+ ssize_t cbWritten = lwip_send(pSocketInt->hSock, (void *)pvBuffer,
+ RT_MIN(cbBuffer, 32768), 0);
+ if (cbWritten < 0)
+ return VERR_NET_CONNECTION_REFUSED; /** @todo real solution needed */
+ AssertMsg(cbBuffer >= (size_t)cbWritten, ("Wrote more than we requested!!! cbWritten=%d cbBuffer=%d\n",
+ cbWritten, cbBuffer));
+ cbBuffer -= cbWritten;
+ pvBuffer = (const char *)pvBuffer + cbWritten;
+ } while (cbBuffer);
+
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSgWrite} */
+static DECLCALLBACK(int) drvvdINIPSgWrite(VDSOCKET Sock, PCRTSGBUF pSgBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ /* This is an extremely crude emulation, however it's good enough
+ * for our iSCSI code. INIP has no sendmsg(). */
+ for (unsigned i = 0; i < pSgBuf->cSegs; i++)
+ {
+ rc = drvvdINIPWrite(Sock, pSgBuf->paSegs[i].pvSeg,
+ pSgBuf->paSegs[i].cbSeg);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ drvvdINIPFlush(Sock);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnFlush} */
+static DECLCALLBACK(int) drvvdINIPFlush(VDSOCKET Sock)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ int fFlag = 1;
+ lwip_setsockopt(pSocketInt->hSock, IPPROTO_TCP, TCP_NODELAY,
+ (const char *)&fFlag, sizeof(fFlag));
+ fFlag = 0;
+ lwip_setsockopt(pSocketInt->hSock, IPPROTO_TCP, TCP_NODELAY,
+ (const char *)&fFlag, sizeof(fFlag));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSetSendCoalescing} */
+static DECLCALLBACK(int) drvvdINIPSetSendCoalescing(VDSOCKET Sock, bool fEnable)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+
+ int fFlag = fEnable ? 0 : 1;
+ lwip_setsockopt(pSocketInt->hSock, IPPROTO_TCP, TCP_NODELAY,
+ (const char *)&fFlag, sizeof(fFlag));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetLocalAddress} */
+static DECLCALLBACK(int) drvvdINIPGetLocalAddress(VDSOCKET Sock, PRTNETADDR pAddr)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+ INIPSOCKADDRUNION u;
+ socklen_t cbAddr = sizeof(u);
+ RT_ZERO(u);
+ if (!lwip_getsockname(pSocketInt->hSock, &u.Addr, &cbAddr))
+ {
+ /*
+ * Convert the address.
+ */
+ if ( cbAddr == sizeof(struct sockaddr_in)
+ && u.Addr.sa_family == AF_INET)
+ {
+ RT_ZERO(*pAddr);
+ pAddr->enmType = RTNETADDRTYPE_IPV4;
+ pAddr->uPort = RT_N2H_U16(u.Ipv4.sin_port);
+ pAddr->uAddr.IPv4.u = u.Ipv4.sin_addr.s_addr;
+ }
+#if LWIP_IPV6
+ else if ( cbAddr == sizeof(struct sockaddr_in6)
+ && u.Addr.sa_family == AF_INET6)
+ {
+ RT_ZERO(*pAddr);
+ pAddr->enmType = RTNETADDRTYPE_IPV6;
+ pAddr->uPort = RT_N2H_U16(u.Ipv6.sin6_port);
+ memcpy(&pAddr->uAddr.IPv6, &u.Ipv6.sin6_addr, sizeof(RTNETADDRIPV6));
+ }
+#endif
+ else
+ return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED;
+ return VINF_SUCCESS;
+ }
+ return VERR_NET_OPERATION_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetPeerAddress} */
+static DECLCALLBACK(int) drvvdINIPGetPeerAddress(VDSOCKET Sock, PRTNETADDR pAddr)
+{
+ PINIPSOCKET pSocketInt = (PINIPSOCKET)Sock;
+ INIPSOCKADDRUNION u;
+ socklen_t cbAddr = sizeof(u);
+ RT_ZERO(u);
+ if (!lwip_getpeername(pSocketInt->hSock, &u.Addr, &cbAddr))
+ {
+ /*
+ * Convert the address.
+ */
+ if ( cbAddr == sizeof(struct sockaddr_in)
+ && u.Addr.sa_family == AF_INET)
+ {
+ RT_ZERO(*pAddr);
+ pAddr->enmType = RTNETADDRTYPE_IPV4;
+ pAddr->uPort = RT_N2H_U16(u.Ipv4.sin_port);
+ pAddr->uAddr.IPv4.u = u.Ipv4.sin_addr.s_addr;
+ }
+#if LWIP_IPV6
+ else if ( cbAddr == sizeof(struct sockaddr_in6)
+ && u.Addr.sa_family == AF_INET6)
+ {
+ RT_ZERO(*pAddr);
+ pAddr->enmType = RTNETADDRTYPE_IPV6;
+ pAddr->uPort = RT_N2H_U16(u.Ipv6.sin6_port);
+ memcpy(&pAddr->uAddr.IPv6, &u.Ipv6.sin6_addr, sizeof(RTNETADDRIPV6));
+ }
+#endif
+ else
+ return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED;
+ return VINF_SUCCESS;
+ }
+ return VERR_NET_OPERATION_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOneEx} */
+static DECLCALLBACK(int) drvvdINIPSelectOneEx(VDSOCKET Sock, uint32_t fEvents, uint32_t *pfEvents, RTMSINTERVAL cMillies)
+{
+ RT_NOREF(Sock, fEvents, pfEvents, cMillies);
+ AssertMsgFailed(("Not supported!\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnPoke} */
+static DECLCALLBACK(int) drvvdINIPPoke(VDSOCKET Sock)
+{
+ RT_NOREF(Sock);
+ AssertMsgFailed(("Not supported!\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+#endif /* VBOX_WITH_INIP */
+
+
+/*********************************************************************************************************************************
+* VD TCP network stack interface implementation - Host TCP case *
+*********************************************************************************************************************************/
+
+/**
+ * Socket data.
+ */
+typedef struct VDSOCKETINT
+{
+ /** IPRT socket handle. */
+ RTSOCKET hSocket;
+ /** Pollset with the wakeup pipe and socket. */
+ RTPOLLSET hPollSet;
+ /** Pipe endpoint - read (in the pollset). */
+ RTPIPE hPipeR;
+ /** Pipe endpoint - write. */
+ RTPIPE hPipeW;
+ /** Flag whether the thread was woken up. */
+ volatile bool fWokenUp;
+ /** Flag whether the thread is waiting in the select call. */
+ volatile bool fWaiting;
+ /** Old event mask. */
+ uint32_t fEventsOld;
+} VDSOCKETINT, *PVDSOCKETINT;
+
+/** Pollset id of the socket. */
+#define VDSOCKET_POLL_ID_SOCKET 0
+/** Pollset id of the pipe. */
+#define VDSOCKET_POLL_ID_PIPE 1
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketCreate} */
+static DECLCALLBACK(int) drvvdTcpSocketCreate(uint32_t fFlags, PVDSOCKET phVdSock)
+{
+ int rc = VINF_SUCCESS;
+ int rc2 = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = NULL;
+
+ pSockInt = (PVDSOCKETINT)RTMemAllocZ(sizeof(VDSOCKETINT));
+ if (!pSockInt)
+ return VERR_NO_MEMORY;
+
+ pSockInt->hSocket = NIL_RTSOCKET;
+ pSockInt->hPollSet = NIL_RTPOLLSET;
+ pSockInt->hPipeR = NIL_RTPIPE;
+ pSockInt->hPipeW = NIL_RTPIPE;
+ pSockInt->fWokenUp = false;
+ pSockInt->fWaiting = false;
+
+ if (fFlags & VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT)
+ {
+ /* Init pipe and pollset. */
+ rc = RTPipeCreate(&pSockInt->hPipeR, &pSockInt->hPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&pSockInt->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetAddPipe(pSockInt->hPollSet, pSockInt->hPipeR,
+ RTPOLL_EVT_READ, VDSOCKET_POLL_ID_PIPE);
+ if (RT_SUCCESS(rc))
+ {
+ *phVdSock = pSockInt;
+ return VINF_SUCCESS;
+ }
+
+ RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_PIPE);
+ rc2 = RTPollSetDestroy(pSockInt->hPollSet);
+ AssertRC(rc2);
+ }
+
+ rc2 = RTPipeClose(pSockInt->hPipeR);
+ AssertRC(rc2);
+ rc2 = RTPipeClose(pSockInt->hPipeW);
+ AssertRC(rc2);
+ }
+ }
+ else
+ {
+ *phVdSock = pSockInt;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pSockInt);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSocketDestroy} */
+static DECLCALLBACK(int) drvvdTcpSocketDestroy(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ /* Destroy the pipe and pollset if necessary. */
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ if (pSockInt->hSocket != NIL_RTSOCKET)
+ {
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+ }
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_PIPE);
+ AssertRC(rc);
+ rc = RTPollSetDestroy(pSockInt->hPollSet);
+ AssertRC(rc);
+ rc = RTPipeClose(pSockInt->hPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(pSockInt->hPipeW);
+ AssertRC(rc);
+ }
+
+ if (pSockInt->hSocket != NIL_RTSOCKET)
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+
+ RTMemFree(pSockInt);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientConnect} */
+static DECLCALLBACK(int) drvvdTcpClientConnect(VDSOCKET hVdSock, const char *pszAddress, uint32_t uPort,
+ RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ rc = RTTcpClientConnectEx(pszAddress, uPort, &pSockInt->hSocket, cMillies, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add to the pollset if required. */
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ pSockInt->fEventsOld = RTPOLL_EVT_READ | RTPOLL_EVT_WRITE | RTPOLL_EVT_ERROR;
+
+ rc = RTPollSetAddSocket(pSockInt->hPollSet, pSockInt->hSocket,
+ pSockInt->fEventsOld, VDSOCKET_POLL_ID_SOCKET);
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnClientClose} */
+static DECLCALLBACK(int) drvvdTcpClientClose(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ if (pSockInt->hPollSet != NIL_RTPOLLSET)
+ {
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ AssertRC(rc);
+ }
+
+ rc = RTTcpClientCloseEx(pSockInt->hSocket, false /*fGracefulShutdown*/);
+ pSockInt->hSocket = NIL_RTSOCKET;
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnIsClientConnected} */
+static DECLCALLBACK(bool) drvvdTcpIsClientConnected(VDSOCKET hVdSock)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return pSockInt->hSocket != NIL_RTSOCKET;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOne} */
+static DECLCALLBACK(int) drvvdTcpSelectOne(VDSOCKET hVdSock, RTMSINTERVAL cMillies)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSelectOne(pSockInt->hSocket, cMillies);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnRead} */
+static DECLCALLBACK(int) drvvdTcpRead(VDSOCKET hVdSock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpRead(pSockInt->hSocket, pvBuffer, cbBuffer, pcbRead);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnWrite} */
+static DECLCALLBACK(int) drvvdTcpWrite(VDSOCKET hVdSock, const void *pvBuffer, size_t cbBuffer)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpWrite(pSockInt->hSocket, pvBuffer, cbBuffer);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSgWrite} */
+static DECLCALLBACK(int) drvvdTcpSgWrite(VDSOCKET hVdSock, PCRTSGBUF pSgBuf)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSgWrite(pSockInt->hSocket, pSgBuf);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnReadNB} */
+static DECLCALLBACK(int) drvvdTcpReadNB(VDSOCKET hVdSock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpReadNB(pSockInt->hSocket, pvBuffer, cbBuffer, pcbRead);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnWriteNB} */
+static DECLCALLBACK(int) drvvdTcpWriteNB(VDSOCKET hVdSock, const void *pvBuffer, size_t cbBuffer, size_t *pcbWritten)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpWriteNB(pSockInt->hSocket, pvBuffer, cbBuffer, pcbWritten);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSgWriteNB} */
+static DECLCALLBACK(int) drvvdTcpSgWriteNB(VDSOCKET hVdSock, PRTSGBUF pSgBuf, size_t *pcbWritten)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSgWriteNB(pSockInt->hSocket, pSgBuf, pcbWritten);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnFlush} */
+static DECLCALLBACK(int) drvvdTcpFlush(VDSOCKET hVdSock)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpFlush(pSockInt->hSocket);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSetSendCoalescing} */
+static DECLCALLBACK(int) drvvdTcpSetSendCoalescing(VDSOCKET hVdSock, bool fEnable)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpSetSendCoalescing(pSockInt->hSocket, fEnable);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetLocalAddress} */
+static DECLCALLBACK(int) drvvdTcpGetLocalAddress(VDSOCKET hVdSock, PRTNETADDR pAddr)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpGetLocalAddress(pSockInt->hSocket, pAddr);
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnGetPeerAddress} */
+static DECLCALLBACK(int) drvvdTcpGetPeerAddress(VDSOCKET hVdSock, PRTNETADDR pAddr)
+{
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ return RTTcpGetPeerAddress(pSockInt->hSocket, pAddr);
+}
+
+static DECLCALLBACK(int) drvvdTcpSelectOneExPoll(VDSOCKET hVdSock, uint32_t fEvents,
+ uint32_t *pfEvents, RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t id = 0;
+ uint32_t fEventsRecv = 0;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ *pfEvents = 0;
+
+ if ( pSockInt->fEventsOld != fEvents
+ && pSockInt->hSocket != NIL_RTSOCKET)
+ {
+ uint32_t fPollEvents = 0;
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_READ)
+ fPollEvents |= RTPOLL_EVT_READ;
+ if (fEvents & VD_INTERFACETCPNET_EVT_WRITE)
+ fPollEvents |= RTPOLL_EVT_WRITE;
+ if (fEvents & VD_INTERFACETCPNET_EVT_ERROR)
+ fPollEvents |= RTPOLL_EVT_ERROR;
+
+ rc = RTPollSetEventsChange(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET, fPollEvents);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pSockInt->fEventsOld = fEvents;
+ }
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, true);
+ if (ASMAtomicXchgBool(&pSockInt->fWokenUp, false))
+ {
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+ return VERR_INTERRUPTED;
+ }
+
+ rc = RTPoll(pSockInt->hPollSet, cMillies, &fEventsRecv, &id);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (id == VDSOCKET_POLL_ID_SOCKET)
+ {
+ fEventsRecv &= RTPOLL_EVT_VALID_MASK;
+
+ if (fEventsRecv & RTPOLL_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTPOLL_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTPOLL_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ }
+ else
+ {
+ size_t cbRead = 0;
+ uint8_t abBuf[10];
+ Assert(id == VDSOCKET_POLL_ID_PIPE);
+ Assert((fEventsRecv & RTPOLL_EVT_VALID_MASK) == RTPOLL_EVT_READ);
+
+ /* We got interrupted, drain the pipe. */
+ rc = RTPipeRead(pSockInt->hPipeR, abBuf, sizeof(abBuf), &cbRead);
+ AssertRC(rc);
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+
+ rc = VERR_INTERRUPTED;
+ }
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnSelectOneEx} */
+static DECLCALLBACK(int) drvvdTcpSelectOneExNoPoll(VDSOCKET hVdSock, uint32_t fEvents, uint32_t *pfEvents, RTMSINTERVAL cMillies)
+{
+ RT_NOREF(cMillies); /** @todo timeouts */
+ int rc = VINF_SUCCESS;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ *pfEvents = 0;
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, true);
+ if (ASMAtomicXchgBool(&pSockInt->fWokenUp, false))
+ {
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+ return VERR_INTERRUPTED;
+ }
+
+ if ( pSockInt->hSocket == NIL_RTSOCKET
+ || !fEvents)
+ {
+ /*
+ * Only the pipe is configured or the caller doesn't wait for a socket event,
+ * wait until there is something to read from the pipe.
+ */
+ size_t cbRead = 0;
+ char ch = 0;
+ rc = RTPipeReadBlocking(pSockInt->hPipeR, &ch, 1, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbRead == 1);
+ rc = VERR_INTERRUPTED;
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+ }
+ }
+ else
+ {
+ uint32_t fSelectEvents = 0;
+
+ if (fEvents & VD_INTERFACETCPNET_EVT_READ)
+ fSelectEvents |= RTSOCKET_EVT_READ;
+ if (fEvents & VD_INTERFACETCPNET_EVT_WRITE)
+ fSelectEvents |= RTSOCKET_EVT_WRITE;
+ if (fEvents & VD_INTERFACETCPNET_EVT_ERROR)
+ fSelectEvents |= RTSOCKET_EVT_ERROR;
+
+ if (fEvents & VD_INTERFACETCPNET_HINT_INTERRUPT)
+ {
+ uint32_t fEventsRecv = 0;
+
+ /* Make sure the socket is not in the pollset. */
+ rc = RTPollSetRemove(pSockInt->hPollSet, VDSOCKET_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+
+ for (;;)
+ {
+ uint32_t id = 0;
+ rc = RTPoll(pSockInt->hPollSet, 5, &fEvents, &id);
+ if (rc == VERR_TIMEOUT)
+ {
+ /* Check the socket. */
+ rc = RTTcpSelectOneEx(pSockInt->hSocket, fSelectEvents, &fEventsRecv, 0);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEventsRecv & RTSOCKET_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTSOCKET_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTSOCKET_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ break; /* Quit */
+ }
+ else if (rc != VERR_TIMEOUT)
+ break;
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ size_t cbRead = 0;
+ uint8_t abBuf[10];
+ Assert(id == VDSOCKET_POLL_ID_PIPE);
+ Assert((fEventsRecv & RTPOLL_EVT_VALID_MASK) == RTPOLL_EVT_READ);
+
+ /* We got interrupted, drain the pipe. */
+ rc = RTPipeRead(pSockInt->hPipeR, abBuf, sizeof(abBuf), &cbRead);
+ AssertRC(rc);
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+
+ rc = VERR_INTERRUPTED;
+ break;
+ }
+ else
+ break;
+ }
+ }
+ else /* The caller waits for a socket event. */
+ {
+ uint32_t fEventsRecv = 0;
+
+ /* Loop until we got woken up or a socket event occurred. */
+ for (;;)
+ {
+ /** @todo find an adaptive wait algorithm based on the
+ * number of wakeups in the past. */
+ rc = RTTcpSelectOneEx(pSockInt->hSocket, fSelectEvents, &fEventsRecv, 5);
+ if (rc == VERR_TIMEOUT)
+ {
+ /* Check if there is an event pending. */
+ size_t cbRead = 0;
+ char ch = 0;
+ rc = RTPipeRead(pSockInt->hPipeR, &ch, 1, &cbRead);
+ if (RT_SUCCESS(rc) && rc != VINF_TRY_AGAIN)
+ {
+ Assert(cbRead == 1);
+ rc = VERR_INTERRUPTED;
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, false);
+ break; /* Quit */
+ }
+ else
+ Assert(rc == VINF_TRY_AGAIN);
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ if (fEventsRecv & RTSOCKET_EVT_READ)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_READ;
+ if (fEventsRecv & RTSOCKET_EVT_WRITE)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_WRITE;
+ if (fEventsRecv & RTSOCKET_EVT_ERROR)
+ *pfEvents |= VD_INTERFACETCPNET_EVT_ERROR;
+ break; /* Quit */
+ }
+ else
+ break;
+ }
+ }
+ }
+
+ ASMAtomicXchgBool(&pSockInt->fWaiting, false);
+
+ return rc;
+}
+
+/** @interface_method_impl{VDINTERFACETCPNET,pfnPoke} */
+static DECLCALLBACK(int) drvvdTcpPoke(VDSOCKET hVdSock)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+ PVDSOCKETINT pSockInt = (PVDSOCKETINT)hVdSock;
+
+ ASMAtomicXchgBool(&pSockInt->fWokenUp, true);
+
+ if (ASMAtomicReadBool(&pSockInt->fWaiting))
+ {
+ rc = RTPipeWrite(pSockInt->hPipeW, "", 1, &cbWritten);
+ Assert(RT_SUCCESS(rc) || cbWritten == 0);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Checks the prerequisites for encrypted I/O.
+ *
+ * @returns VBox status code.
+ * @param pThis The VD driver instance data.
+ * @param fSetError Flag whether to set a runtime error.
+ */
+static int drvvdKeyCheckPrereqs(PVBOXDISK pThis, bool fSetError)
+{
+ if ( pThis->pCfgCrypto
+ && !pThis->pIfSecKey)
+ {
+ AssertPtr(pThis->pIfSecKeyHlp);
+ pThis->pIfSecKeyHlp->pfnKeyMissingNotify(pThis->pIfSecKeyHlp);
+
+ if (fSetError)
+ {
+ int rc = PDMDrvHlpVMSetRuntimeError(pThis->pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_DEKMISSING",
+ N_("VD: The DEK for this disk is missing"));
+ AssertRC(rc);
+ }
+ return VERR_VD_DEK_MISSING;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Media interface methods *
+*********************************************************************************************************************************/
+
+/** @interface_method_impl{PDMIMEDIA,pfnRead} */
+static DECLCALLBACK(int) drvvdRead(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("off=%#llx pvBuf=%p cbRead=%d\n", off, pvBuf, cbRead));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ rc = drvvdKeyCheckPrereqs(pThis, true /* fSetError */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsRead);
+
+ if (!pThis->fBootAccelActive)
+ rc = VDRead(pThis->pDisk, off, pvBuf, cbRead);
+ else
+ {
+ /* Can we serve the request from the buffer? */
+ if ( off >= pThis->offDisk
+ && off - pThis->offDisk < pThis->cbDataValid)
+ {
+ size_t cbToCopy = RT_MIN(cbRead, pThis->offDisk + pThis->cbDataValid - off);
+
+ memcpy(pvBuf, pThis->pbData + (off - pThis->offDisk), cbToCopy);
+ cbRead -= cbToCopy;
+ off += cbToCopy;
+ pvBuf = (char *)pvBuf + cbToCopy;
+ }
+
+ if ( cbRead > 0
+ && cbRead < pThis->cbBootAccelBuffer)
+ {
+ /* Increase request to the buffer size and read. */
+ pThis->cbDataValid = RT_MIN(pThis->cbDisk - off, pThis->cbBootAccelBuffer);
+ pThis->offDisk = off;
+ rc = VDRead(pThis->pDisk, off, pThis->pbData, pThis->cbDataValid);
+ if (RT_FAILURE(rc))
+ pThis->cbDataValid = 0;
+ else
+ memcpy(pvBuf, pThis->pbData, cbRead);
+ }
+ else if (cbRead >= pThis->cbBootAccelBuffer)
+ {
+ pThis->fBootAccelActive = false; /* Deactiviate */
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, cbRead);
+ Log2(("%s: off=%#llx pvBuf=%p cbRead=%d\n%.*Rhxd\n", __FUNCTION__,
+ off, pvBuf, cbRead, cbRead, pvBuf));
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnRead} */
+static DECLCALLBACK(int) drvvdReadPcBios(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("off=%#llx pvBuf=%p cbRead=%d\n", off, pvBuf, cbRead));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ if ( pThis->pCfgCrypto
+ && !pThis->pIfSecKey)
+ return VERR_VD_DEK_MISSING;
+
+ if (!pThis->fBootAccelActive)
+ rc = VDRead(pThis->pDisk, off, pvBuf, cbRead);
+ else
+ {
+ /* Can we serve the request from the buffer? */
+ if ( off >= pThis->offDisk
+ && off - pThis->offDisk < pThis->cbDataValid)
+ {
+ size_t cbToCopy = RT_MIN(cbRead, pThis->offDisk + pThis->cbDataValid - off);
+
+ memcpy(pvBuf, pThis->pbData + (off - pThis->offDisk), cbToCopy);
+ cbRead -= cbToCopy;
+ off += cbToCopy;
+ pvBuf = (char *)pvBuf + cbToCopy;
+ }
+
+ if ( cbRead > 0
+ && cbRead < pThis->cbBootAccelBuffer)
+ {
+ /* Increase request to the buffer size and read. */
+ pThis->cbDataValid = RT_MIN(pThis->cbDisk - off, pThis->cbBootAccelBuffer);
+ pThis->offDisk = off;
+ rc = VDRead(pThis->pDisk, off, pThis->pbData, pThis->cbDataValid);
+ if (RT_FAILURE(rc))
+ pThis->cbDataValid = 0;
+ else
+ memcpy(pvBuf, pThis->pbData, cbRead);
+ }
+ else if (cbRead >= pThis->cbBootAccelBuffer)
+ {
+ pThis->fBootAccelActive = false; /* Deactiviate */
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ Log2(("%s: off=%#llx pvBuf=%p cbRead=%d\n%.*Rhxd\n", __FUNCTION__,
+ off, pvBuf, cbRead, cbRead, pvBuf));
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMIMEDIA,pfnWrite} */
+static DECLCALLBACK(int) drvvdWrite(PPDMIMEDIA pInterface,
+ uint64_t off, const void *pvBuf,
+ size_t cbWrite)
+{
+ LogFlowFunc(("off=%#llx pvBuf=%p cbWrite=%d\n", off, pvBuf, cbWrite));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ Log2(("%s: off=%#llx pvBuf=%p cbWrite=%d\n%.*Rhxd\n", __FUNCTION__,
+ off, pvBuf, cbWrite, cbWrite, pvBuf));
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ /* Set an FTM checkpoint as this operation changes the state permanently. */
+ PDMDrvHlpFTSetCheckpoint(pThis->pDrvIns, FTMCHECKPOINTTYPE_STORAGE);
+
+ int rc = drvvdKeyCheckPrereqs(pThis, true /* fSetError */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Invalidate any buffer if boot acceleration is enabled. */
+ if (pThis->fBootAccelActive)
+ {
+ pThis->cbDataValid = 0;
+ pThis->offDisk = 0;
+ }
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsWrite);
+
+ rc = VDWrite(pThis->pDisk, off, pvBuf, cbWrite);
+#ifdef VBOX_PERIODIC_FLUSH
+ if (pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten += (uint32_t)cbWrite;
+ if (pThis->cbDataWritten > pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten = 0;
+ VDFlush(pThis->pDisk);
+ }
+ }
+#endif /* VBOX_PERIODIC_FLUSH */
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, cbWrite);
+ }
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnFlush} */
+static DECLCALLBACK(int) drvvdFlush(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+#ifdef VBOX_IGNORE_FLUSH
+ if (pThis->fIgnoreFlush)
+ return VINF_SUCCESS;
+#endif /* VBOX_IGNORE_FLUSH */
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFlush);
+
+ int rc = VDFlush(pThis->pDisk);
+ if (RT_SUCCESS(rc))
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnMerge} */
+static DECLCALLBACK(int) drvvdMerge(PPDMIMEDIA pInterface,
+ PFNSIMPLEPROGRESS pfnProgress,
+ void *pvUser)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ /* Note: There is an unavoidable race between destruction and another
+ * thread invoking this function. This is handled safely and gracefully by
+ * atomically invalidating the lock handle in drvvdDestruct. */
+ int rc2 = RTSemFastMutexRequest(pThis->MergeCompleteMutex);
+ AssertRC(rc2);
+ if (RT_SUCCESS(rc2) && pThis->fMergePending)
+ {
+ /* Take shortcut: PFNSIMPLEPROGRESS is exactly the same type as
+ * PFNVDPROGRESS, so there's no need for a conversion function. */
+ /** @todo maybe introduce a conversion which limits update frequency. */
+ PVDINTERFACE pVDIfsOperation = NULL;
+ VDINTERFACEPROGRESS VDIfProgress;
+ VDIfProgress.pfnProgress = pfnProgress;
+ rc2 = VDInterfaceAdd(&VDIfProgress.Core, "DrvVD_VDIProgress", VDINTERFACETYPE_PROGRESS,
+ pvUser, sizeof(VDINTERFACEPROGRESS), &pVDIfsOperation);
+ AssertRC(rc2);
+ pThis->fMergePending = false;
+ rc = VDMerge(pThis->pDisk, pThis->uMergeSource,
+ pThis->uMergeTarget, pVDIfsOperation);
+ }
+ rc2 = RTSemFastMutexRelease(pThis->MergeCompleteMutex);
+ AssertRC(rc2);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnSetSecKeyIf} */
+static DECLCALLBACK(int) drvvdSetSecKeyIf(PPDMIMEDIA pInterface, PPDMISECKEY pIfSecKey, PPDMISECKEYHLP pIfSecKeyHlp)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ int rc = VINF_SUCCESS;
+
+ if (pThis->pCfgCrypto)
+ {
+ PVDINTERFACE pVDIfFilter = NULL;
+
+ pThis->pIfSecKeyHlp = pIfSecKeyHlp;
+
+ if ( pThis->pIfSecKey
+ && !pIfSecKey)
+ {
+ /* Unload the crypto filter first to make sure it doesn't access the keys anymore. */
+ rc = VDFilterRemove(pThis->pDisk, VD_FILTER_FLAGS_DEFAULT);
+ AssertRC(rc);
+
+ pThis->pIfSecKey = NULL;
+ }
+
+ if ( pIfSecKey
+ && RT_SUCCESS(rc))
+ {
+ pThis->pIfSecKey = pIfSecKey;
+
+ rc = VDInterfaceAdd(&pThis->VDIfCfg.Core, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ pThis->pCfgCrypto, sizeof(VDINTERFACECONFIG), &pVDIfFilter);
+ AssertRC(rc);
+
+ rc = VDInterfaceAdd(&pThis->VDIfCrypto.Core, "DrvVD_Crypto", VDINTERFACETYPE_CRYPTO,
+ pThis, sizeof(VDINTERFACECRYPTO), &pVDIfFilter);
+ AssertRC(rc);
+
+ /* Load the crypt filter plugin. */
+ rc = VDFilterAdd(pThis->pDisk, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pVDIfFilter);
+ if (RT_FAILURE(rc))
+ pThis->pIfSecKey = NULL;
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSize} */
+static DECLCALLBACK(uint64_t) drvvdGetSize(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ return 0;
+
+ uint64_t cb = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ LogFlowFunc(("returns %#llx (%llu)\n", cb, cb));
+ return cb;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetSectorSize} */
+static DECLCALLBACK(uint32_t) drvvdGetSectorSize(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ return 0;
+
+ uint32_t cb = VDGetSectorSize(pThis->pDisk, VD_LAST_IMAGE);
+ LogFlowFunc(("returns %u\n", cb));
+ return cb;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsReadOnly} */
+static DECLCALLBACK(bool) drvvdIsReadOnly(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ return false;
+
+ bool f = VDIsReadOnly(pThis->pDisk);
+ LogFlowFunc(("returns %d\n", f));
+ return f;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvvdIsNonRotational(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ return pThis->fNonRotational;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetPCHSGeometry} */
+static DECLCALLBACK(int) drvvdBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ VDGEOMETRY geo;
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ /*
+ * Use configured/cached values if present.
+ */
+ if ( pThis->PCHSGeometry.cCylinders > 0
+ && pThis->PCHSGeometry.cHeads > 0
+ && pThis->PCHSGeometry.cSectors > 0)
+ {
+ *pPCHSGeometry = pThis->PCHSGeometry;
+ LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
+ return VINF_SUCCESS;
+ }
+
+ int rc = VDGetPCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, &geo);
+ if (RT_SUCCESS(rc))
+ {
+ pPCHSGeometry->cCylinders = geo.cCylinders;
+ pPCHSGeometry->cHeads = geo.cHeads;
+ pPCHSGeometry->cSectors = geo.cSectors;
+ pThis->PCHSGeometry = *pPCHSGeometry;
+ }
+ else
+ {
+ LogFunc(("geometry not available.\n"));
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ LogFlowFunc(("returns %Rrc (CHS=%d/%d/%d)\n",
+ rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetPCHSGeometry} */
+static DECLCALLBACK(int) drvvdBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ LogFlowFunc(("CHS=%d/%d/%d\n",
+ pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ VDGEOMETRY geo;
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ geo.cCylinders = pPCHSGeometry->cCylinders;
+ geo.cHeads = pPCHSGeometry->cHeads;
+ geo.cSectors = pPCHSGeometry->cSectors;
+ int rc = VDSetPCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, &geo);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ if (RT_SUCCESS(rc))
+ pThis->PCHSGeometry = *pPCHSGeometry;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosGetLCHSGeometry} */
+static DECLCALLBACK(int) drvvdBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ VDGEOMETRY geo;
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+
+ /*
+ * Use configured/cached values if present.
+ */
+ if ( pThis->LCHSGeometry.cCylinders > 0
+ && pThis->LCHSGeometry.cHeads > 0
+ && pThis->LCHSGeometry.cSectors > 0)
+ {
+ *pLCHSGeometry = pThis->LCHSGeometry;
+ LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
+ return VINF_SUCCESS;
+ }
+
+ int rc = VDGetLCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, &geo);
+ if (RT_SUCCESS(rc))
+ {
+ pLCHSGeometry->cCylinders = geo.cCylinders;
+ pLCHSGeometry->cHeads = geo.cHeads;
+ pLCHSGeometry->cSectors = geo.cSectors;
+ pThis->LCHSGeometry = *pLCHSGeometry;
+ }
+ else
+ {
+ LogFunc(("geometry not available.\n"));
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ }
+ LogFlowFunc(("returns %Rrc (CHS=%d/%d/%d)\n",
+ rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosSetLCHSGeometry} */
+static DECLCALLBACK(int) drvvdBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ LogFlowFunc(("CHS=%d/%d/%d\n",
+ pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ VDGEOMETRY geo;
+
+ /*
+ * Check the state.
+ */
+ if (!pThis->pDisk)
+ {
+ AssertMsgFailed(("Invalid state! Not mounted!\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+
+ geo.cCylinders = pLCHSGeometry->cCylinders;
+ geo.cHeads = pLCHSGeometry->cHeads;
+ geo.cSectors = pLCHSGeometry->cSectors;
+ int rc = VDSetLCHSGeometry(pThis->pDisk, VD_LAST_IMAGE, &geo);
+ if (rc == VERR_VD_GEOMETRY_NOT_SET)
+ rc = VERR_PDM_GEOMETRY_NOT_SET;
+ if (RT_SUCCESS(rc))
+ pThis->LCHSGeometry = *pLCHSGeometry;
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvvdBiosIsVisible(PPDMIMEDIA pInterface)
+{
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ LogFlow(("drvvdBiosIsVisible: returns %d\n", pThis->fBiosVisible));
+ return pThis->fBiosVisible;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetType} */
+static DECLCALLBACK(PDMMEDIATYPE) drvvdGetType(PPDMIMEDIA pInterface)
+{
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ LogFlow(("drvvdBiosIsVisible: returns %d\n", pThis->fBiosVisible));
+ return pThis->enmType;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetUuid} */
+static DECLCALLBACK(int) drvvdGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ /*
+ * Copy the uuid.
+ */
+ *pUuid = pThis->Uuid;
+ LogFlowFunc(("returns {%RTuuid}\n", pUuid));
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnDiscard} */
+static DECLCALLBACK(int) drvvdDiscard(PPDMIMEDIA pInterface, PCRTRANGE paRanges, unsigned cRanges)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsDiscard);
+
+ int rc = VDDiscardRanges(pThis->pDisk, paRanges, cRanges);
+ if (RT_SUCCESS(rc))
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+ else
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetRegionCount} */
+static DECLCALLBACK(uint32_t) drvvdGetRegionCount(PPDMIMEDIA pInterface)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+ uint32_t cRegions = 0;
+
+ if (pThis->pDisk)
+ {
+ if (!pThis->pRegionList)
+ {
+ int rc = VDQueryRegions(pThis->pDisk, VD_LAST_IMAGE, VD_REGION_LIST_F_LOC_SIZE_BLOCKS,
+ &pThis->pRegionList);
+ if (RT_SUCCESS(rc))
+ cRegions = pThis->pRegionList->cRegions;
+ }
+ else
+ cRegions = pThis->pRegionList->cRegions;
+ }
+
+ LogFlowFunc(("returns %u\n", cRegions));
+ return cRegions;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionProperties} */
+static DECLCALLBACK(int) drvvdQueryRegionProperties(PPDMIMEDIA pInterface, uint32_t uRegion, uint64_t *pu64LbaStart,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ LogFlowFunc(("\n"));
+ int rc = VINF_SUCCESS;
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ if ( pThis->pRegionList
+ && uRegion < pThis->pRegionList->cRegions)
+ {
+ PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[uRegion];
+
+ if (pu64LbaStart)
+ *pu64LbaStart = pRegion->offRegion;
+ if (pcBlocks)
+ *pcBlocks = pRegion->cRegionBlocksOrBytes;
+ if (pcbBlock)
+ *pcbBlock = pRegion->cbBlock;
+ if (penmDataForm)
+ *penmDataForm = pRegion->enmDataForm;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionPropertiesForLba} */
+static DECLCALLBACK(int) drvvdQueryRegionPropertiesForLba(PPDMIMEDIA pInterface, uint64_t u64LbaStart,
+ uint32_t *puRegion, uint64_t *pcBlocks,
+ uint64_t *pcbBlock, PVDREGIONDATAFORM penmDataForm)
+{
+ LogFlowFunc(("\n"));
+ int rc = VINF_SUCCESS;
+ PVBOXDISK pThis = PDMIMEDIA_2_VBOXDISK(pInterface);
+
+ if (!pThis->pRegionList)
+ rc = VDQueryRegions(pThis->pDisk, VD_LAST_IMAGE, VD_REGION_LIST_F_LOC_SIZE_BLOCKS,
+ &pThis->pRegionList);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_NOT_FOUND;
+
+ for (uint32_t i = 0; i < pThis->pRegionList->cRegions; i++)
+ {
+ PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
+ if ( pRegion->offRegion <= u64LbaStart
+ && pRegion->offRegion + pRegion->cRegionBlocksOrBytes > u64LbaStart)
+ {
+ uint64_t offRegion = u64LbaStart - pRegion->offRegion;
+
+ if (puRegion)
+ *puRegion = i;
+ if (pcBlocks)
+ *pcBlocks = pRegion->cRegionBlocksOrBytes - offRegion;
+ if (pcbBlock)
+ *pcbBlock = pRegion->cbBlock;
+ if (penmDataForm)
+ *penmDataForm = pRegion->enmDataForm;
+
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/* -=-=-=-=- IMount -=-=-=-=- */
+
+/** @interface_method_impl{PDMIMOUNT,pfnUnmount} */
+static DECLCALLBACK(int) drvvdUnmount(PPDMIMOUNT pInterface, bool fForce, bool fEject)
+{
+ RT_NOREF(fEject);
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMount);
+
+ /*
+ * Validate state.
+ */
+ if (!pThis->pDisk)
+ {
+ Log(("drvvdUnmount: Not mounted\n"));
+ return VERR_PDM_MEDIA_NOT_MOUNTED;
+ }
+ if (pThis->fLocked && !fForce)
+ {
+ Log(("drvvdUnmount: Locked\n"));
+ return VERR_PDM_MEDIA_LOCKED;
+ }
+
+ /* Media is no longer locked even if it was previously. */
+ pThis->fLocked = false;
+ drvvdPowerOffOrDestructOrUnmount(pThis->pDrvIns);
+
+ /*
+ * Notify driver/device above us.
+ */
+ if (pThis->pDrvMountNotify)
+ pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
+ Log(("drvblockUnmount: success\n"));
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsMounted} */
+static DECLCALLBACK(bool) drvvdIsMounted(PPDMIMOUNT pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMount);
+ return pThis->pDisk != NULL;
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnLock} */
+static DECLCALLBACK(int) drvvdLock(PPDMIMOUNT pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMount);
+ Log(("drvblockLock: %d -> %d\n", pThis->fLocked, true));
+ pThis->fLocked = true;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnUnlock} */
+static DECLCALLBACK(int) drvvdUnlock(PPDMIMOUNT pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMount);
+ Log(("drvblockUnlock: %d -> %d\n", pThis->fLocked, false));
+ pThis->fLocked = false;
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{PDMIMOUNT,pfnIsLocked} */
+static DECLCALLBACK(bool) drvvdIsLocked(PPDMIMOUNT pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMount);
+ return pThis->fLocked;
+}
+
+
+static DECLCALLBACK(void) drvvdBlkCacheReqComplete(void *pvUser1, void *pvUser2, int rcReq)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser1;
+
+ AssertPtr(pThis->pBlkCache);
+ PDMR3BlkCacheIoXferComplete(pThis->pBlkCache, (PPDMBLKCACHEIOXFER)pvUser2, rcReq);
+}
+
+
+/** @copydoc FNPDMBLKCACHEXFERCOMPLETEDRV */
+static DECLCALLBACK(void) drvvdBlkCacheXferCompleteIoReq(PPDMDRVINS pDrvIns, void *pvUser, int rc)
+{
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ drvvdMediaExIoReqCompleteWorker(pThis, (PPDMMEDIAEXIOREQINT)pvUser, rc, true /* fUpNotify */);
+}
+
+/** @copydoc FNPDMBLKCACHEXFERENQUEUEDRV */
+static DECLCALLBACK(int) drvvdBlkCacheXferEnqueue(PPDMDRVINS pDrvIns,
+ PDMBLKCACHEXFERDIR enmXferDir,
+ uint64_t off, size_t cbXfer,
+ PCRTSGBUF pSgBuf, PPDMBLKCACHEIOXFER hIoXfer)
+{
+ int rc = VINF_SUCCESS;
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ Assert (!pThis->pCfgCrypto);
+
+ switch (enmXferDir)
+ {
+ case PDMBLKCACHEXFERDIR_READ:
+ rc = VDAsyncRead(pThis->pDisk, off, cbXfer, pSgBuf, drvvdBlkCacheReqComplete,
+ pThis, hIoXfer);
+ break;
+ case PDMBLKCACHEXFERDIR_WRITE:
+ rc = VDAsyncWrite(pThis->pDisk, off, cbXfer, pSgBuf, drvvdBlkCacheReqComplete,
+ pThis, hIoXfer);
+ break;
+ case PDMBLKCACHEXFERDIR_FLUSH:
+ rc = VDAsyncFlush(pThis->pDisk, drvvdBlkCacheReqComplete, pThis, hIoXfer);
+ break;
+ default:
+ AssertMsgFailed(("Invalid transfer type %d\n", enmXferDir));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ PDMR3BlkCacheIoXferComplete(pThis->pBlkCache, hIoXfer, VINF_SUCCESS);
+ else if (RT_FAILURE(rc) && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ PDMR3BlkCacheIoXferComplete(pThis->pBlkCache, hIoXfer, rc);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc FNPDMBLKCACHEXFERENQUEUEDISCARDDRV */
+static DECLCALLBACK(int) drvvdBlkCacheXferEnqueueDiscard(PPDMDRVINS pDrvIns, PCRTRANGE paRanges,
+ unsigned cRanges, PPDMBLKCACHEIOXFER hIoXfer)
+{
+ int rc = VINF_SUCCESS;
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ rc = VDAsyncDiscardRanges(pThis->pDisk, paRanges, cRanges,
+ drvvdBlkCacheReqComplete, pThis, hIoXfer);
+
+ if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ PDMR3BlkCacheIoXferComplete(pThis->pBlkCache, hIoXfer, VINF_SUCCESS);
+ else if (RT_FAILURE(rc) && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ PDMR3BlkCacheIoXferComplete(pThis->pBlkCache, hIoXfer, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Extended media interface methods *
+*********************************************************************************************************************************/
+
+static void drvvdMediaExIoReqWarningDiskFull(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("VD#%u: Host disk full\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_DISKFULL",
+ N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
+ AssertRC(rc);
+}
+
+static void drvvdMediaExIoReqWarningFileTooBig(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("VD#%u: File too big\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_FILETOOBIG",
+ N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
+ AssertRC(rc);
+}
+
+static void drvvdMediaExIoReqWarningISCSI(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("VD#%u: iSCSI target unavailable\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_ISCSIDOWN",
+ N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+static void drvvdMediaExIoReqWarningDekMissing(PPDMDRVINS pDrvIns)
+{
+ LogRel(("VD#%u: DEK is missing\n", pDrvIns->iInstance));
+ int rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_DEKMISSING",
+ N_("VD: The DEK for this disk is missing"));
+ 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 drvvdMediaExIoReqIsRedoSetWarning(PVBOXDISK pThis, int rc)
+{
+ if (rc == VERR_DISK_FULL)
+ {
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvvdMediaExIoReqWarningDiskFull(pThis->pDrvIns);
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvvdMediaExIoReqWarningFileTooBig(pThis->pDrvIns);
+ return true;
+ }
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ /* iSCSI connection abort (first error) or failure to reestablish
+ * connection (second error). Pause VM. On resume we'll retry. */
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvvdMediaExIoReqWarningISCSI(pThis->pDrvIns);
+ return true;
+ }
+ if (rc == VERR_VD_DEK_MISSING)
+ {
+ /* Error message already set. */
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvvdMediaExIoReqWarningDekMissing(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) drvvdMediaExIoReqBufSync(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fToIoBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+ Assert(pIoReq->ReadWrite.cbIoBuf > 0);
+
+ if (!pIoReq->ReadWrite.fDirectBuf)
+ {
+ /* Make sure the buffer is reset. */
+ RTSgBufReset(&pIoReq->ReadWrite.IoBuf.SgBuf);
+
+ size_t const offSrc = pIoReq->ReadWrite.cbReq - pIoReq->ReadWrite.cbReqLeft;
+ Assert((uint32_t)offSrc == offSrc);
+ if (fToIoBuf)
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0], (uint32_t)offSrc,
+ &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)offSrc,
+ &pIoReq->ReadWrite.IoBuf.SgBuf,
+ (uint32_t)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) drvvdMediaExIoReqIdHash(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 drvvdMediaExIoReqInsert(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvvdMediaExIoReqIdHash(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
+ && pIt->enmState != VDIOREQSTATE_CANCELED))
+ {
+ 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 drvvdMediaExIoReqRemove(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvvdMediaExIoReqIdHash(pIoReq->uIoReqId);
+
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pIoReq->NdAllocatedList);
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * Retires a given I/O request marking it as complete and notiyfing the
+ * device/driver above about the completion if requested.
+ *
+ * @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 void drvvdMediaExIoReqRetire(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, int rcReq, bool fUpNotify)
+{
+ LogFlowFunc(("pThis=%#p pIoReq=%#p rcReq=%Rrc fUpNotify=%RTbool\n",
+ pThis, pIoReq, rcReq, fUpNotify));
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETING, VDIOREQSTATE_ACTIVE);
+ if (fXchg)
+ {
+ uint32_t cNew = ASMAtomicDecU32(&pThis->cIoReqsActive);
+ AssertMsg(cNew != UINT32_MAX, ("Number of active requests underflowed!\n")); RT_NOREF(cNew);
+ }
+ else
+ {
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ rcReq = VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETED);
+ drvvdMediaExIoReqBufFree(pThis, pIoReq);
+
+ /*
+ * 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(("VD#%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++ < DRVVD_MAX_LOG_REL_ERRORS)
+ {
+ if (rcReq == VERR_PDM_MEDIAEX_IOREQ_CANCELED)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("VD#%u: Aborted flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("VD#%u: Aborted discard returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("VD#%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(("VD#%u: Flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("VD#%u: Discard returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("VD#%u: %s (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "Read"
+ : "Write",
+ pIoReq->ReadWrite.cbReqLeft, rcReq));
+ }
+ }
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFailed);
+ }
+ else
+ {
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSucceeded);
+
+ switch (pIoReq->enmType)
+ {
+ case PDMMEDIAEXIOREQTYPE_READ:
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, pIoReq->ReadWrite.cbReq);
+ break;
+ case PDMMEDIAEXIOREQTYPE_WRITE:
+ STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, pIoReq->ReadWrite.cbReq);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (fUpNotify)
+ {
+ int rc = pThis->pDrvMediaExPort->pfnIoReqCompleteNotify(pThis->pDrvMediaExPort,
+ pIoReq, &pIoReq->abAlloc[0], rcReq);
+ AssertRC(rc);
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+/**
+ * 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 drvvdMediaExIoReqCompleteWorker(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, int rcReq, bool fUpNotify)
+{
+ LogFlowFunc(("pThis=%#p pIoReq=%#p rcReq=%Rrc fUpNotify=%RTbool\n",
+ pThis, pIoReq, rcReq, fUpNotify));
+
+ /*
+ * 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 = drvvdMediaExIoReqBufSync(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)
+ && drvvdMediaExIoReqIsRedoSetWarning(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);
+ uint32_t cNew = ASMAtomicDecU32(&pThis->cIoReqsActive);
+ AssertMsg(cNew != UINT32_MAX, ("Number of active requests underflowed!\n")); RT_NOREF(cNew);
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_SUSPENDED);
+ LogFlowFunc(("Suspended I/O request %#p\n", pIoReq));
+ rcReq = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+ else
+ {
+ /* Request was canceled inbetween, so don't care and notify the owner about the completed request. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvvdMediaExIoReqRetire(pThis, pIoReq, rcReq, fUpNotify);
+ }
+ }
+ else
+ {
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ /* Adjust the remaining amount to transfer. */
+ Assert(pIoReq->ReadWrite.cbIoBuf > 0 || rcReq == VERR_PDM_MEDIAEX_IOREQ_CANCELED);
+
+ 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))
+ drvvdMediaExIoReqRetire(pThis, pIoReq, rcReq, fUpNotify);
+ else
+ drvvdMediaExIoReqReadWriteProcess(pThis, pIoReq, fUpNotify);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rcReq));
+ 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) drvvdMediaExIoReqBufAlloc(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, size_t cb)
+{
+ int rc = VERR_NOT_SUPPORTED;
+ LogFlowFunc(("pThis=%#p pIoReq=%#p cb=%zu\n", pThis, pIoReq, cb));
+
+/** @todo This does not work at all with encryption enabled because the encryption plugin
+ * encrypts the data in place trashing guest memory and causing data corruption later on!
+ *
+ * DO NOT ENABLE UNLESS YOU WANT YOUR DATA SHREDDED!!!
+ */
+#if 0
+ if ( cb == _4K
+ && pThis->pDrvMediaExPort->pfnIoReqQueryBuf)
+ {
+ /* Try to get a direct pointer to the buffer first. */
+ void *pvBuf = NULL;
+ size_t cbBuf = 0;
+
+ STAM_COUNTER_INC(&pThis->StatQueryBufAttempts);
+ rc = pThis->pDrvMediaExPort->pfnIoReqQueryBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ &pvBuf, &cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ STAM_COUNTER_INC(&pThis->StatQueryBufSuccess);
+ pIoReq->ReadWrite.cbIoBuf = cbBuf;
+ pIoReq->ReadWrite.fDirectBuf = true;
+ pIoReq->ReadWrite.Direct.Seg.pvSeg = pvBuf;
+ pIoReq->ReadWrite.Direct.Seg.cbSeg = cbBuf;
+ RTSgBufInit(&pIoReq->ReadWrite.Direct.SgBuf, &pIoReq->ReadWrite.Direct.Seg, 1);
+ pIoReq->ReadWrite.pSgBuf = &pIoReq->ReadWrite.Direct.SgBuf;
+ }
+ }
+#endif
+
+ if (RT_FAILURE(rc))
+ {
+ rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReq->ReadWrite.IoBuf, cb, &pIoReq->ReadWrite.cbIoBuf);
+ if (rc == VERR_NO_MEMORY)
+ {
+ LogFlowFunc(("Could not allocate memory for request, deferring\n"));
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ RTListAppend(&pThis->LstIoReqIoBufWait, &pIoReq->NdLstWait);
+ ASMAtomicIncU32(&pThis->cIoReqsWaiting);
+ if (ASMAtomicReadBool(&pThis->fSuspending))
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_SUSPENDED);
+ LogFlowFunc(("Suspended I/O request %#p\n", pIoReq));
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+ else
+ {
+ LogFlowFunc(("Allocated %zu bytes of memory\n", pIoReq->ReadWrite.cbIoBuf));
+ Assert(pIoReq->ReadWrite.cbIoBuf > 0);
+ pIoReq->ReadWrite.fDirectBuf = false;
+ pIoReq->ReadWrite.pSgBuf = &pIoReq->ReadWrite.IoBuf.SgBuf;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Wrapper around the various ways to read from the underlying medium (cache, async vs. sync).
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ * @param cbReqIo Transfer size.
+ * @param pcbReqIo Where to store the amount of transferred data.
+ */
+static int drvvdMediaExIoReqReadWrapper(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, size_t cbReqIo, size_t *pcbReqIo)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pIoReq=%#p cbReqIo=%zu pcbReqIo=%#p\n", pThis, pIoReq, cbReqIo, pcbReqIo));
+
+ Assert(cbReqIo > 0);
+
+ if ( pThis->fAsyncIOSupported
+ && !(pIoReq->fFlags & PDMIMEDIAEX_F_SYNC))
+ {
+ if (pThis->pBlkCache)
+ {
+ rc = PDMR3BlkCacheRead(pThis->pBlkCache, pIoReq->ReadWrite.offStart,
+ pIoReq->ReadWrite.pSgBuf, cbReqIo, pIoReq);
+ if (rc == VINF_SUCCESS)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ rc = VDAsyncRead(pThis->pDisk, pIoReq->ReadWrite.offStart, cbReqIo, pIoReq->ReadWrite.pSgBuf,
+ drvvdMediaExIoReqComplete, pThis, pIoReq);
+ }
+ else
+ {
+ void *pvBuf = RTSgBufGetNextSegment(pIoReq->ReadWrite.pSgBuf, &cbReqIo);
+
+ Assert(cbReqIo > 0 && VALID_PTR(pvBuf));
+ rc = VDRead(pThis->pDisk, pIoReq->ReadWrite.offStart, pvBuf, cbReqIo);
+ if (RT_SUCCESS(rc))
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+
+ *pcbReqIo = cbReqIo;
+
+ LogFlowFunc(("returns %Rrc *pcbReqIo=%zu\n", rc, *pcbReqIo));
+ return rc;
+}
+
+/**
+ * Wrapper around the various ways to write to the underlying medium (cache, async vs. sync).
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ * @param cbReqIo Transfer size.
+ * @param pcbReqIo Where to store the amount of transferred data.
+ */
+static int drvvdMediaExIoReqWriteWrapper(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, size_t cbReqIo, size_t *pcbReqIo)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(cbReqIo > 0);
+
+ LogFlowFunc(("pThis=%#p pIoReq=%#p cbReqIo=%zu pcbReqIo=%#p\n", pThis, pIoReq, cbReqIo, pcbReqIo));
+
+ if ( pThis->fAsyncIOSupported
+ && !(pIoReq->fFlags & PDMIMEDIAEX_F_SYNC))
+ {
+ if (pThis->pBlkCache)
+ {
+ rc = PDMR3BlkCacheWrite(pThis->pBlkCache, pIoReq->ReadWrite.offStart,
+ pIoReq->ReadWrite.pSgBuf, cbReqIo, pIoReq);
+ if (rc == VINF_SUCCESS)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ rc = VDAsyncWrite(pThis->pDisk, pIoReq->ReadWrite.offStart, cbReqIo, pIoReq->ReadWrite.pSgBuf,
+ drvvdMediaExIoReqComplete, pThis, pIoReq);
+ }
+ else
+ {
+ void *pvBuf = RTSgBufGetNextSegment(pIoReq->ReadWrite.pSgBuf, &cbReqIo);
+
+ Assert(cbReqIo > 0 && VALID_PTR(pvBuf));
+ rc = VDWrite(pThis->pDisk, pIoReq->ReadWrite.offStart, pvBuf, cbReqIo);
+ if (RT_SUCCESS(rc))
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+
+#ifdef VBOX_PERIODIC_FLUSH
+ if (pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten += (uint32_t)cbReqIo;
+ if (pThis->cbDataWritten > pThis->cbFlushInterval)
+ {
+ pThis->cbDataWritten = 0;
+ VDFlush(pThis->pDisk);
+ }
+ }
+#endif /* VBOX_PERIODIC_FLUSH */
+ }
+
+ *pcbReqIo = cbReqIo;
+
+ LogFlowFunc(("returns %Rrc *pcbReqIo=%zu\n", rc, *pcbReqIo));
+ return rc;
+}
+
+/**
+ * Wrapper around the various ways to flush all data to the underlying medium (cache, async vs. sync).
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ */
+static int drvvdMediaExIoReqFlushWrapper(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pIoReq=%#p\n", pThis, pIoReq));
+
+ if ( pThis->fAsyncIOSupported
+ && !(pIoReq->fFlags & PDMIMEDIAEX_F_SYNC))
+ {
+#ifdef VBOX_IGNORE_FLUSH
+ if (pThis->fIgnoreFlushAsync)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else
+#endif /* VBOX_IGNORE_FLUSH */
+ {
+ if (pThis->pBlkCache)
+ {
+ rc = PDMR3BlkCacheFlush(pThis->pBlkCache, pIoReq);
+ if (rc == VINF_SUCCESS)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ rc = VDAsyncFlush(pThis->pDisk, drvvdMediaExIoReqComplete, pThis, pIoReq);
+ }
+ }
+ else
+ {
+#ifdef VBOX_IGNORE_FLUSH
+ if (pThis->fIgnoreFlush)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else
+#endif /* VBOX_IGNORE_FLUSH */
+ {
+ rc = VDFlush(pThis->pDisk);
+ if (RT_SUCCESS(rc))
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Wrapper around the various ways to discard data blocks on the underlying medium (cache, async vs. sync).
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ */
+static int drvvdMediaExIoReqDiscardWrapper(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pIoReq=%#p\n", pThis, pIoReq));
+
+ if ( pThis->fAsyncIOSupported
+ && !(pIoReq->fFlags & PDMIMEDIAEX_F_SYNC))
+ {
+ if (pThis->pBlkCache)
+ {
+ rc = PDMR3BlkCacheDiscard(pThis->pBlkCache, pIoReq->Discard.paRanges, pIoReq->Discard.cRanges, pIoReq);
+ if (rc == VINF_SUCCESS)
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ else if (rc == VINF_AIO_TASK_PENDING)
+ rc = VERR_VD_ASYNC_IO_IN_PROGRESS;
+ }
+ else
+ rc = VDAsyncDiscardRanges(pThis->pDisk, pIoReq->Discard.paRanges, pIoReq->Discard.cRanges,
+ drvvdMediaExIoReqComplete, pThis, pIoReq);
+ }
+ else
+ {
+ rc = VDDiscardRanges(pThis->pDisk, pIoReq->Discard.paRanges, pIoReq->Discard.cRanges);
+ if (RT_SUCCESS(rc))
+ rc = VINF_VD_ASYNC_IO_FINISHED;
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * 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 drvvdMediaExIoReqReadWriteProcess(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fUpNotify)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pIoReq=%#p fUpNotify=%RTbool\n", pThis, pIoReq, fUpNotify));
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+
+ rc = drvvdKeyCheckPrereqs(pThis, false /* fSetError */);
+
+ while ( pIoReq->ReadWrite.cbReqLeft
+ && rc == VINF_SUCCESS)
+ {
+ Assert(pIoReq->ReadWrite.cbIoBuf > 0);
+
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rc = drvvdMediaExIoReqReadWrapper(pThis, pIoReq, cbReqIo, &cbReqIo);
+ else
+ {
+ /* Sync memory buffer from the request initiator. */
+ rc = drvvdMediaExIoReqBufSync(pThis, pIoReq, true /* fToIoBuf */);
+ if (RT_SUCCESS(rc))
+ rc = drvvdMediaExIoReqWriteWrapper(pThis, pIoReq, cbReqIo, &cbReqIo);
+ }
+
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ {
+ /*
+ * Don't sync the buffer or update the I/O state for the last chunk as it is done
+ * already in the completion worker called below.
+ */
+ if (cbReqIo < pIoReq->ReadWrite.cbReqLeft)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rc = drvvdMediaExIoReqBufSync(pThis, pIoReq, false /* fToIoBuf */);
+ else
+ rc = VINF_SUCCESS;
+ pIoReq->ReadWrite.offStart += cbReqIo;
+ pIoReq->ReadWrite.cbReqLeft -= cbReqIo;
+ }
+ else
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, rc, fUpNotify);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Tries to process any requests waiting for available I/O memory.
+ *
+ * @returns nothing.
+ * @param pThis VBox disk container instance data.
+ */
+static void drvvdMediaExIoReqProcessWaiting(PVBOXDISK pThis)
+{
+ uint32_t cIoReqsWaiting = ASMAtomicXchgU32(&pThis->cIoReqsWaiting, 0);
+ if (cIoReqsWaiting > 0)
+ {
+ RTLISTANCHOR LstIoReqProcess;
+ RTLISTANCHOR LstIoReqCanceled;
+ RTListInit(&LstIoReqProcess);
+ RTListInit(&LstIoReqCanceled);
+
+ /* Try to process as many requests as possible. */
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ PPDMMEDIAEXIOREQINT pIoReqCur, pIoReqNext;
+
+ RTListForEachSafe(&pThis->LstIoReqIoBufWait, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ LogFlowFunc(("Found I/O request %#p on waiting list, trying to allocate buffer of size %zu bytes\n",
+ pIoReqCur, pIoReqCur->ReadWrite.cbReq));
+
+ /* 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)
+ {
+ Assert(pIoReqCur->ReadWrite.cbIoBuf > 0);
+
+ cIoReqsWaiting--;
+ RTListNodeRemove(&pIoReqCur->NdLstWait);
+
+ pIoReqCur->ReadWrite.fDirectBuf = false;
+ pIoReqCur->ReadWrite.pSgBuf = &pIoReqCur->ReadWrite.IoBuf.SgBuf;
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReqCur->enmState,
+ VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReqCur->enmState == VDIOREQSTATE_CANCELED);
+
+ /* Free the buffer here already again to let other requests get a chance to allocate the memory. */
+ IOBUFMgrFreeBuf(&pIoReqCur->ReadWrite.IoBuf);
+ pIoReqCur->ReadWrite.cbIoBuf = 0;
+ RTListAppend(&LstIoReqCanceled, &pIoReqCur->NdLstWait);
+ }
+ else
+ {
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ RTListAppend(&LstIoReqProcess, &pIoReqCur->NdLstWait);
+ }
+ }
+ else
+ {
+ Assert(rc == VERR_NO_MEMORY);
+ break;
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+
+ ASMAtomicAddU32(&pThis->cIoReqsWaiting, cIoReqsWaiting);
+
+ /* Process the requests we could allocate memory for and the ones which got canceled outside the lock now. */
+ RTListForEachSafe(&LstIoReqCanceled, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ RTListNodeRemove(&pIoReqCur->NdLstWait);
+ drvvdMediaExIoReqCompleteWorker(pThis, pIoReqCur, VERR_PDM_MEDIAEX_IOREQ_CANCELED, true /* fUpNotify */);
+ }
+
+ RTListForEachSafe(&LstIoReqProcess, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ RTListNodeRemove(&pIoReqCur->NdLstWait);
+ drvvdMediaExIoReqReadWriteProcess(pThis, pIoReqCur, true /* fUpNotify */);
+ }
+ }
+}
+
+/**
+ * Frees a I/O memory buffer allocated previously.
+ *
+ * @returns nothing.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request for which to free memory.
+ */
+DECLINLINE(void) drvvdMediaExIoReqBufFree(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ LogFlowFunc(("pThis=%#p pIoReq=%#p{.cbIoBuf=%zu}\n", pThis, pIoReq, pIoReq->ReadWrite.cbIoBuf));
+
+ if ( ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ && !pIoReq->ReadWrite.fDirectBuf
+ && pIoReq->ReadWrite.cbIoBuf > 0)
+ {
+ IOBUFMgrFreeBuf(&pIoReq->ReadWrite.IoBuf);
+
+ if (!ASMAtomicReadBool(&pThis->fSuspending))
+ drvvdMediaExIoReqProcessWaiting(pThis);
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Returns a string description of the given request state.
+ *
+ * @returns Pointer to the stringified state.
+ * @param enmState The state.
+ */
+DECLINLINE(const char *) drvvdMediaExIoReqStateStringify(VDIOREQSTATE enmState)
+{
+#define STATE2STR(a_State) case VDIOREQSTATE_##a_State: return #a_State
+ switch (enmState)
+ {
+ STATE2STR(INVALID);
+ STATE2STR(FREE);
+ STATE2STR(ALLOCATED);
+ STATE2STR(ACTIVE);
+ STATE2STR(SUSPENDED);
+ STATE2STR(COMPLETING);
+ STATE2STR(COMPLETED);
+ STATE2STR(CANCELED);
+ default:
+ AssertMsgFailed(("Unknown state %u\n", enmState));
+ return "UNKNOWN";
+ }
+#undef STATE2STR
+}
+
+
+/**
+ * Returns a string description of the given request type.
+ *
+ * @returns Pointer to the stringified type.
+ * @param enmType The request type.
+ */
+DECLINLINE(const char *) drvvdMediaExIoReqTypeStringify(PDMMEDIAEXIOREQTYPE enmType)
+{
+#define TYPE2STR(a_Type) case PDMMEDIAEXIOREQTYPE_##a_Type: return #a_Type
+ switch (enmType)
+ {
+ TYPE2STR(INVALID);
+ TYPE2STR(FLUSH);
+ TYPE2STR(WRITE);
+ TYPE2STR(READ);
+ TYPE2STR(DISCARD);
+ TYPE2STR(SCSI);
+ default:
+ AssertMsgFailed(("Unknown type %u\n", enmType));
+ return "UNKNOWN";
+ }
+#undef TYPE2STR
+}
+
+
+/**
+ * Dumps the interesting bits about the given I/O request to the release log.
+ *
+ * @returns nothing.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq The I/O request to dump.
+ */
+static void drvvdMediaExIoReqLogRel(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ uint64_t offStart = 0;
+ size_t cbReq = 0;
+ size_t cbLeft = 0;
+ size_t cbBufSize = 0;
+ uint64_t tsActive = RTTimeMilliTS() - pIoReq->tsSubmit;
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ offStart = pIoReq->ReadWrite.offStart;
+ cbReq = pIoReq->ReadWrite.cbReq;
+ cbLeft = pIoReq->ReadWrite.cbReqLeft;
+ cbBufSize = pIoReq->ReadWrite.cbIoBuf;
+ }
+
+ LogRel(("VD#%u: Request{%#p}:\n"
+ " Type=%s State=%s Id=%#llx SubmitTs=%llu {%llu} Flags=%#x\n"
+ " Offset=%llu Size=%zu Left=%zu BufSize=%zu\n",
+ pThis->pDrvIns->iInstance, pIoReq,
+ drvvdMediaExIoReqTypeStringify(pIoReq->enmType),
+ drvvdMediaExIoReqStateStringify(pIoReq->enmState),
+ pIoReq->uIoReqId, pIoReq->tsSubmit, tsActive, pIoReq->fFlags,
+ offStart, cbReq, cbLeft, cbBufSize));
+}
+
+
+/**
+ * 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) drvvdMediaExIoReqIsVmRunning(PVBOXDISK pThis)
+{
+ VMSTATE enmVmState = PDMDrvHlpVMState(pThis->pDrvIns);
+ if ( enmVmState == VMSTATE_RESUMING
+ || enmVmState == VMSTATE_RUNNING
+ || enmVmState == VMSTATE_RUNNING_LS
+ || enmVmState == VMSTATE_RUNNING_FT
+ || 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 DECLCALLBACK(void) drvvdMediaExIoReqComplete(void *pvUser1, void *pvUser2, int rcReq)
+{
+ PVBOXDISK pThis = (PVBOXDISK)pvUser1;
+ PPDMMEDIAEXIOREQINT pIoReq = (PPDMMEDIAEXIOREQINT)pvUser2;
+
+ drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, rcReq, true /* fUpNotify */);
+}
+
+/**
+ * Tries to cancel the given I/O request returning the result.
+ *
+ * @returns Flag whether the request was successfully canceled or whether it
+ * already complete inbetween.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq The I/O request to cancel.
+ */
+static bool drvvdMediaExIoReqCancel(PVBOXDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ bool fXchg = false;
+ VDIOREQSTATE enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ drvvdMediaExIoReqLogRel(pThis, pIoReq);
+
+ /*
+ * 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 *)&pIoReq->enmState, VDIOREQSTATE_CANCELED, enmStateOld);
+ if (fXchg)
+ break;
+
+ enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+ }
+
+ if (fXchg && enmStateOld == VDIOREQSTATE_ACTIVE)
+ {
+ uint32_t cNew = ASMAtomicDecU32(&pThis->cIoReqsActive);
+ AssertMsg(cNew != UINT32_MAX, ("Number of active requests underflowed!\n")); RT_NOREF(cNew);
+ }
+
+ return fXchg;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures}
+ */
+static DECLCALLBACK(int) drvvdQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+
+ AssertPtrReturn(pfFeatures, VERR_INVALID_POINTER);
+
+ uint32_t fFeatures = 0;
+ if (pThis->fAsyncIOSupported)
+ fFeatures |= PDMIMEDIAEX_FEATURE_F_ASYNC;
+ if (pThis->IMedia.pfnDiscard)
+ fFeatures |= PDMIMEDIAEX_FEATURE_F_DISCARD;
+
+ *pfFeatures = fFeatures;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnNotifySuspend}
+ */
+static DECLCALLBACK(void) drvvdNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+
+ ASMAtomicXchgBool(&pThis->fSuspending, true);
+
+ /* Mark all waiting requests as suspended so they don't get accounted for. */
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ PPDMMEDIAEXIOREQINT pIoReqCur, pIoReqNext;
+ RTListForEachSafe(&pThis->LstIoReqIoBufWait, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReqCur, &pIoReqCur->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_SUSPENDED);
+ LogFlowFunc(("Suspended I/O request %#p\n", pIoReqCur));
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet}
+ */
+static DECLCALLBACK(int) drvvdIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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) drvvdIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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 = drvvdMediaExIoReqInsert(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) drvvdIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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 = drvvdMediaExIoReqRemove(pThis, pIoReq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Free any associated I/O memory. */
+ drvvdMediaExIoReqBufFree(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) drvvdIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ RT_NOREF1(pInterface);
+
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if (pIoReq->enmState != VDIOREQSTATE_COMPLETED)
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ if ( pIoReq->enmType != PDMMEDIAEXIOREQTYPE_READ
+ && pIoReq->enmType != PDMMEDIAEXIOREQTYPE_WRITE
+ && pIoReq->enmType != PDMMEDIAEXIOREQTYPE_FLUSH)
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ *pcbResidual = 0; /* No data left to transfer always. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize}
+ */
+static DECLCALLBACK(int) drvvdIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ int rc = VINF_SUCCESS;
+ RT_NOREF1(pInterface);
+
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if (pIoReq->enmState != VDIOREQSTATE_COMPLETED)
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ *pcbXfer = pIoReq->ReadWrite.cbReq;
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ *pcbXfer = 0;
+ else
+ rc = VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll}
+ */
+static DECLCALLBACK(int) drvvdIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ int rc = VINF_SUCCESS;
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+
+ LogRel(("VD#%u: Cancelling all active requests\n", pThis->pDrvIns->iInstance));
+
+ for (unsigned idxBin = 0; idxBin < RT_ELEMENTS(pThis->aIoReqAllocBins); idxBin++)
+ {
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for I/O request with ID. */
+ PPDMMEDIAEXIOREQINT pIt;
+
+ RTListForEach(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, pIt, PDMMEDIAEXIOREQINT, NdAllocatedList)
+ {
+ drvvdMediaExIoReqCancel(pThis, pIt);
+ }
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel}
+ */
+static DECLCALLBACK(int) drvvdIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+ unsigned idxBin = drvvdMediaExIoReqIdHash(uIoReqId);
+
+ LogRel(("VD#%u: Trying to cancel request %#llx\n", pThis->pDrvIns->iInstance, 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)
+ {
+ if (drvvdMediaExIoReqCancel(pThis, pIt))
+ rc = VINF_SUCCESS;
+
+ break;
+ }
+ }
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead}
+ */
+static DECLCALLBACK(int) drvvdIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsRead);
+
+ 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 = drvvdMediaExIoReqBufAlloc(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 = drvvdMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite}
+ */
+static DECLCALLBACK(int) drvvdIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsWrite);
+
+ 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 = drvvdMediaExIoReqBufAlloc(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 = drvvdMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush}
+ */
+static DECLCALLBACK(int) drvvdIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsFlush);
+
+ 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);
+ int rc = drvvdMediaExIoReqFlushWrapper(pThis, pIoReq);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, rc, false /* fUpNotify */);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard}
+ */
+static DECLCALLBACK(int) drvvdIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, 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;
+
+ STAM_REL_COUNTER_INC(&pThis->StatReqsSubmitted);
+ STAM_REL_COUNTER_INC(&pThis->StatReqsDiscard);
+
+ /* 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 = drvvdMediaExIoReqDiscardWrapper(pThis, pIoReq);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ rc = drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, rc, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd}
+ */
+static DECLCALLBACK(int) drvvdIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
+ const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
+ uint32_t cTimeoutMillies)
+{
+ RT_NOREF10(pInterface, uLun, pbCdb, cbCdb, enmTxDir, cbBuf, pabSense, cbSense, pu8ScsiSts, cTimeoutMillies);
+ 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;
+
+ return VERR_NOT_SUPPORTED;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount}
+ */
+static DECLCALLBACK(uint32_t) drvvdIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+ return ASMAtomicReadU32(&pThis->cIoReqsActive);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount}
+ */
+static DECLCALLBACK(uint32_t) drvvdIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+
+ AssertReturn(!drvvdMediaExIoReqIsVmRunning(pThis), 0);
+
+ uint32_t cIoReqSuspended = 0;
+ PPDMMEDIAEXIOREQINT pIoReq;
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListForEach(&pThis->LstIoReqRedo, pIoReq, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ cIoReqSuspended++;
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return cIoReqSuspended + pThis->cIoReqsWaiting;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart}
+ */
+static DECLCALLBACK(int) drvvdIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq,
+ void **ppvIoReqAlloc)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+
+ AssertReturn(!drvvdMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertReturn(!( RTListIsEmpty(&pThis->LstIoReqRedo)
+ && RTListIsEmpty(&pThis->LstIoReqIoBufWait)), VERR_NOT_FOUND);
+
+ PRTLISTANCHOR pLst;
+ PRTCRITSECT pCritSect;
+ if (!RTListIsEmpty(&pThis->LstIoReqRedo))
+ {
+ pLst = &pThis->LstIoReqRedo;
+ pCritSect = &pThis->CritSectIoReqRedo;
+ }
+ else
+ {
+ pLst = &pThis->LstIoReqIoBufWait;
+ pCritSect = &pThis->CritSectIoReqsIoBufWait;
+ }
+
+ RTCritSectEnter(pCritSect);
+ PPDMMEDIAEXIOREQINT pIoReq = RTListGetFirst(pLst, PDMMEDIAEXIOREQINT, NdLstWait);
+ *phIoReq = pIoReq;
+ *ppvIoReqAlloc = &pIoReq->abAlloc[0];
+ RTCritSectLeave(pCritSect);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext}
+ */
+static DECLCALLBACK(int) drvvdIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ AssertReturn(!drvvdMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn( ( pIoReq->enmState == VDIOREQSTATE_SUSPENDED
+ && ( !RTListNodeIsLast(&pThis->LstIoReqRedo, &pIoReq->NdLstWait)
+ || !RTListIsEmpty(&pThis->LstIoReqIoBufWait)))
+ || ( pIoReq->enmState == VDIOREQSTATE_ALLOCATED
+ && !RTListNodeIsLast(&pThis->LstIoReqIoBufWait, &pIoReq->NdLstWait)), VERR_NOT_FOUND);
+
+ PPDMMEDIAEXIOREQINT pIoReqNext;
+ if (pIoReq->enmState == VDIOREQSTATE_SUSPENDED)
+ {
+ if (!RTListNodeIsLast(&pThis->LstIoReqRedo, &pIoReq->NdLstWait))
+ {
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ pIoReqNext = RTListNodeGetNext(&pIoReq->NdLstWait, PDMMEDIAEXIOREQINT, NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+ }
+ else
+ {
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ pIoReqNext = RTListGetFirst(&pThis->LstIoReqIoBufWait, PDMMEDIAEXIOREQINT, NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ }
+ }
+ else
+ {
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ pIoReqNext = RTListNodeGetNext(&pIoReq->NdLstWait, PDMMEDIAEXIOREQINT, NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ }
+
+ *phIoReqNext = pIoReqNext;
+ *ppvIoReqAllocNext = &pIoReqNext->abAlloc[0];
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave}
+ */
+static DECLCALLBACK(int) drvvdIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ AssertReturn(!drvvdMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn( pIoReq->enmState == VDIOREQSTATE_SUSPENDED
+ || pIoReq->enmState == VDIOREQSTATE_ALLOCATED, VERR_INVALID_STATE);
+
+ SSMR3PutU32(pSSM, DRVVD_IOREQ_SAVED_STATE_VERSION);
+ SSMR3PutU32(pSSM, (uint32_t)pIoReq->enmType);
+ SSMR3PutU32(pSSM, pIoReq->uIoReqId);
+ SSMR3PutU32(pSSM, pIoReq->fFlags);
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ SSMR3PutU64(pSSM, pIoReq->ReadWrite.offStart);
+ SSMR3PutU64(pSSM, pIoReq->ReadWrite.cbReq);
+ SSMR3PutU64(pSSM, pIoReq->ReadWrite.cbReqLeft);
+ }
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ {
+ SSMR3PutU32(pSSM, pIoReq->Discard.cRanges);
+ for (unsigned i = 0; i < pIoReq->Discard.cRanges; i++)
+ {
+ SSMR3PutU64(pSSM, pIoReq->Discard.paRanges[i].offStart);
+ SSMR3PutU64(pSSM, pIoReq->Discard.paRanges[i].cbRange);
+ }
+ }
+
+ return SSMR3PutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad}
+ */
+static DECLCALLBACK(int) drvvdIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PVBOXDISK pThis = RT_FROM_MEMBER(pInterface, VBOXDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ AssertReturn(!drvvdMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pIoReq->enmState == VDIOREQSTATE_ALLOCATED, VERR_INVALID_STATE);
+
+ uint32_t u32;
+ uint64_t u64;
+ int rc = VINF_SUCCESS;
+ bool fPlaceOnRedoList = true;
+
+ SSMR3GetU32(pSSM, &u32);
+ if (u32 <= DRVVD_IOREQ_SAVED_STATE_VERSION)
+ {
+ SSMR3GetU32(pSSM, &u32);
+ AssertReturn( u32 == PDMMEDIAEXIOREQTYPE_WRITE
+ || u32 == PDMMEDIAEXIOREQTYPE_READ
+ || u32 == PDMMEDIAEXIOREQTYPE_DISCARD
+ || u32 == PDMMEDIAEXIOREQTYPE_FLUSH,
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ pIoReq->enmType = (PDMMEDIAEXIOREQTYPE)u32;
+
+ SSMR3GetU32(pSSM, &u32);
+ AssertReturn(u32 == pIoReq->uIoReqId, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ SSMR3GetU32(pSSM, &u32);
+ AssertReturn(u32 == pIoReq->fFlags, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ SSMR3GetU64(pSSM, &pIoReq->ReadWrite.offStart);
+ SSMR3GetU64(pSSM, &u64);
+ pIoReq->ReadWrite.cbReq = (size_t)u64;
+ SSMR3GetU64(pSSM, &u64);
+ pIoReq->ReadWrite.cbReqLeft = (size_t)u64;
+
+ /*
+ * Try to allocate enough I/O buffer, if this fails for some reason put it onto the
+ * waiting list instead of the redo list.
+ */
+ pIoReq->ReadWrite.cbIoBuf = 0;
+ rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReq->ReadWrite.IoBuf, pIoReq->ReadWrite.cbReqLeft,
+ &pIoReq->ReadWrite.cbIoBuf);
+ if (rc == VERR_NO_MEMORY)
+ {
+ pIoReq->enmState = VDIOREQSTATE_ALLOCATED;
+ ASMAtomicIncU32(&pThis->cIoReqsWaiting);
+ RTListAppend(&pThis->LstIoReqIoBufWait, &pIoReq->NdLstWait);
+ fPlaceOnRedoList = false;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pIoReq->ReadWrite.fDirectBuf = false;
+ pIoReq->ReadWrite.pSgBuf = &pIoReq->ReadWrite.IoBuf.SgBuf;
+ }
+ }
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ {
+ rc = SSMR3GetU32(pSSM, &pIoReq->Discard.cRanges);
+ if (RT_SUCCESS(rc))
+ {
+ pIoReq->Discard.paRanges = (PRTRANGE)RTMemAllocZ(pIoReq->Discard.cRanges * sizeof(RTRANGE));
+ if (RT_LIKELY(pIoReq->Discard.paRanges))
+ {
+ for (unsigned i = 0; i < pIoReq->Discard.cRanges; i++)
+ {
+ SSMR3GetU64(pSSM, &pIoReq->Discard.paRanges[i].offStart);
+ SSMR3GetU64(pSSM, &u64);
+ pIoReq->Discard.paRanges[i].cbRange = (size_t)u64;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = SSMR3GetU32(pSSM, &u32); /* sanity/terminator */
+ if (RT_SUCCESS(rc))
+ AssertReturn(u32 == UINT32_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ if ( RT_SUCCESS(rc)
+ && fPlaceOnRedoList)
+ {
+ /* Mark as suspended */
+ pIoReq->enmState = VDIOREQSTATE_SUSPENDED;
+
+ /* Link into suspended list so it gets kicked off again when we resume. */
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListAppend(&pThis->LstIoReqRedo, &pIoReq->NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Loads all configured plugins.
+ *
+ * @returns VBox status code.
+ * @param pCfg CFGM node holding plugin list.
+ */
+static int drvvdLoadPlugins(PCFGMNODE pCfg)
+{
+ PCFGMNODE pCfgPlugins = CFGMR3GetChild(pCfg, "Plugins");
+
+ if (pCfgPlugins)
+ {
+ PCFGMNODE pPluginCur = CFGMR3GetFirstChild(pCfgPlugins);
+ while (pPluginCur)
+ {
+ int rc = VINF_SUCCESS;
+ char *pszPluginFilename = NULL;
+ rc = CFGMR3QueryStringAlloc(pPluginCur, "Path", &pszPluginFilename);
+ if (RT_SUCCESS(rc))
+ rc = VDPluginLoadFromFilename(pszPluginFilename);
+
+ if (RT_FAILURE(rc))
+ LogRel(("VD: Failed to load plugin '%s' with %Rrc, continuing\n", pszPluginFilename, rc));
+
+ pPluginCur = CFGMR3GetNextChild(pPluginCur);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets up the disk filter chain.
+ *
+ * @returns VBox status code.
+ * @param pThis The disk instance.
+ * @param pCfg CFGM node holding the filter parameters.
+ */
+static int drvvdSetupFilters(PVBOXDISK pThis, PCFGMNODE pCfg)
+{
+ int rc = VINF_SUCCESS;
+ PCFGMNODE pCfgFilter = CFGMR3GetChild(pCfg, "Filters");
+
+ if (pCfgFilter)
+ {
+ PCFGMNODE pCfgFilterConfig = CFGMR3GetChild(pCfgFilter, "VDConfig");
+ char *pszFilterName = NULL;
+ VDINTERFACECONFIG VDIfConfig;
+ PVDINTERFACE pVDIfsFilter = NULL;
+
+ rc = CFGMR3QueryStringAlloc(pCfgFilter, "FilterName", &pszFilterName);
+ if (RT_SUCCESS(rc))
+ {
+ VDIfConfig.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ VDIfConfig.pfnQuerySize = drvvdCfgQuerySize;
+ VDIfConfig.pfnQuery = drvvdCfgQuery;
+ VDIfConfig.pfnQueryBytes = drvvdCfgQueryBytes;
+ rc = VDInterfaceAdd(&VDIfConfig.Core, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ pCfgFilterConfig, sizeof(VDINTERFACECONFIG), &pVDIfsFilter);
+ AssertRC(rc);
+
+ rc = VDFilterAdd(pThis->pDisk, pszFilterName, VD_FILTER_FLAGS_DEFAULT, pVDIfsFilter);
+
+ MMR3HeapFree(pszFilterName);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Translates a PDMMEDIATYPE value into a string.
+ *
+ * @returns Read only string.
+ * @param enmType The type value.
+ */
+static const char *drvvdGetTypeName(PDMMEDIATYPE enmType)
+{
+ switch (enmType)
+ {
+ case PDMMEDIATYPE_ERROR: return "ERROR";
+ case PDMMEDIATYPE_FLOPPY_360: return "FLOPPY_360";
+ case PDMMEDIATYPE_FLOPPY_720: return "FLOPPY_720";
+ case PDMMEDIATYPE_FLOPPY_1_20: return "FLOPPY_1_20";
+ case PDMMEDIATYPE_FLOPPY_1_44: return "FLOPPY_1_44";
+ case PDMMEDIATYPE_FLOPPY_2_88: return "FLOPPY_2_88";
+ case PDMMEDIATYPE_FLOPPY_FAKE_15_6: return "FLOPPY_FAKE_15_6";
+ case PDMMEDIATYPE_FLOPPY_FAKE_63_5: return "FLOPPY_FAKE_63_5";
+ case PDMMEDIATYPE_CDROM: return "CDROM";
+ case PDMMEDIATYPE_DVD: return "DVD";
+ case PDMMEDIATYPE_HARD_DISK: return "HARD_DISK";
+ default: return "Unknown";
+ }
+}
+
+/**
+ * Returns the appropriate PDMMEDIATYPE for t he given string.
+ *
+ * @returns PDMMEDIATYPE
+ * @param pszType The string representation of the media type.
+ */
+static PDMMEDIATYPE drvvdGetMediaTypeFromString(const char *pszType)
+{
+ PDMMEDIATYPE enmType = PDMMEDIATYPE_ERROR;
+
+ if (!strcmp(pszType, "HardDisk"))
+ enmType = PDMMEDIATYPE_HARD_DISK;
+ else if (!strcmp(pszType, "DVD"))
+ enmType = PDMMEDIATYPE_DVD;
+ else if (!strcmp(pszType, "CDROM"))
+ enmType = PDMMEDIATYPE_CDROM;
+ else if (!strcmp(pszType, "Floppy 2.88"))
+ enmType = PDMMEDIATYPE_FLOPPY_2_88;
+ else if (!strcmp(pszType, "Floppy 1.44"))
+ enmType = PDMMEDIATYPE_FLOPPY_1_44;
+ else if (!strcmp(pszType, "Floppy 1.20"))
+ enmType = PDMMEDIATYPE_FLOPPY_1_20;
+ else if (!strcmp(pszType, "Floppy 720"))
+ enmType = PDMMEDIATYPE_FLOPPY_720;
+ else if (!strcmp(pszType, "Floppy 360"))
+ enmType = PDMMEDIATYPE_FLOPPY_360;
+ else if (!strcmp(pszType, "Floppy 15.6"))
+ enmType = PDMMEDIATYPE_FLOPPY_FAKE_15_6;
+ else if (!strcmp(pszType, "Floppy 63.5"))
+ enmType = PDMMEDIATYPE_FLOPPY_FAKE_63_5;
+
+ return enmType;
+}
+
+/**
+ * Converts PDMMEDIATYPE to the appropriate VDTYPE.
+ *
+ * @returns The VDTYPE.
+ * @param enmType The PDMMEDIATYPE to convert from.
+ */
+static VDTYPE drvvdGetVDFromMediaType(PDMMEDIATYPE enmType)
+{
+ if (PDMMEDIATYPE_IS_FLOPPY(enmType))
+ return VDTYPE_FLOPPY;
+ else if (enmType == PDMMEDIATYPE_DVD || enmType == PDMMEDIATYPE_CDROM)
+ return VDTYPE_OPTICAL_DISC;
+ else if (enmType == PDMMEDIATYPE_HARD_DISK)
+ return VDTYPE_HDD;
+
+ AssertMsgFailed(("Invalid media type %d{%s} given!\n", enmType, drvvdGetTypeName(enmType)));
+ return VDTYPE_HDD;
+}
+
+/**
+ * Registers statistics associated with the given media driver.
+ *
+ * @returns VBox status code.
+ * @param pThis The media driver instance.
+ */
+static int drvvdStatsRegister(PVBOXDISK pThis)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns;
+ uint32_t iInstance, iLUN;
+ const char *pcszController;
+
+ int rc = pThis->pDrvMediaPort->pfnQueryDeviceLocation(pThis->pDrvMediaPort, &pcszController,
+ &iInstance, &iLUN);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCtrlUpper = RTStrDup(pcszController);
+ if (pszCtrlUpper)
+ {
+ RTStrToUpper(pszCtrlUpper);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatQueryBufAttempts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT, "Number of attempts to query a direct buffer.",
+ "/Devices/%s%u/Port%u/QueryBufAttempts", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatQueryBufSuccess, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT, "Number of succeeded attempts to query a direct buffer.",
+ "/Devices/%s%u/Port%u/QueryBufSuccess", pszCtrlUpper, iInstance, iLUN);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data read.", "/Devices/%s%u/Port%u/ReadBytes", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data written.", "/Devices/%s%u/Port%u/WrittenBytes", pszCtrlUpper, iInstance, iLUN);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsSubmitted", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests failed.", "/Devices/%s%u/Port%u/ReqsFailed", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSucceeded, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests succeeded.", "/Devices/%s%u/Port%u/ReqsSucceeded", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFlush, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of flush I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsFlush", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsWrite, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of write I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsWrite", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of read I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsRead", pszCtrlUpper, iInstance, iLUN);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsDiscard, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of discard I/O requests submitted.", "/Devices/%s%u/Port%u/ReqsDiscard", pszCtrlUpper, iInstance, iLUN);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsPerSec, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of processed I/O requests per second.", "/Devices/%s%u/Port%u/ReqsPerSec",
+ pszCtrlUpper, iInstance, iLUN);
+
+ RTStrFree(pszCtrlUpper);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Deregisters statistics associated with the given media driver.
+ *
+ * @returns nothing.
+ * @param pThis The media driver instance.
+ */
+static void drvvdStatsDeregister(PVBOXDISK pThis)
+{
+ PPDMDRVINS pDrvIns = pThis->pDrvIns;
+
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatQueryBufAttempts);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatQueryBufSuccess);
+
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatBytesRead);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatBytesWritten);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsSubmitted);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsFailed);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsSucceeded);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsFlush);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsWrite);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsRead);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsDiscard);
+ PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->StatReqsPerSec);
+}
+
+
+/*********************************************************************************************************************************
+* Base interface methods *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvvdQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, &pThis->IMedia);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, pThis->fMountable ? &pThis->IMount : NULL);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, pThis->pDrvMediaExPort ? &pThis->IMediaEx : NULL);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* Saved state notification methods *
+*********************************************************************************************************************************/
+
+/**
+ * Load done callback for re-opening the image writable during teleportation.
+ *
+ * This is called both for successful and failed load runs, we only care about
+ * successful ones.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pSSM The saved state handle.
+ */
+static DECLCALLBACK(int) drvvdLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ Assert(!pThis->fErrorUseRuntime);
+
+ /* Drop out if we don't have any work to do or if it's a failed load. */
+ if ( !pThis->fTempReadOnly
+ || RT_FAILURE(SSMR3HandleGetStatus(pSSM)))
+ return VINF_SUCCESS;
+
+ int rc = drvvdSetWritable(pThis);
+ if (RT_FAILURE(rc)) /** @todo does the bugger set any errors? */
+ return SSMR3SetLoadError(pSSM, rc, RT_SRC_POS,
+ N_("Failed to write lock the images"));
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Driver methods *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for the power off or destruct callback.
+ *
+ * @returns nothing.
+ * @param pDrvIns The driver instance.
+ */
+static void drvvdPowerOffOrDestructOrUnmount(PPDMDRVINS pDrvIns)
+{
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ LogFlowFunc(("\n"));
+
+ RTSEMFASTMUTEX mutex;
+ ASMAtomicXchgHandle(&pThis->MergeCompleteMutex, NIL_RTSEMFASTMUTEX, &mutex);
+ if (mutex != NIL_RTSEMFASTMUTEX)
+ {
+ /* Request the semaphore to wait until a potentially running merge
+ * operation has been finished. */
+ int rc = RTSemFastMutexRequest(mutex);
+ AssertRC(rc);
+ pThis->fMergePending = false;
+ rc = RTSemFastMutexRelease(mutex);
+ AssertRC(rc);
+ rc = RTSemFastMutexDestroy(mutex);
+ AssertRC(rc);
+ }
+
+ if (RT_VALID_PTR(pThis->pBlkCache))
+ {
+ PDMR3BlkCacheRelease(pThis->pBlkCache);
+ pThis->pBlkCache = NULL;
+ }
+
+ if (RT_VALID_PTR(pThis->pRegionList))
+ {
+ VDRegionListFree(pThis->pRegionList);
+ pThis->pRegionList = NULL;
+ }
+
+ if (RT_VALID_PTR(pThis->pDisk))
+ {
+ VDDestroy(pThis->pDisk);
+ pThis->pDisk = NULL;
+ }
+ drvvdFreeImages(pThis);
+}
+
+/**
+ * @copydoc FNPDMDRVPOWEROFF
+ */
+static DECLCALLBACK(void) drvvdPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ drvvdPowerOffOrDestructOrUnmount(pDrvIns);
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVRESUME}
+ *
+ * VM resume notification that we use to undo what the temporary read-only image
+ * mode set by drvvdSuspend.
+ *
+ * Also switch to runtime error mode if we're resuming after a state load
+ * without having been powered on first.
+ *
+ * @todo The VMSetError vs VMSetRuntimeError mess must be fixed elsewhere,
+ * we're making assumptions about Main behavior here!
+ */
+static DECLCALLBACK(void) drvvdResume(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ drvvdSetWritable(pThis);
+ pThis->fSuspending = false;
+ pThis->fRedo = false;
+
+ if (pThis->pBlkCache)
+ {
+ int rc = PDMR3BlkCacheResume(pThis->pBlkCache);
+ AssertRC(rc);
+ }
+
+ if (pThis->pDrvMediaExPort)
+ {
+ /* Mark all requests waiting for I/O memory as active again so they get accounted for. */
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ PPDMMEDIAEXIOREQINT pIoReq, pIoReqNext;
+ RTListForEachSafe(&pThis->LstIoReqIoBufWait, pIoReq, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_ACTIVE);
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ LogFlowFunc(("Resumed I/O request %#p\n", pIoReq));
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+
+ /* Kick of any request we have to redo. */
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListForEachSafe(&pThis->LstIoReqRedo, pIoReq, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ int rc = VINF_SUCCESS;
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_SUSPENDED);
+
+ RTListNodeRemove(&pIoReq->NdLstWait);
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ LogFlowFunc(("Resuming I/O request %#p fXchg=%RTbool\n", pIoReq, fXchg));
+ if (fXchg)
+ {
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_ACTIVE);
+ LogFlowFunc(("Resumed I/O request %#p\n", pIoReq));
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ rc = drvvdMediaExIoReqReadWriteProcess(pThis, pIoReq, true /* fUpNotify */);
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ {
+ rc = drvvdMediaExIoReqFlushWrapper(pThis, pIoReq);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+ }
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ {
+ rc = drvvdMediaExIoReqDiscardWrapper(pThis, pIoReq);
+ if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ else if (rc == VINF_VD_ASYNC_IO_FINISHED)
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertMsgFailed(("Invalid request type %u\n", pIoReq->enmType));
+
+ /* The read write process will call the completion callback on its own. */
+ if ( rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS
+ && ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH))
+ {
+ Assert( ( pIoReq->enmType != PDMMEDIAEXIOREQTYPE_WRITE
+ && pIoReq->enmType != PDMMEDIAEXIOREQTYPE_READ)
+ || !pIoReq->ReadWrite.cbReqLeft
+ || RT_FAILURE(rc));
+ drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, rc, true /* fUpNotify */);
+ }
+
+ }
+ else
+ {
+ /* Request was canceled inbetween, so don't care and notify the owner about the completed request. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvvdMediaExIoReqCompleteWorker(pThis, pIoReq, VERR_PDM_MEDIAEX_IOREQ_CANCELED, true /* fUpNotify */);
+ }
+ }
+ Assert(RTListIsEmpty(&pThis->LstIoReqRedo));
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+ }
+
+ /* Try to process any requests waiting for I/O memory now. */
+ drvvdMediaExIoReqProcessWaiting(pThis);
+ pThis->fErrorUseRuntime = true;
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVSUSPEND}
+ *
+ * When the VM is being suspended, temporarily change to read-only image mode.
+ *
+ * This is important for several reasons:
+ * -# It makes sure that there are no pending writes to the image. Most
+ * backends implements this by closing and reopening the image in read-only
+ * mode.
+ * -# It allows Main to read the images during snapshotting without having
+ * to account for concurrent writes.
+ * -# This is essential for making teleportation targets sharing images work
+ * right. Both with regards to caching and with regards to file sharing
+ * locks (RTFILE_O_DENY_*). (See also drvvdLoadDone.)
+ */
+static DECLCALLBACK(void) drvvdSuspend(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ if (pThis->pBlkCache)
+ {
+ int rc = PDMR3BlkCacheSuspend(pThis->pBlkCache);
+ AssertRC(rc);
+ }
+
+ drvvdSetReadonly(pThis);
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVPOWERON}
+ */
+static DECLCALLBACK(void) drvvdPowerOn(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ drvvdSetWritable(pThis);
+ pThis->fErrorUseRuntime = true;
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVRESET}
+ */
+static DECLCALLBACK(void) drvvdReset(PPDMDRVINS pDrvIns)
+{
+ LogFlowFunc(("\n"));
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+
+ if (pThis->pBlkCache)
+ {
+ int rc = PDMR3BlkCacheClear(pThis->pBlkCache);
+ AssertRC(rc);
+ }
+
+ if (pThis->fBootAccelEnabled)
+ {
+ pThis->fBootAccelActive = true;
+ pThis->cbDataValid = 0;
+ pThis->offDisk = 0;
+ }
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT}
+ */
+static DECLCALLBACK(void) drvvdDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ LogFlowFunc(("\n"));
+
+ /*
+ * Make sure the block cache and disks are closed when this driver is
+ * destroyed. This method will get called without calling the power off
+ * callback first when we reconfigure the driver chain after a snapshot.
+ */
+ drvvdPowerOffOrDestructOrUnmount(pDrvIns);
+ if (pThis->MergeLock != NIL_RTSEMRW)
+ {
+ int rc = RTSemRWDestroy(pThis->MergeLock);
+ AssertRC(rc);
+ pThis->MergeLock = NIL_RTSEMRW;
+ }
+ if (pThis->pbData)
+ {
+ RTMemFree(pThis->pbData);
+ pThis->pbData = NULL;
+ }
+ if (pThis->pszBwGroup)
+ {
+ MMR3HeapFree(pThis->pszBwGroup);
+ pThis->pszBwGroup = NULL;
+ }
+ if (pThis->hHbdMgr != NIL_HBDMGR)
+ HBDMgrDestroy(pThis->hHbdMgr);
+ if (pThis->hIoReqCache != NIL_RTMEMCACHE)
+ RTMemCacheDestroy(pThis->hIoReqCache);
+ if (pThis->hIoBufMgr != NIL_IOBUFMGR)
+ IOBUFMgrDestroy(pThis->hIoBufMgr);
+ if (RTCritSectIsInitialized(&pThis->CritSectIoReqsIoBufWait))
+ RTCritSectDelete(&pThis->CritSectIoReqsIoBufWait);
+ if (RTCritSectIsInitialized(&pThis->CritSectIoReqRedo))
+ RTCritSectDelete(&pThis->CritSectIoReqRedo);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIoReqAllocBins); i++)
+ if (pThis->aIoReqAllocBins[i].hMtxLstIoReqAlloc != NIL_RTSEMFASTMUTEX)
+ RTSemFastMutexDestroy(pThis->aIoReqAllocBins[i].hMtxLstIoReqAlloc);
+
+ drvvdStatsDeregister(pThis);
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT,
+ * Construct a VBox disk media driver instance.}
+ */
+static DECLCALLBACK(int) drvvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ LogFlowFunc(("\n"));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ int rc = VINF_SUCCESS;
+ char *pszName = NULL; /* The path of the disk image file. */
+ char *pszFormat = NULL; /* The format backed to use for this image. */
+ char *pszCachePath = NULL; /* The path to the cache image. */
+ char *pszCacheFormat = NULL; /* The format backend to use for the cache image. */
+ bool fReadOnly = false; /* True if the media is read-only. */
+ bool fMaybeReadOnly = false; /* True if the media may or may not be read-only. */
+ bool fHonorZeroWrites = false; /* True if zero blocks should be written. */
+
+ /*
+ * Init the static parts.
+ */
+ pDrvIns->IBase.pfnQueryInterface = drvvdQueryInterface;
+ pThis->pDrvIns = pDrvIns;
+ pThis->fTempReadOnly = false;
+ pThis->pDisk = NULL;
+ pThis->fAsyncIOSupported = false;
+ pThis->fShareable = false;
+ pThis->fMergePending = false;
+ pThis->MergeCompleteMutex = NIL_RTSEMFASTMUTEX;
+ pThis->MergeLock = NIL_RTSEMRW;
+ pThis->uMergeSource = VD_LAST_IMAGE;
+ pThis->uMergeTarget = VD_LAST_IMAGE;
+ pThis->pCfgCrypto = NULL;
+ pThis->pIfSecKey = NULL;
+ pThis->hIoReqCache = NIL_RTMEMCACHE;
+ pThis->hIoBufMgr = NIL_IOBUFMGR;
+ pThis->pRegionList = NULL;
+ pThis->fSuspending = false;
+ pThis->fRedo = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIoReqAllocBins); i++)
+ pThis->aIoReqAllocBins[i].hMtxLstIoReqAlloc = NIL_RTSEMFASTMUTEX;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvvdRead;
+ pThis->IMedia.pfnReadPcBios = drvvdReadPcBios;
+ pThis->IMedia.pfnWrite = drvvdWrite;
+ pThis->IMedia.pfnFlush = drvvdFlush;
+ pThis->IMedia.pfnMerge = drvvdMerge;
+ pThis->IMedia.pfnSetSecKeyIf = drvvdSetSecKeyIf;
+ pThis->IMedia.pfnGetSize = drvvdGetSize;
+ pThis->IMedia.pfnGetSectorSize = drvvdGetSectorSize;
+ pThis->IMedia.pfnIsReadOnly = drvvdIsReadOnly;
+ pThis->IMedia.pfnIsNonRotational = drvvdIsNonRotational;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvvdBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvvdBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvvdBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvvdBiosSetLCHSGeometry;
+ pThis->IMedia.pfnBiosIsVisible = drvvdBiosIsVisible;
+ pThis->IMedia.pfnGetType = drvvdGetType;
+ pThis->IMedia.pfnGetUuid = drvvdGetUuid;
+ pThis->IMedia.pfnDiscard = drvvdDiscard;
+ pThis->IMedia.pfnSendCmd = NULL;
+ pThis->IMedia.pfnGetRegionCount = drvvdGetRegionCount;
+ pThis->IMedia.pfnQueryRegionProperties = drvvdQueryRegionProperties;
+ pThis->IMedia.pfnQueryRegionPropertiesForLba = drvvdQueryRegionPropertiesForLba;
+
+ /* IMount */
+ pThis->IMount.pfnUnmount = drvvdUnmount;
+ pThis->IMount.pfnIsMounted = drvvdIsMounted;
+ pThis->IMount.pfnLock = drvvdLock;
+ pThis->IMount.pfnUnlock = drvvdUnlock;
+ pThis->IMount.pfnIsLocked = drvvdIsLocked;
+
+ /* IMediaEx */
+ pThis->IMediaEx.pfnQueryFeatures = drvvdQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvvdNotifySuspend;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvvdIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvvdIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvvdIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvvdIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvvdIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvvdIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvvdIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvvdIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvvdIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvvdIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvvdIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqSendScsiCmd = drvvdIoReqSendScsiCmd;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvvdIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvvdIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvvdIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvvdIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvvdIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvvdIoReqSuspendedLoad;
+
+ /* Initialize supported VD interfaces. */
+ pThis->pVDIfsDisk = NULL;
+
+ pThis->VDIfError.pfnError = drvvdErrorCallback;
+ pThis->VDIfError.pfnMessage = NULL;
+ rc = VDInterfaceAdd(&pThis->VDIfError.Core, "DrvVD_VDIError", VDINTERFACETYPE_ERROR,
+ pDrvIns, sizeof(VDINTERFACEERROR), &pThis->pVDIfsDisk);
+ AssertRC(rc);
+
+ /* List of images is empty now. */
+ pThis->pImages = NULL;
+
+ pThis->pDrvMediaPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAPORT);
+ if (!pThis->pDrvMediaPort)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE,
+ N_("No media port interface above"));
+
+ pThis->pDrvMountNotify = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMOUNTNOTIFY);
+
+ /*
+ * Try to attach the optional extended media interface port above and initialize associated
+ * structures if available.
+ */
+ 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);
+ }
+
+ /* Before we access any VD API load all given plugins. */
+ rc = drvvdLoadPlugins(pCfg);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Loading VD plugins failed"));
+
+ /*
+ * Validate configuration and find all parent images.
+ * It's sort of up side down from the image dependency tree.
+ */
+ bool fHostIP = false;
+ bool fUseNewIo = false;
+ bool fUseBlockCache = false;
+ bool fDiscard = false;
+ bool fInformAboutZeroBlocks = false;
+ bool fSkipConsistencyChecks = false;
+ bool fEmptyDrive = false;
+ unsigned iLevel = 0;
+ PCFGMNODE pCurNode = pCfg;
+ uint32_t cbIoBufMax = 0;
+
+ for (;;)
+ {
+ bool fValid;
+
+ if (pCurNode == pCfg)
+ {
+ /* Toplevel configuration additionally contains the global image
+ * open flags. Some might be converted to per-image flags later. */
+ fValid = CFGMR3AreValuesValid(pCurNode,
+ "Format\0Path\0"
+ "ReadOnly\0MaybeReadOnly\0TempReadOnly\0Shareable\0HonorZeroWrites\0"
+ "HostIPStack\0UseNewIo\0BootAcceleration\0BootAccelerationBuffer\0"
+ "SetupMerge\0MergeSource\0MergeTarget\0BwGroup\0Type\0BlockCache\0"
+ "CachePath\0CacheFormat\0Discard\0InformAboutZeroBlocks\0"
+ "SkipConsistencyChecks\0"
+ "Locked\0BIOSVisible\0Cylinders\0Heads\0Sectors\0Mountable\0"
+ "EmptyDrive\0IoBufMax\0NonRotationalMedium\0"
+#if defined(VBOX_PERIODIC_FLUSH) || defined(VBOX_IGNORE_FLUSH)
+ "FlushInterval\0IgnoreFlush\0IgnoreFlushAsync\0"
+#endif /* !(VBOX_PERIODIC_FLUSH || VBOX_IGNORE_FLUSH) */
+ );
+ }
+ else
+ {
+ /* All other image configurations only contain image name and
+ * the format information. */
+ fValid = CFGMR3AreValuesValid(pCurNode, "Format\0Path\0"
+ "MergeSource\0MergeTarget\0");
+ }
+ if (!fValid)
+ {
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: keys incorrect at level %d"), iLevel);
+ break;
+ }
+
+ if (pCurNode == pCfg)
+ {
+ rc = CFGMR3QueryBoolDef(pCurNode, "HostIPStack", &fHostIP, true);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"HostIPStack\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "HonorZeroWrites", &fHonorZeroWrites, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"HonorZeroWrites\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "ReadOnly", &fReadOnly, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"ReadOnly\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "MaybeReadOnly", &fMaybeReadOnly, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"MaybeReadOnly\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "TempReadOnly", &pThis->fTempReadOnly, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"TempReadOnly\" as boolean failed"));
+ break;
+ }
+ if (fReadOnly && pThis->fTempReadOnly)
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Both \"ReadOnly\" and \"TempReadOnly\" are set"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "Shareable", &pThis->fShareable, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Shareable\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "UseNewIo", &fUseNewIo, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"UseNewIo\" as boolean failed"));
+ break;
+ }
+ rc = CFGMR3QueryBoolDef(pCurNode, "SetupMerge", &pThis->fMergePending, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"SetupMerge\" as boolean failed"));
+ break;
+ }
+ if (fReadOnly && pThis->fMergePending)
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Both \"ReadOnly\" and \"MergePending\" are set"));
+ break;
+ }
+ rc = CFGMR3QueryBoolDef(pCurNode, "BootAcceleration", &pThis->fBootAccelEnabled, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"BootAcceleration\" as boolean failed"));
+ break;
+ }
+ rc = CFGMR3QueryU32Def(pCurNode, "BootAccelerationBuffer", (uint32_t *)&pThis->cbBootAccelBuffer, 16 * _1K);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"BootAccelerationBuffer\" as integer failed"));
+ break;
+ }
+ rc = CFGMR3QueryBoolDef(pCurNode, "BlockCache", &fUseBlockCache, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"BlockCache\" as boolean failed"));
+ break;
+ }
+ rc = CFGMR3QueryStringAlloc(pCurNode, "BwGroup", &pThis->pszBwGroup);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"BwGroup\" as string failed"));
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+ rc = CFGMR3QueryBoolDef(pCurNode, "Discard", &fDiscard, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Discard\" as boolean failed"));
+ break;
+ }
+ if (fReadOnly && fDiscard)
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Both \"ReadOnly\" and \"Discard\" are set"));
+ break;
+ }
+ rc = CFGMR3QueryBoolDef(pCurNode, "InformAboutZeroBlocks", &fInformAboutZeroBlocks, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"InformAboutZeroBlocks\" as boolean failed"));
+ break;
+ }
+ rc = CFGMR3QueryBoolDef(pCurNode, "SkipConsistencyChecks", &fSkipConsistencyChecks, true);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"SKipConsistencyChecks\" as boolean failed"));
+ break;
+ }
+
+ char *psz = NULL;
+ rc = CFGMR3QueryStringAlloc(pCfg, "Type", &psz);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_BLOCK_NO_TYPE, N_("Failed to obtain the sub type"));
+ pThis->enmType = drvvdGetMediaTypeFromString(psz);
+ if (pThis->enmType == PDMMEDIATYPE_ERROR)
+ {
+ PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_BLOCK_UNKNOWN_TYPE, RT_SRC_POS,
+ N_("Unknown type \"%s\""), psz);
+ MMR3HeapFree(psz);
+ return VERR_PDM_BLOCK_UNKNOWN_TYPE;
+ }
+ MMR3HeapFree(psz); psz = NULL;
+
+ rc = CFGMR3QueryStringAlloc(pCurNode, "CachePath", &pszCachePath);
+ if (RT_FAILURE(rc) && rc != VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"CachePath\" as string failed"));
+ break;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (pszCachePath)
+ {
+ rc = CFGMR3QueryStringAlloc(pCurNode, "CacheFormat", &pszCacheFormat);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"CacheFormat\" as string failed"));
+ break;
+ }
+ }
+
+ /* Mountable */
+ rc = CFGMR3QueryBoolDef(pCfg, "Mountable", &pThis->fMountable, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Mountable\" from the config"));
+
+ /* Locked */
+ rc = CFGMR3QueryBoolDef(pCfg, "Locked", &pThis->fLocked, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Locked\" from the config"));
+
+ /* BIOS visible */
+ rc = CFGMR3QueryBoolDef(pCfg, "BIOSVisible", &pThis->fBiosVisible, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"BIOSVisible\" from the config"));
+
+ /* Cylinders */
+ rc = CFGMR3QueryU32Def(pCfg, "Cylinders", &pThis->LCHSGeometry.cCylinders, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Cylinders\" from the config"));
+
+ /* Heads */
+ rc = CFGMR3QueryU32Def(pCfg, "Heads", &pThis->LCHSGeometry.cHeads, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Heads\" from the config"));
+
+ /* Sectors */
+ rc = CFGMR3QueryU32Def(pCfg, "Sectors", &pThis->LCHSGeometry.cSectors, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Sectors\" from the config"));
+
+ /* Uuid */
+ rc = CFGMR3QueryStringAlloc(pCfg, "Uuid", &psz);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ RTUuidClear(&pThis->Uuid);
+ else if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidFromStr(&pThis->Uuid, psz);
+ if (RT_FAILURE(rc))
+ {
+ PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Uuid from string failed on \"%s\""), psz);
+ MMR3HeapFree(psz);
+ return rc;
+ }
+ MMR3HeapFree(psz); psz = NULL;
+ }
+ else
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Uuid\" from the config"));
+
+#ifdef VBOX_PERIODIC_FLUSH
+ rc = CFGMR3QueryU32Def(pCfg, "FlushInterval", &pThis->cbFlushInterval, 0);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"FlushInterval\" from the config"));
+#endif /* VBOX_PERIODIC_FLUSH */
+
+#ifdef VBOX_IGNORE_FLUSH
+ rc = CFGMR3QueryBoolDef(pCfg, "IgnoreFlush", &pThis->fIgnoreFlush, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IgnoreFlush\" from the config"));
+
+ if (pThis->fIgnoreFlush)
+ LogRel(("DrvVD: Flushes will be ignored\n"));
+ else
+ LogRel(("DrvVD: Flushes will be passed to the disk\n"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "IgnoreFlushAsync", &pThis->fIgnoreFlushAsync, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IgnoreFlushAsync\" from the config"));
+
+ if (pThis->fIgnoreFlushAsync)
+ LogRel(("DrvVD: Async flushes will be ignored\n"));
+ else
+ LogRel(("DrvVD: Async flushes will be passed to the disk\n"));
+#endif /* VBOX_IGNORE_FLUSH */
+
+ rc = CFGMR3QueryBoolDef(pCurNode, "EmptyDrive", &fEmptyDrive, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"EmptyDrive\" as boolean failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryU32Def(pCfg, "IoBufMax", &cbIoBufMax, 5 * _1M);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IoBufMax\" from the config"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "NonRotationalMedium", &pThis->fNonRotational, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD configuration error: Querying \"NonRotationalMedium\" as boolean failed"));
+ }
+
+ PCFGMNODE pParent = CFGMR3GetChild(pCurNode, "Parent");
+ if (!pParent)
+ break;
+ pCurNode = pParent;
+ iLevel++;
+ }
+
+ if (pThis->pDrvMediaExPort)
+ rc = IOBUFMgrCreate(&pThis->hIoBufMgr, cbIoBufMax, pThis->pCfgCrypto ? IOBUFMGR_F_REQUIRE_NOT_PAGABLE : IOBUFMGR_F_DEFAULT);
+
+ if ( !fEmptyDrive
+ && RT_SUCCESS(rc))
+ {
+ /*
+ * Create the image container and the necessary interfaces.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The image has a bandwidth group but the host cache is enabled.
+ * Use the async I/O framework but tell it to enable the host cache.
+ */
+ if (!fUseNewIo && pThis->pszBwGroup)
+ {
+ pThis->fAsyncIoWithHostCache = true;
+ fUseNewIo = true;
+ }
+
+ /** @todo quick hack to work around problems in the async I/O
+ * implementation (rw semaphore thread ownership problem)
+ * while a merge is running. Remove once this is fixed. */
+ if (pThis->fMergePending)
+ fUseNewIo = false;
+
+ if (RT_SUCCESS(rc) && pThis->fMergePending)
+ {
+ rc = RTSemFastMutexCreate(&pThis->MergeCompleteMutex);
+ if (RT_SUCCESS(rc))
+ rc = RTSemRWCreate(&pThis->MergeLock);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->VDIfThreadSync.pfnStartRead = drvvdThreadStartRead;
+ pThis->VDIfThreadSync.pfnFinishRead = drvvdThreadFinishRead;
+ pThis->VDIfThreadSync.pfnStartWrite = drvvdThreadStartWrite;
+ pThis->VDIfThreadSync.pfnFinishWrite = drvvdThreadFinishWrite;
+
+ rc = VDInterfaceAdd(&pThis->VDIfThreadSync.Core, "DrvVD_ThreadSync", VDINTERFACETYPE_THREADSYNC,
+ pThis, sizeof(VDINTERFACETHREADSYNC), &pThis->pVDIfsDisk);
+ }
+ else
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Failed to create semaphores for \"MergePending\""));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = VDCreate(pThis->pVDIfsDisk, drvvdGetVDFromMediaType(pThis->enmType), &pThis->pDisk);
+ /* Error message is already set correctly. */
+ }
+ }
+
+ if (pThis->pDrvMediaExPort && fUseNewIo)
+ pThis->fAsyncIOSupported = true;
+
+ uint64_t tsStart = RTTimeNanoTS();
+
+ unsigned iImageIdx = 0;
+ while (pCurNode && RT_SUCCESS(rc))
+ {
+ /* Allocate per-image data. */
+ PVBOXIMAGE pImage = drvvdNewImage(pThis);
+ if (!pImage)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /*
+ * Read the image configuration.
+ */
+ rc = CFGMR3QueryStringAlloc(pCurNode, "Path", &pszName);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Path\" as string failed"));
+ break;
+ }
+
+ rc = CFGMR3QueryStringAlloc(pCurNode, "Format", &pszFormat);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Format\" as string failed"));
+ break;
+ }
+
+ bool fMergeSource;
+ rc = CFGMR3QueryBoolDef(pCurNode, "MergeSource", &fMergeSource, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"MergeSource\" as boolean failed"));
+ break;
+ }
+ if (fMergeSource)
+ {
+ if (pThis->uMergeSource == VD_LAST_IMAGE)
+ pThis->uMergeSource = iImageIdx;
+ else
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Multiple \"MergeSource\" occurrences"));
+ break;
+ }
+ }
+
+ bool fMergeTarget;
+ rc = CFGMR3QueryBoolDef(pCurNode, "MergeTarget", &fMergeTarget, false);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"MergeTarget\" as boolean failed"));
+ break;
+ }
+ if (fMergeTarget)
+ {
+ if (pThis->uMergeTarget == VD_LAST_IMAGE)
+ pThis->uMergeTarget = iImageIdx;
+ else
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Multiple \"MergeTarget\" occurrences"));
+ break;
+ }
+ }
+
+ PCFGMNODE pCfgVDConfig = CFGMR3GetChild(pCurNode, "VDConfig");
+ pImage->VDIfConfig.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ pImage->VDIfConfig.pfnQuerySize = drvvdCfgQuerySize;
+ pImage->VDIfConfig.pfnQuery = drvvdCfgQuery;
+ pImage->VDIfConfig.pfnQueryBytes = NULL;
+ rc = VDInterfaceAdd(&pImage->VDIfConfig.Core, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ pCfgVDConfig, sizeof(VDINTERFACECONFIG), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ /* Check VDConfig for encryption config. */
+ if (pCfgVDConfig)
+ pThis->pCfgCrypto = CFGMR3GetChild(pCfgVDConfig, "CRYPT");
+
+ if (pThis->pCfgCrypto)
+ {
+ /* Setup VDConfig interface for disk encryption support. */
+ pThis->VDIfCfg.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ pThis->VDIfCfg.pfnQuerySize = drvvdCfgQuerySize;
+ pThis->VDIfCfg.pfnQuery = drvvdCfgQuery;
+ pThis->VDIfCfg.pfnQueryBytes = NULL;
+
+ pThis->VDIfCrypto.pfnKeyRetain = drvvdCryptoKeyRetain;
+ pThis->VDIfCrypto.pfnKeyRelease = drvvdCryptoKeyRelease;
+ pThis->VDIfCrypto.pfnKeyStorePasswordRetain = drvvdCryptoKeyStorePasswordRetain;
+ pThis->VDIfCrypto.pfnKeyStorePasswordRelease = drvvdCryptoKeyStorePasswordRelease;
+ }
+
+ /* Unconditionally insert the TCPNET interface, don't bother to check
+ * if an image really needs it. Will be ignored. Since the TCPNET
+ * interface is per image we could make this more flexible in the
+ * future if we want to. */
+ /* Construct TCPNET callback table depending on the config. This is
+ * done unconditionally, as uninterested backends will ignore it. */
+ if (fHostIP)
+ {
+ pImage->VDIfTcpNet.pfnSocketCreate = drvvdTcpSocketCreate;
+ pImage->VDIfTcpNet.pfnSocketDestroy = drvvdTcpSocketDestroy;
+ pImage->VDIfTcpNet.pfnClientConnect = drvvdTcpClientConnect;
+ pImage->VDIfTcpNet.pfnIsClientConnected = drvvdTcpIsClientConnected;
+ pImage->VDIfTcpNet.pfnClientClose = drvvdTcpClientClose;
+ pImage->VDIfTcpNet.pfnSelectOne = drvvdTcpSelectOne;
+ pImage->VDIfTcpNet.pfnRead = drvvdTcpRead;
+ pImage->VDIfTcpNet.pfnWrite = drvvdTcpWrite;
+ pImage->VDIfTcpNet.pfnSgWrite = drvvdTcpSgWrite;
+ pImage->VDIfTcpNet.pfnReadNB = drvvdTcpReadNB;
+ pImage->VDIfTcpNet.pfnWriteNB = drvvdTcpWriteNB;
+ pImage->VDIfTcpNet.pfnSgWriteNB = drvvdTcpSgWriteNB;
+ pImage->VDIfTcpNet.pfnFlush = drvvdTcpFlush;
+ pImage->VDIfTcpNet.pfnSetSendCoalescing = drvvdTcpSetSendCoalescing;
+ pImage->VDIfTcpNet.pfnGetLocalAddress = drvvdTcpGetLocalAddress;
+ pImage->VDIfTcpNet.pfnGetPeerAddress = drvvdTcpGetPeerAddress;
+
+ /*
+ * There is a 15ms delay between receiving the data and marking the socket
+ * as readable on Windows XP which hurts async I/O performance of
+ * TCP backends badly. Provide a different select method without
+ * using poll on XP.
+ * This is only used on XP because it is not as efficient as the one using poll
+ * and all other Windows versions are working fine.
+ */
+ char szOS[64];
+ memset(szOS, 0, sizeof(szOS));
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, &szOS[0], sizeof(szOS));
+
+ if (RT_SUCCESS(rc) && !strncmp(szOS, "Windows XP", 10))
+ {
+ LogRel(("VD: Detected Windows XP, disabled poll based waiting for TCP\n"));
+ pImage->VDIfTcpNet.pfnSelectOneEx = drvvdTcpSelectOneExNoPoll;
+ }
+ else
+ pImage->VDIfTcpNet.pfnSelectOneEx = drvvdTcpSelectOneExPoll;
+
+ pImage->VDIfTcpNet.pfnPoke = drvvdTcpPoke;
+ }
+ else
+ {
+#ifndef VBOX_WITH_INIP
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: TCP over Internal Networking not compiled in"));
+#else /* VBOX_WITH_INIP */
+ pImage->VDIfTcpNet.pfnSocketCreate = drvvdINIPSocketCreate;
+ pImage->VDIfTcpNet.pfnSocketDestroy = drvvdINIPSocketDestroy;
+ pImage->VDIfTcpNet.pfnClientConnect = drvvdINIPClientConnect;
+ pImage->VDIfTcpNet.pfnClientClose = drvvdINIPClientClose;
+ pImage->VDIfTcpNet.pfnIsClientConnected = drvvdINIPIsClientConnected;
+ pImage->VDIfTcpNet.pfnSelectOne = drvvdINIPSelectOne;
+ pImage->VDIfTcpNet.pfnRead = drvvdINIPRead;
+ pImage->VDIfTcpNet.pfnWrite = drvvdINIPWrite;
+ pImage->VDIfTcpNet.pfnSgWrite = drvvdINIPSgWrite;
+ pImage->VDIfTcpNet.pfnFlush = drvvdINIPFlush;
+ pImage->VDIfTcpNet.pfnSetSendCoalescing = drvvdINIPSetSendCoalescing;
+ pImage->VDIfTcpNet.pfnGetLocalAddress = drvvdINIPGetLocalAddress;
+ pImage->VDIfTcpNet.pfnGetPeerAddress = drvvdINIPGetPeerAddress;
+ pImage->VDIfTcpNet.pfnSelectOneEx = drvvdINIPSelectOneEx;
+ pImage->VDIfTcpNet.pfnPoke = drvvdINIPPoke;
+#endif /* VBOX_WITH_INIP */
+ }
+ rc = VDInterfaceAdd(&pImage->VDIfTcpNet.Core, "DrvVD_TCPNET",
+ VDINTERFACETYPE_TCPNET, NULL,
+ sizeof(VDINTERFACETCPNET), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ /* Insert the custom I/O interface only if we're told to use new IO.
+ * Since the I/O interface is per image we could make this more
+ * flexible in the future if we want to. */
+ if (fUseNewIo)
+ {
+#ifdef VBOX_WITH_PDM_ASYNC_COMPLETION
+ pImage->VDIfIo.pfnOpen = drvvdAsyncIOOpen;
+ pImage->VDIfIo.pfnClose = drvvdAsyncIOClose;
+ pImage->VDIfIo.pfnGetSize = drvvdAsyncIOGetSize;
+ pImage->VDIfIo.pfnSetSize = drvvdAsyncIOSetSize;
+ pImage->VDIfIo.pfnSetAllocationSize = drvvdAsyncIOSetAllocationSize;
+ pImage->VDIfIo.pfnReadSync = drvvdAsyncIOReadSync;
+ pImage->VDIfIo.pfnWriteSync = drvvdAsyncIOWriteSync;
+ pImage->VDIfIo.pfnFlushSync = drvvdAsyncIOFlushSync;
+ pImage->VDIfIo.pfnReadAsync = drvvdAsyncIOReadAsync;
+ pImage->VDIfIo.pfnWriteAsync = drvvdAsyncIOWriteAsync;
+ pImage->VDIfIo.pfnFlushAsync = drvvdAsyncIOFlushAsync;
+#else /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: Async Completion Framework not compiled in"));
+#endif /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
+ if (RT_SUCCESS(rc))
+ rc = VDInterfaceAdd(&pImage->VDIfIo.Core, "DrvVD_IO", VDINTERFACETYPE_IO,
+ pThis, sizeof(VDINTERFACEIO), &pImage->pVDIfsImage);
+ AssertRC(rc);
+ }
+
+ /*
+ * Open the image.
+ */
+ unsigned uOpenFlags;
+ if (fReadOnly || pThis->fTempReadOnly || iLevel != 0)
+ uOpenFlags = VD_OPEN_FLAGS_READONLY;
+ else
+ uOpenFlags = VD_OPEN_FLAGS_NORMAL;
+ if (fHonorZeroWrites)
+ uOpenFlags |= VD_OPEN_FLAGS_HONOR_ZEROES;
+ if (pThis->fAsyncIOSupported)
+ uOpenFlags |= VD_OPEN_FLAGS_ASYNC_IO;
+ if (pThis->fShareable)
+ uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE;
+ if (fDiscard && iLevel == 0)
+ uOpenFlags |= VD_OPEN_FLAGS_DISCARD;
+ if (fInformAboutZeroBlocks)
+ uOpenFlags |= VD_OPEN_FLAGS_INFORM_ABOUT_ZERO_BLOCKS;
+ if ( (uOpenFlags & VD_OPEN_FLAGS_READONLY)
+ && fSkipConsistencyChecks)
+ uOpenFlags |= VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS;
+
+ /* Try to open backend in async I/O mode first. */
+ rc = VDOpen(pThis->pDisk, pszFormat, pszName, uOpenFlags, pImage->pVDIfsImage);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ pThis->fAsyncIOSupported = false;
+ uOpenFlags &= ~VD_OPEN_FLAGS_ASYNC_IO;
+ rc = VDOpen(pThis->pDisk, pszFormat, pszName, uOpenFlags, pImage->pVDIfsImage);
+ }
+
+ if (rc == VERR_VD_DISCARD_NOT_SUPPORTED)
+ {
+ fDiscard = false;
+ uOpenFlags &= ~VD_OPEN_FLAGS_DISCARD;
+ rc = VDOpen(pThis->pDisk, pszFormat, pszName, uOpenFlags, pImage->pVDIfsImage);
+ }
+
+ if (!fDiscard)
+ {
+ pThis->IMedia.pfnDiscard = NULL;
+ pThis->IMediaEx.pfnIoReqDiscard = NULL;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("%d - Opened '%s' in %s mode\n",
+ iLevel, pszName,
+ VDIsReadOnly(pThis->pDisk) ? "read-only" : "read-write"));
+ if ( VDIsReadOnly(pThis->pDisk)
+ && !fReadOnly
+ && !fMaybeReadOnly
+ && !pThis->fTempReadOnly
+ && iLevel == 0)
+ {
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_VD_IMAGE_READ_ONLY, RT_SRC_POS,
+ N_("Failed to open image '%s' for writing due to wrong permissions"),
+ pszName);
+ break;
+ }
+ }
+ else
+ {
+ rc = PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Failed to open image '%s' in %s mode"), pszName,
+ (uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "read-only" : "read-write");
+ break;
+ }
+
+ MMR3HeapFree(pszName);
+ pszName = NULL;
+ MMR3HeapFree(pszFormat);
+ pszFormat = NULL;
+
+ /* next */
+ iLevel--;
+ iImageIdx++;
+ pCurNode = CFGMR3GetParent(pCurNode);
+ }
+
+ LogRel(("VD: Opening the disk took %lld ns\n", RTTimeNanoTS() - tsStart));
+
+ /* Open the cache image if set. */
+ if ( RT_SUCCESS(rc)
+ && RT_VALID_PTR(pszCachePath))
+ {
+ /* Insert the custom I/O interface only if we're told to use new IO.
+ * Since the I/O interface is per image we could make this more
+ * flexible in the future if we want to. */
+ if (fUseNewIo)
+ {
+#ifdef VBOX_WITH_PDM_ASYNC_COMPLETION
+ pThis->VDIfIoCache.pfnOpen = drvvdAsyncIOOpen;
+ pThis->VDIfIoCache.pfnClose = drvvdAsyncIOClose;
+ pThis->VDIfIoCache.pfnGetSize = drvvdAsyncIOGetSize;
+ pThis->VDIfIoCache.pfnSetSize = drvvdAsyncIOSetSize;
+ pThis->VDIfIoCache.pfnReadSync = drvvdAsyncIOReadSync;
+ pThis->VDIfIoCache.pfnWriteSync = drvvdAsyncIOWriteSync;
+ pThis->VDIfIoCache.pfnFlushSync = drvvdAsyncIOFlushSync;
+ pThis->VDIfIoCache.pfnReadAsync = drvvdAsyncIOReadAsync;
+ pThis->VDIfIoCache.pfnWriteAsync = drvvdAsyncIOWriteAsync;
+ pThis->VDIfIoCache.pfnFlushAsync = drvvdAsyncIOFlushAsync;
+#else /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
+ rc = PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
+ RT_SRC_POS, N_("DrvVD: Configuration error: Async Completion Framework not compiled in"));
+#endif /* !VBOX_WITH_PDM_ASYNC_COMPLETION */
+ if (RT_SUCCESS(rc))
+ rc = VDInterfaceAdd(&pThis->VDIfIoCache.Core, "DrvVD_IO", VDINTERFACETYPE_IO,
+ pThis, sizeof(VDINTERFACEIO), &pThis->pVDIfsCache);
+ AssertRC(rc);
+ }
+
+ rc = VDCacheOpen(pThis->pDisk, pszCacheFormat, pszCachePath, VD_OPEN_FLAGS_NORMAL, pThis->pVDIfsCache);
+ if (RT_FAILURE(rc))
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc, N_("DrvVD: Could not open cache image"));
+ }
+
+ if (RT_VALID_PTR(pszCachePath))
+ MMR3HeapFree(pszCachePath);
+ if (RT_VALID_PTR(pszCacheFormat))
+ MMR3HeapFree(pszCacheFormat);
+
+ if ( RT_SUCCESS(rc)
+ && pThis->fMergePending
+ && ( pThis->uMergeSource == VD_LAST_IMAGE
+ || pThis->uMergeTarget == VD_LAST_IMAGE))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Inconsistent image merge data"));
+ }
+
+ /* Create the block cache if enabled. */
+ if ( fUseBlockCache
+ && !pThis->fShareable
+ && !fDiscard
+ && !pThis->pCfgCrypto /* Disk encryption disables the block cache for security reasons */
+ && RT_SUCCESS(rc))
+ {
+ /*
+ * We need a unique ID for the block cache (to identify the owner of data
+ * blocks in a saved state). UUIDs are not really suitable because
+ * there are image formats which don't support them. Furthermore it is
+ * possible that a new diff image was attached after a saved state
+ * which changes the UUID.
+ * However the device "name + device instance + LUN" triple the disk is
+ * attached to is always constant for saved states.
+ */
+ char *pszId = NULL;
+ uint32_t iInstance, iLUN;
+ const char *pcszController;
+
+ rc = pThis->pDrvMediaPort->pfnQueryDeviceLocation(pThis->pDrvMediaPort, &pcszController,
+ &iInstance, &iLUN);
+ if (RT_FAILURE(rc))
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Configuration error: Could not query device data"));
+ else
+ {
+ int cbStr = RTStrAPrintf(&pszId, "%s-%d-%d", pcszController, iInstance, iLUN);
+
+ if (cbStr > 0)
+ {
+ rc = PDMDrvHlpBlkCacheRetain(pDrvIns, &pThis->pBlkCache,
+ drvvdBlkCacheXferCompleteIoReq,
+ drvvdBlkCacheXferEnqueue,
+ drvvdBlkCacheXferEnqueueDiscard,
+ pszId);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ LogRel(("VD: Block cache is not supported\n"));
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertRC(rc);
+
+ RTStrFree(pszId);
+ }
+ else
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRIVER_INVALID_PROPERTIES,
+ N_("DrvVD: Out of memory when creating block cache"));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = drvvdSetupFilters(pThis, pCfg);
+
+ /*
+ * Register a load-done callback so we can undo TempReadOnly config before
+ * we get to drvvdResume. Automatically deregistered upon destruction.
+ */
+ if (RT_SUCCESS(rc))
+ rc = PDMDrvHlpSSMRegisterEx(pDrvIns, 0 /* version */, 0 /* cbGuess */,
+ NULL /*pfnLivePrep*/, NULL /*pfnLiveExec*/, NULL /*pfnLiveVote*/,
+ NULL /*pfnSavePrep*/, NULL /*pfnSaveExec*/, NULL /*pfnSaveDone*/,
+ NULL /*pfnDonePrep*/, NULL /*pfnLoadExec*/, drvvdLoadDone);
+
+ /* Setup the boot acceleration stuff if enabled. */
+ if (RT_SUCCESS(rc) && pThis->fBootAccelEnabled)
+ {
+ pThis->cbDisk = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ Assert(pThis->cbDisk > 0);
+ pThis->pbData = (uint8_t *)RTMemAllocZ(pThis->cbBootAccelBuffer);
+ if (pThis->pbData)
+ {
+ pThis->fBootAccelActive = true;
+ pThis->offDisk = 0;
+ pThis->cbDataValid = 0;
+ LogRel(("VD: Boot acceleration enabled\n"));
+ }
+ else
+ LogRel(("VD: Boot acceleration, out of memory, disabled\n"));
+ }
+
+ if ( RTUuidIsNull(&pThis->Uuid)
+ && pThis->enmType == PDMMEDIATYPE_HARD_DISK)
+ VDGetUuid(pThis->pDisk, 0, &pThis->Uuid);
+
+ /*
+ * Automatically upgrade the floppy drive if the specified one is too
+ * small to represent the whole boot time image. (We cannot do this later
+ * since the BIOS (and others) gets the info via CMOS.)
+ *
+ * This trick should make 2.88 images as well as the fake 15.6 and 63.5 MB
+ * images despite the hardcoded default 1.44 drive.
+ */
+ if ( PDMMEDIATYPE_IS_FLOPPY(pThis->enmType)
+ && pThis->pDisk)
+ {
+ uint64_t const cbFloppyImg = VDGetSize(pThis->pDisk, VD_LAST_IMAGE);
+ PDMMEDIATYPE const enmCfgType = pThis->enmType;
+ switch (enmCfgType)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_360:
+ if (cbFloppyImg > 40 * 2 * 9 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_720;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_720:
+ if (cbFloppyImg > 80 * 2 * 14 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_1_20;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_1_20:
+ if (cbFloppyImg > 80 * 2 * 20 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_1_44;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_1_44:
+ if (cbFloppyImg > 80 * 2 * 24 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_2_88;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_2_88:
+ if (cbFloppyImg > 80 * 2 * 48 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_FAKE_15_6;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_FAKE_15_6:
+ if (cbFloppyImg > 255 * 2 * 63 * 512)
+ pThis->enmType = PDMMEDIATYPE_FLOPPY_FAKE_63_5;
+ RT_FALL_THRU();
+ case PDMMEDIATYPE_FLOPPY_FAKE_63_5:
+ if (cbFloppyImg > 255 * 2 * 255 * 512)
+ LogRel(("Warning: Floppy image is larger that 63.5 MB! (%llu bytes)\n", cbFloppyImg));
+ break;
+ }
+ if (pThis->enmType != enmCfgType)
+ LogRel(("DrvVD: Automatically upgraded floppy drive from %s to %s to better support the %u byte image\n",
+ drvvdGetTypeName(enmCfgType), drvvdGetTypeName(pThis->enmType), cbFloppyImg));
+ }
+ } /* !fEmptyDrive */
+
+ if (RT_SUCCESS(rc))
+ drvvdStatsRegister(pThis);
+
+ if (RT_FAILURE(rc))
+ {
+ if (RT_VALID_PTR(pszName))
+ MMR3HeapFree(pszName);
+ if (RT_VALID_PTR(pszFormat))
+ MMR3HeapFree(pszFormat);
+ /* drvvdDestruct does the rest. */
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * VBox disk container media driver registration record.
+ */
+const PDMDRVREG g_DrvVD =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "VD",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Generic VBox disk media driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MEDIA,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(VBOXDISK),
+ /* pfnConstruct */
+ drvvdConstruct,
+ /* pfnDestruct */
+ drvvdDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ drvvdPowerOn,
+ /* pfnReset */
+ drvvdReset,
+ /* pfnSuspend */
+ drvvdSuspend,
+ /* pfnResume */
+ drvvdResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvvdPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/HBDMgmt-darwin.cpp b/src/VBox/Devices/Storage/HBDMgmt-darwin.cpp
new file mode 100644
index 00000000..fe569ed0
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-darwin.cpp
@@ -0,0 +1,534 @@
+/* $Id: HBDMgmt-darwin.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API - darwin specifics.
+ */
+
+/*
+ * Copyright (C) 2015-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/cdefs.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/once.h>
+#include <iprt/semaphore.h>
+#include <iprt/path.h>
+#include <iprt/thread.h>
+
+#include <DiskArbitration/DiskArbitration.h>
+
+#include "HBDMgmt.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Claimed block device state.
+ */
+typedef struct HBDMGRDEV
+{
+ /** List node. */
+ RTLISTNODE ListNode;
+ /** Handle to the DA Disk object. */
+ DADiskRef hDiskRef;
+} HBDMGRDEV;
+/** Pointer to a claimed block device. */
+typedef HBDMGRDEV *PHBDMGRDEV;
+
+/**
+ * Internal Host block device manager state.
+ */
+typedef struct HBDMGRINT
+{
+ /** Session handle to the DiskArbitration daemon. */
+ DASessionRef hSessionRef;
+ /** Runloop reference of the worker thread. */
+ CFRunLoopRef hRunLoopRef;
+ /** Runloop source for waking up the worker thread. */
+ CFRunLoopSourceRef hRunLoopSrcWakeRef;
+ /** List of claimed block devices. */
+ RTLISTANCHOR ListClaimed;
+ /** Fast mutex protecting the list. */
+ RTSEMFASTMUTEX hMtxList;
+ /** Event sempahore to signal callback completion. */
+ RTSEMEVENT hEvtCallback;
+ /** Thread processing DA events. */
+ RTTHREAD hThrdDAEvts;
+ /** Flag whether the thread should keep running. */
+ volatile bool fRunning;
+} HBDMGRINT;
+/** Pointer to an interal block device manager state. */
+typedef HBDMGRINT *PHBDMGRINT;
+
+/**
+ * Helper structure containing the arguments
+ * for the claim/unmount callbacks.
+ */
+typedef struct HBDMGRDACLBKARGS
+{
+ /** Pointer to the block device manager. */
+ PHBDMGRINT pThis;
+ /** The status code returned by the callback, after the operation completed. */
+ DAReturn rcDA;
+ /** A detailed error string in case of an error, can be NULL.
+ * Must be freed with RTStrFree(). */
+ char *pszErrDetail;
+} HBDMGRDACLBKARGS;
+typedef HBDMGRDACLBKARGS *PHBDMGRDACLBKARGS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Unclaims the given block device and frees its state removing it from the list.
+ *
+ * @returns nothing.
+ * @param pDev The block device to unclaim.
+ */
+static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
+{
+ DADiskUnclaim(pDev->hDiskRef);
+ CFRelease(pDev->hDiskRef);
+ RTListNodeRemove(&pDev->ListNode);
+ RTMemFree(pDev);
+}
+
+/**
+ * Returns the block device given by the filename if claimed or NULL.
+ *
+ * @returns Pointer to the claimed block device or NULL if not claimed.
+ * @param pThis The block device manager.
+ * @param pszFilename The name to look for.
+ */
+static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
+{
+ bool fFound = false;
+ const char *pszFilenameStripped = RTPathFilename(pszFilename);
+
+ AssertPtrReturn(pszFilenameStripped, NULL);
+
+ PHBDMGRDEV pIt;
+ RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
+ {
+ const char *pszBSDName = DADiskGetBSDName(pIt->hDiskRef);
+ if (!RTStrCmp(pszFilenameStripped, pszBSDName))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Converts a given DA return code to a VBox status code.
+ *
+ * @returns VBox status code.
+ * @param hReturn The status code returned by a DA API call.
+ */
+static int hbdMgrDAReturn2VBoxStatus(DAReturn hReturn)
+{
+ int rc = VERR_UNRESOLVED_ERROR;
+
+ switch (hReturn)
+ {
+ case kDAReturnBusy:
+ rc = VERR_RESOURCE_BUSY;
+ break;
+ case kDAReturnNotMounted:
+ case kDAReturnBadArgument:
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ case kDAReturnNotPermitted:
+ case kDAReturnNotPrivileged:
+ case kDAReturnExclusiveAccess:
+ rc = VERR_ACCESS_DENIED;
+ break;
+ case kDAReturnNoResources:
+ rc = VERR_NO_MEMORY;
+ break;
+ case kDAReturnNotFound:
+ rc = VERR_NOT_FOUND;
+ break;
+ case kDAReturnNotReady:
+ rc = VERR_TRY_AGAIN;
+ break;
+ case kDAReturnNotWritable:
+ rc = VERR_WRITE_PROTECT;
+ break;
+ case kDAReturnUnsupported:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ case kDAReturnError:
+ default:
+ rc = VERR_UNRESOLVED_ERROR;
+ }
+
+ return rc;
+}
+
+/**
+ * Implements the OS X callback DADiskClaimCallback.
+ *
+ * This notifies us that the async DADiskClaim()/DADiskUnmount call has
+ * completed.
+ *
+ * @param hDiskRef The disk that was attempted claimed / unmounted.
+ * @param hDissenterRef NULL on success, contains details on failure.
+ * @param pvContext Pointer to the return code variable.
+ */
+static void hbdMgrDACallbackComplete(DADiskRef hDiskRef, DADissenterRef hDissenterRef, void *pvContext)
+{
+ RT_NOREF(hDiskRef);
+ PHBDMGRDACLBKARGS pArgs = (PHBDMGRDACLBKARGS)pvContext;
+ pArgs->pszErrDetail = NULL;
+
+ if (!hDissenterRef)
+ pArgs->rcDA = kDAReturnSuccess;
+ else
+ {
+ CFStringRef hStrErr = DADissenterGetStatusString(hDissenterRef);
+ if (hStrErr)
+ {
+ const char *pszErrDetail = CFStringGetCStringPtr(hStrErr, kCFStringEncodingUTF8);
+ if (pszErrDetail)
+ pArgs->pszErrDetail = RTStrDup(pszErrDetail);
+ CFRelease(hStrErr);
+ }
+ pArgs->rcDA = DADissenterGetStatus(hDissenterRef);
+
+ }
+ RTSemEventSignal(pArgs->pThis->hEvtCallback);
+}
+
+/**
+ * Implements the OS X callback DADiskMountApprovalCallback.
+ *
+ * This notifies us about any attempt to mount a volume. If we claimed the
+ * volume or the complete disk containing the volume we will deny the attempt.
+ *
+ * @returns Reference to a DADissenter object which contains the result.
+ * @param hDiskRef The disk that is about to be mounted.
+ * @param pvContext Pointer to the block device manager.
+ */
+static DADissenterRef hbdMgrDAMountApprovalCallback(DADiskRef hDiskRef, void *pvContext)
+{
+ PHBDMGRINT pThis = (PHBDMGRINT)pvContext;
+ DADiskRef hDiskParentRef = DADiskCopyWholeDisk(hDiskRef);
+ const char *pszBSDName = DADiskGetBSDName(hDiskRef);
+ const char *pszBSDNameParent = hDiskParentRef ? DADiskGetBSDName(hDiskParentRef) : NULL;
+ DADissenterRef hDissenterRef = NULL;
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt;
+ RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
+ {
+ const char *pszBSDNameCur = DADiskGetBSDName(pIt->hDiskRef);
+ /*
+ * Prevent mounting any volume we have in use. This applies to the case
+ * where we have the whole disk occupied but a single volume is about to be
+ * mounted.
+ */
+ if ( !RTStrCmp(pszBSDNameCur, pszBSDName)
+ || ( pszBSDNameParent
+ && !RTStrCmp(pszBSDNameParent, pszBSDNameCur)))
+ {
+ CFStringRef hStrReason = CFStringCreateWithCString(kCFAllocatorDefault, "The disk is currently in use by VirtualBox and cannot be mounted", kCFStringEncodingUTF8);
+ hDissenterRef = DADissenterCreate(kCFAllocatorDefault, kDAReturnExclusiveAccess, hStrReason);
+ break;
+ }
+ }
+
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ if (hDiskParentRef)
+ CFRelease(hDiskParentRef);
+ return hDissenterRef;
+}
+
+
+/**
+ * Implements OS X callback CFRunLoopSourceContext::perform.
+ *
+ * Dummy handler for the wakeup source to kick the worker thread.
+ *
+ * @returns nothing.
+ * @param pInfo Opaque user data given during source creation, unused.
+ */
+static void hbdMgrDAPerformWakeup(void *pInfo)
+{
+ RT_NOREF(pInfo);
+}
+
+
+/**
+ * Worker function of the thread processing messages from the Disk Arbitration daemon.
+ *
+ * @returns IPRT status code.
+ * @param hThreadSelf The thread handle.
+ * @param pvUser Opaque user data, the block device manager instance.
+ */
+static DECLCALLBACK(int) hbdMgrDAWorker(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PHBDMGRINT pThis = (PHBDMGRINT)pvUser;
+
+ /* Provide the runloop reference. */
+ pThis->hRunLoopRef = CFRunLoopGetCurrent();
+ RTThreadUserSignal(hThreadSelf);
+
+ /* Add the wake source to our runloop so we get notified about state changes. */
+ CFRunLoopAddSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
+
+ /* Do what we are here for. */
+ while (ASMAtomicReadBool(&pThis->fRunning))
+ {
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, true);
+ }
+
+ /* Remove the wakeup source form our runloop. */
+ CFRunLoopRemoveSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
+
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
+{
+ AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
+
+ PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
+ if (RT_UNLIKELY(!pThis))
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+ RTListInit(&pThis->ListClaimed);
+ pThis->fRunning = true;
+ pThis->hSessionRef = DASessionCreate(kCFAllocatorDefault);
+ if (pThis->hSessionRef)
+ {
+ rc = RTSemFastMutexCreate(&pThis->hMtxList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventCreate(&pThis->hEvtCallback);
+ if (RT_SUCCESS(rc))
+ {
+ CFRunLoopSourceContext CtxRunLoopSource;
+ CtxRunLoopSource.version = 0;
+ CtxRunLoopSource.info = NULL;
+ CtxRunLoopSource.retain = NULL;
+ CtxRunLoopSource.release = NULL;
+ CtxRunLoopSource.copyDescription = NULL;
+ CtxRunLoopSource.equal = NULL;
+ CtxRunLoopSource.hash = NULL;
+ CtxRunLoopSource.schedule = NULL;
+ CtxRunLoopSource.cancel = NULL;
+ CtxRunLoopSource.perform = hbdMgrDAPerformWakeup;
+ pThis->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource);
+ if (CFRunLoopSourceIsValid(pThis->hRunLoopSrcWakeRef))
+ {
+ rc = RTThreadCreate(&pThis->hThrdDAEvts, hbdMgrDAWorker, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "HbdDA-Wrk");
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for the thread to start up and provide the runloop reference. */
+ rc = RTThreadUserWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ AssertPtr(pThis->hRunLoopRef);
+
+ DARegisterDiskMountApprovalCallback(pThis->hSessionRef, NULL, hbdMgrDAMountApprovalCallback, pThis);
+ DASessionScheduleWithRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
+ *phHbdMgr = pThis;
+ return VINF_SUCCESS;
+ }
+ CFRelease(pThis->hRunLoopSrcWakeRef);
+ }
+ }
+
+ RTSemFastMutexDestroy(pThis->hMtxList);
+ }
+
+ CFRelease(pThis->hSessionRef);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pThis);
+ return rc;
+}
+
+
+DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturnVoid(pThis);
+
+ /* Unregister the mount approval and DA session from the runloop. */
+ DASessionUnscheduleFromRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
+ DAUnregisterApprovalCallback(pThis->hSessionRef, (void *)hbdMgrDAMountApprovalCallback, pThis);
+
+ /* Kick the worker thread to exit. */
+ ASMAtomicXchgBool(&pThis->fRunning, false);
+ CFRunLoopSourceSignal(pThis->hRunLoopSrcWakeRef);
+ CFRunLoopWakeUp(pThis->hRunLoopRef);
+ int rcThrd = VINF_SUCCESS;
+ int rc = RTThreadWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT, &rcThrd);
+ AssertRC(rc); AssertRC(rcThrd);
+
+ CFRelease(pThis->hRunLoopSrcWakeRef);
+
+ /* Go through all claimed block devices and release them. */
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt, pItNext;
+ RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
+ {
+ hbdMgrDevUnclaim(pIt);
+ }
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ CFRelease(pThis->hSessionRef);
+ RTSemFastMutexDestroy(pThis->hMtxList);
+ RTSemEventDestroy(pThis->hEvtCallback);
+ RTMemFree(pThis);
+}
+
+
+DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
+{
+ bool fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/disk", sizeof("/dev/disk") - 1) == 0 ? true : false;
+ if (!fIsBlockDevice)
+ fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/rdisk", sizeof("/dev/rdisk") - 1) == 0 ? true : false;
+ return fIsBlockDevice;
+}
+
+
+DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (!pDev)
+ {
+ DADiskRef hDiskRef = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->hSessionRef, pszFilename);
+ if (hDiskRef)
+ {
+ HBDMGRDACLBKARGS CalllbackArgs;
+ CalllbackArgs.pThis = pThis;
+ CalllbackArgs.rcDA = kDAReturnSuccess;
+
+ /* Claim the device. */
+ DADiskClaim(hDiskRef, kDADiskClaimOptionDefault, NULL, NULL, hbdMgrDACallbackComplete, &CalllbackArgs);
+ rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
+ if ( RT_SUCCESS(rc)
+ && CalllbackArgs.rcDA == kDAReturnSuccess)
+ {
+ /* Unmount anything which might be mounted. */
+ DADiskUnmount(hDiskRef, kDADiskUnmountOptionWhole, hbdMgrDACallbackComplete, &CalllbackArgs);
+ rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
+ if ( RT_SUCCESS(rc)
+ && ( CalllbackArgs.rcDA == kDAReturnSuccess
+ || CalllbackArgs.rcDA == kDAReturnNotMounted))
+ {
+ pDev = (PHBDMGRDEV)RTMemAllocZ(sizeof(HBDMGRDEV));
+ if (RT_LIKELY(pDev))
+ {
+ pDev->hDiskRef = hDiskRef;
+ RTSemFastMutexRequest(pThis->hMtxList);
+ RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
+ RTSemFastMutexRelease(pThis->hMtxList);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
+ LogRel(("HBDMgrClaimBlockDevice: DADiskUnmount(\"%s\") failed with %Rrc (%s)\n",
+ pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
+ if (CalllbackArgs.pszErrDetail)
+ RTStrFree(CalllbackArgs.pszErrDetail);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
+ LogRel(("HBDMgrClaimBlockDevice: DADiskClaim(\"%s\") failed with %Rrc (%s)\n",
+ pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
+ if (CalllbackArgs.pszErrDetail)
+ RTStrFree(CalllbackArgs.pszErrDetail);
+ }
+ if (RT_FAILURE(rc))
+ CFRelease(hDiskRef);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (pDev)
+ hbdMgrDevUnclaim(pDev);
+ else
+ rc = VERR_NOT_FOUND;
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return rc;
+}
+
+
+DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, false);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return pIt ? true : false;
+}
diff --git a/src/VBox/Devices/Storage/HBDMgmt-generic.cpp b/src/VBox/Devices/Storage/HBDMgmt-generic.cpp
new file mode 100644
index 00000000..747488c2
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-generic.cpp
@@ -0,0 +1,60 @@
+/* $Id: HBDMgmt-generic.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#include <VBox/cdefs.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+
+#include "HBDMgmt.h"
+
+DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
+{
+ AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
+ *phHbdMgr = NIL_HBDMGR;
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
+{
+ NOREF(hHbdMgr);
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
+{
+ NOREF(pszFilename);
+ return false;
+}
+
+DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ NOREF(hHbdMgr);
+ NOREF(pszFilename);
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ NOREF(hHbdMgr);
+ NOREF(pszFilename);
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ NOREF(hHbdMgr);
+ NOREF(pszFilename);
+ return false;
+}
diff --git a/src/VBox/Devices/Storage/HBDMgmt-win.cpp b/src/VBox/Devices/Storage/HBDMgmt-win.cpp
new file mode 100644
index 00000000..f813580d
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-win.cpp
@@ -0,0 +1,563 @@
+/* $Id: HBDMgmt-win.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/cdefs.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/list.h>
+
+#include <iprt/nt/nt-and-windows.h>
+#include <iprt/win/windows.h>
+
+#include "HBDMgmt.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Claimed block device state.
+ */
+typedef struct HBDMGRDEV
+{
+ /** List node. */
+ RTLISTNODE ListNode;
+ /** The block device name. */
+ char *pszDevice;
+ /** Number of volumes for this block device. */
+ unsigned cVolumes;
+ /** Array of handle to the volumes for unmounting and taking it offline. */
+ HANDLE ahVolumes[1];
+} HBDMGRDEV;
+/** Pointer to a claimed block device. */
+typedef HBDMGRDEV *PHBDMGRDEV;
+
+/**
+ * Internal Host block device manager state.
+ */
+typedef struct HBDMGRINT
+{
+ /** List of claimed block devices. */
+ RTLISTANCHOR ListClaimed;
+ /** Fast mutex protecting the list. */
+ RTSEMFASTMUTEX hMtxList;
+} HBDMGRINT;
+/** Pointer to an interal block device manager state. */
+typedef HBDMGRINT *PHBDMGRINT;
+
+#define HBDMGR_NT_HARDDISK_START "\\Device\\Harddisk"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Unclaims the given block device and frees its state removing it from the list.
+ *
+ * @returns nothing.
+ * @param pDev The block device to unclaim.
+ */
+static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
+{
+ LogFlowFunc(("pDev=%p{%s} cVolumes=%u\n", pDev, pDev->pszDevice, pDev->cVolumes));
+
+ for (unsigned i = 0; i < pDev->cVolumes; i++)
+ {
+ DWORD dwReturned = 0;
+
+ LogFlowFunc(("Taking volume %u online\n", i));
+ BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (!bRet)
+ LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
+ pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
+
+ CloseHandle(pDev->ahVolumes[i]);
+ }
+
+ RTListNodeRemove(&pDev->ListNode);
+ RTStrFree(pDev->pszDevice);
+ RTMemFree(pDev);
+}
+
+/**
+ * Returns the block device given by the filename if claimed or NULL.
+ *
+ * @returns Pointer to the claimed block device or NULL if not claimed.
+ * @param pThis The block device manager.
+ * @param pszFilename The name to look for.
+ */
+static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
+{
+ bool fFound = false;
+
+ PHBDMGRDEV pIt;
+ RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
+ {
+ if (!RTStrCmp(pszFilename, pIt->pszDevice))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Queries the target in the NT namespace of the given symbolic link.
+ *
+ * @returns VBox status code.
+ * @param pwszLinkNt The symbolic link to query the target for.
+ * @param ppwszLinkTarget Where to store the link target in the NT namespace on success.
+ * Must be freed with RTUtf16Free().
+ */
+static int hbdMgrQueryNtLinkTarget(PRTUTF16 pwszLinkNt, PRTUTF16 *ppwszLinkTarget)
+{
+ int rc = VINF_SUCCESS;
+ HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
+ IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+ UNICODE_STRING NtName;
+
+ NtName.Buffer = (PWSTR)pwszLinkNt;
+ NtName.Length = (USHORT)(RTUtf16Len(pwszLinkNt) * sizeof(RTUTF16));
+ NtName.MaximumLength = NtName.Length + sizeof(RTUTF16);
+
+ OBJECT_ATTRIBUTES ObjAttr;
+ InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
+
+ NTSTATUS rcNt = NtOpenSymbolicLinkObject(&hFile, SYMBOLIC_LINK_QUERY, &ObjAttr);
+ if (NT_SUCCESS(rcNt))
+ {
+ UNICODE_STRING UniStr;
+ RTUTF16 awszBuf[1024];
+ RT_ZERO(awszBuf);
+ UniStr.Buffer = awszBuf;
+ UniStr.MaximumLength = sizeof(awszBuf);
+ rcNt = NtQuerySymbolicLinkObject(hFile, &UniStr, NULL);
+ if (NT_SUCCESS(rcNt))
+ {
+ *ppwszLinkTarget = RTUtf16Dup((PRTUTF16)UniStr.Buffer);
+ if (!*ppwszLinkTarget)
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+
+ CloseHandle(hFile);
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+
+ return rc;
+}
+
+/**
+ * Queries the harddisk volume device in the NT namespace for the given Win32
+ * block device path.
+ *
+ * @returns VBox status code.
+ * @param pwszDriveWin32 The Win32 path to the block device (e.g. "\\.\PhysicalDrive0" for example)
+ * @param ppwszDriveNt Where to store the NT path to the volume on success.
+ * Must be freed with RTUtf16Free().
+ */
+static int hbdMgrQueryNtName(PRTUTF16 pwszDriveWin32, PRTUTF16 *ppwszDriveNt)
+{
+ int rc = VINF_SUCCESS;
+ RTUTF16 awszFileNt[1024];
+
+ /*
+ * Make sure the path is at least 5 characters long so we can safely access
+ * the part following \\.\ below.
+ */
+ AssertReturn(RTUtf16Len(pwszDriveWin32) >= 5, VERR_INVALID_STATE);
+
+ RT_ZERO(awszFileNt);
+ RTUtf16CatAscii(awszFileNt, RT_ELEMENTS(awszFileNt), "\\??\\");
+ RTUtf16Cat(awszFileNt, RT_ELEMENTS(awszFileNt), &pwszDriveWin32[4]);
+
+ /* Make sure there is no trailing \ at the end or we will fail. */
+ size_t cwcPath = RTUtf16Len(awszFileNt);
+ if (awszFileNt[cwcPath - 1] == L'\\')
+ awszFileNt[cwcPath - 1] = L'\0';
+
+ return hbdMgrQueryNtLinkTarget(awszFileNt, ppwszDriveNt);
+}
+
+static int hbdMgrQueryAllMountpointsForDisk(PRTUTF16 pwszDiskNt, PRTUTF16 **ppapwszVolumes,
+ unsigned *pcVolumes)
+{
+ /*
+ * Try to get the symlink target for every partition, we will take the easy approach
+ * and just try to open every partition starting with \Device\Harddisk<N>\Partition1
+ * (0 is a special reserved partition linking to the complete disk).
+ *
+ * For every partition we get the target \Device\HarddiskVolume<N> and query all mountpoints
+ * with that.
+ */
+ int rc = VINF_SUCCESS;
+ char *pszDiskNt = NULL;
+ unsigned cVolumes = 0;
+ unsigned cVolumesMax = 10;
+ PRTUTF16 *papwszVolumes = (PRTUTF16 *)RTMemAllocZ(cVolumesMax * sizeof(PRTUTF16));
+
+ if (!papwszVolumes)
+ return VERR_NO_MEMORY;
+
+ rc = RTUtf16ToUtf8(pwszDiskNt, &pszDiskNt);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check that the path matches our expectation \Device\Harddisk<N>\DR<N>. */
+ if (!RTStrNCmp(pszDiskNt, HBDMGR_NT_HARDDISK_START, sizeof(HBDMGR_NT_HARDDISK_START) - 1))
+ {
+ uint32_t iDisk = 0;
+
+ rc = RTStrToUInt32Ex(pszDiskNt + sizeof(HBDMGR_NT_HARDDISK_START) - 1, NULL, 10, &iDisk);
+ if (RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS)
+ {
+ uint32_t iPart = 1;
+
+ /* Try to query all mount points for all partitions, the simple way. */
+ do
+ {
+ char aszDisk[1024];
+ RT_ZERO(aszDisk);
+
+ size_t cchWritten = RTStrPrintf(&aszDisk[0], sizeof(aszDisk), "\\Device\\Harddisk%u\\Partition%u", iDisk, iPart);
+ if (cchWritten < sizeof(aszDisk))
+ {
+ PRTUTF16 pwszDisk = NULL;
+ rc = RTStrToUtf16(&aszDisk[0], &pwszDisk);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwszTargetNt = NULL;
+
+ rc = hbdMgrQueryNtLinkTarget(pwszDisk, &pwszTargetNt);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVolumes == cVolumesMax)
+ {
+ /* Increase array of volumes. */
+ PRTUTF16 *papwszVolumesNew = (PRTUTF16 *)RTMemAllocZ((cVolumesMax + 10) * sizeof(PRTUTF16));
+ if (papwszVolumesNew)
+ {
+ cVolumesMax += 10;
+ papwszVolumes = papwszVolumesNew;
+ }
+ else
+ {
+ RTUtf16Free(pwszTargetNt);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cVolumes < cVolumesMax);
+ papwszVolumes[cVolumes++] = pwszTargetNt;
+ iPart++;
+ }
+ }
+ else if (rc == VERR_FILE_NOT_FOUND)
+ {
+ /* The partition does not exist, so stop trying. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ RTUtf16Free(pwszDisk);
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ } while (RT_SUCCESS(rc));
+ }
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ RTStrFree(pszDiskNt);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcVolumes = cVolumes;
+ *ppapwszVolumes = papwszVolumes;
+ LogFlowFunc(("rc=%Rrc cVolumes=%u ppapwszVolumes=%p\n", rc, cVolumes, papwszVolumes));
+ }
+ else
+ {
+ for (unsigned i = 0; i < cVolumes; i++)
+ RTUtf16Free(papwszVolumes[i]);
+
+ RTMemFree(papwszVolumes);
+ }
+
+ return rc;
+}
+
+static NTSTATUS hbdMgrNtCreateFileWrapper(PRTUTF16 pwszVolume, HANDLE *phVolume)
+{
+ HANDLE hVolume = RTNT_INVALID_HANDLE_VALUE;
+ IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+ UNICODE_STRING NtName;
+
+ NtName.Buffer = (PWSTR)pwszVolume;
+ NtName.Length = (USHORT)(RTUtf16Len(pwszVolume) * sizeof(RTUTF16));
+ NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
+
+ OBJECT_ATTRIBUTES ObjAttr;
+ InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
+
+ NTSTATUS rcNt = NtCreateFile(&hVolume,
+ FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
+ &ObjAttr,
+ &Ios,
+ NULL /* Allocation Size*/,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_OPEN,
+ FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL /*EaBuffer*/,
+ 0 /*EaLength*/);
+ if (NT_SUCCESS(rcNt))
+ rcNt = Ios.Status;
+
+ if (NT_SUCCESS(rcNt))
+ *phVolume = hVolume;
+
+ return rcNt;
+}
+
+DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
+{
+ AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
+
+ PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
+ if (RT_UNLIKELY(!pThis))
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+ RTListInit(&pThis->ListClaimed);
+
+ rc = RTSemFastMutexCreate(&pThis->hMtxList);
+ if (RT_SUCCESS(rc))
+ {
+ *phHbdMgr = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ return rc;
+}
+
+DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturnVoid(pThis);
+
+ /* Go through all claimed block devices and release them. */
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt, pItNext;
+ RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
+ {
+ hbdMgrDevUnclaim(pIt);
+ }
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ RTSemFastMutexDestroy(pThis->hMtxList);
+ RTMemFree(pThis);
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
+{
+ bool fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1) == 0 ? true : false;
+ if (!fIsBlockDevice)
+ fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\Harddisk", sizeof("\\\\.\\Harddisk") - 1) == 0 ? true : false;
+
+ LogFlowFunc(("returns %s -> %RTbool\n", pszFilename, fIsBlockDevice));
+ return fIsBlockDevice;
+}
+
+DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (!pDev)
+ {
+ PRTUTF16 pwszVolume = NULL;
+
+ rc = RTStrToUtf16(pszFilename, &pwszVolume);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwszVolNt = NULL;
+
+ rc = hbdMgrQueryNtName(pwszVolume, &pwszVolNt);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 *papwszVolumes = NULL;
+ unsigned cVolumes = 0;
+
+ /* Complete disks need to be handled differently. */
+ if (!RTStrNCmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1))
+ {
+ rc = hbdMgrQueryAllMountpointsForDisk(pwszVolNt, &papwszVolumes, &cVolumes);
+ RTUtf16Free(pwszVolNt);
+ }
+ else
+ {
+ papwszVolumes = &pwszVolNt;
+ cVolumes = 1;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ for (unsigned i = 0; i < cVolumes; i++)
+ LogFlowFunc(("Volume %u: %ls\n", i, papwszVolumes[i]));
+#endif
+ pDev = (PHBDMGRDEV)RTMemAllocZ(RT_UOFFSETOF_DYN(HBDMGRDEV, ahVolumes[cVolumes]));
+ if (pDev)
+ {
+ pDev->cVolumes = 0;
+ pDev->pszDevice = RTStrDup(pszFilename);
+ if (pDev->pszDevice)
+ {
+ for (unsigned i = 0; i < cVolumes; i++)
+ {
+ HANDLE hVolume;
+
+ NTSTATUS rcNt = hbdMgrNtCreateFileWrapper(papwszVolumes[i], &hVolume);
+ if (NT_SUCCESS(rcNt))
+ {
+ DWORD dwReturned = 0;
+
+ Assert(hVolume != INVALID_HANDLE_VALUE);
+ BOOL bRet = DeviceIoControl(hVolume, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (bRet)
+ {
+ bRet = DeviceIoControl(hVolume, IOCTL_VOLUME_OFFLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (bRet)
+ pDev->ahVolumes[pDev->cVolumes++] = hVolume;
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ if (RT_FAILURE(rc))
+ CloseHandle(hVolume);
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+ }
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ RTSemFastMutexRequest(pThis->hMtxList);
+ RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
+ RTSemFastMutexRelease(pThis->hMtxList);
+ }
+ else
+ {
+ /* Close all open handles and take the volumes online again. */
+ for (unsigned i = 0; i < pDev->cVolumes; i++)
+ {
+ DWORD dwReturned = 0;
+ BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (!bRet)
+ LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
+ pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
+
+ CloseHandle(pDev->ahVolumes[i]);
+ }
+ if (pDev->pszDevice)
+ RTStrFree(pDev->pszDevice);
+ RTMemFree(pDev);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ for (unsigned i = 0; i < cVolumes; i++)
+ RTUtf16Free(papwszVolumes[i]);
+
+ RTMemFree(papwszVolumes);
+ }
+ }
+
+ RTUtf16Free(pwszVolume);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+
+ return rc;
+}
+
+DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (pDev)
+ hbdMgrDevUnclaim(pDev);
+ else
+ rc = VERR_NOT_FOUND;
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return rc;
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, false);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return pIt ? true : false;
+}
+
diff --git a/src/VBox/Devices/Storage/HBDMgmt.h b/src/VBox/Devices/Storage/HBDMgmt.h
new file mode 100644
index 00000000..326f1638
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt.h
@@ -0,0 +1,94 @@
+/* $Id: HBDMgmt.h $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_HBDMgmt_h
+#define VBOX_INCLUDED_SRC_Storage_HBDMgmt_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Opaque host block device manager.
+ */
+typedef struct HBDMGRINT *HBDMGR;
+/** Pointer to a block device manager. */
+typedef HBDMGR *PHBDMGR;
+
+/* NIL HBD manager handle. */
+#define NIL_HBDMGR ((HBDMGR)0)
+
+/**
+ * Creates host block device manager.
+ *
+ * @returns VBox status code.
+ * @param phHbdMgr Where to store the handle to the block device manager on success.
+ */
+DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr);
+
+/**
+ * Destroys the given block device manager unclaiming all managed block devices.
+ *
+ * @returns nothing.
+ * @param hHbdMgr The block device manager.
+ */
+DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr);
+
+/**
+ * Returns whether a given filename resembles a block device which can
+ * be managed by this API.
+ *
+ * @returns true if the given filename point to a block device manageable
+ * by the given manager
+ * false otherwise.
+ * @param pszFilename The block device to check.
+ */
+DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename);
+
+/**
+ * Prepares the given block device for use by unmounting and claiming it for exclusive use.
+ *
+ * @returns VBox status code.
+ * @param hHbdMgr The block device manager.
+ * @param pszFilename The block device to claim.
+ */
+DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename);
+
+/**
+ * Unclaims the given block device.
+ *
+ * @returns VBox status code.
+ * @param hHbdMgr The block device manager.
+ * @param pszFilename The block device to unclaim.
+ */
+DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename);
+
+/**
+ * Returns whether the given block device is claimed by the manager.
+ *
+ * @returns true if the block device is claimed, false otherwisw.
+ * @param hHbdMgr The block device manager.
+ * @param pszFilename The block device to check.
+ */
+DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_HBDMgmt_h */
diff --git a/src/VBox/Devices/Storage/IOBufMgmt.cpp b/src/VBox/Devices/Storage/IOBufMgmt.cpp
new file mode 100644
index 00000000..04b1120f
--- /dev/null
+++ b/src/VBox/Devices/Storage/IOBufMgmt.cpp
@@ -0,0 +1,535 @@
+/* $Id: IOBufMgmt.cpp $ */
+/** @file
+ * VBox storage devices: I/O buffer management API.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_IOBUFMGMT
+#include <VBox/cdefs.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/sg.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+/** Set to verify the allocations for distinct memory areas. */
+//#define IOBUFMGR_VERIFY_ALLOCATIONS 1
+
+/** The minimum bin size to create - power of two!. */
+#define IOBUFMGR_BIN_SIZE_MIN _4K
+/** The maximum bin size to create - power of two!. */
+#define IOBUFMGR_BIN_SIZE_MAX _1M
+
+/** Pointer to the internal I/O buffer manager data. */
+typedef struct IOBUFMGRINT *PIOBUFMGRINT;
+
+/**
+ * Internal I/O buffer descriptor data.
+ */
+typedef struct IOBUFDESCINT
+{
+ /** Data segments. */
+ RTSGSEG aSegs[10];
+ /** Data segments used for the current allocation. */
+ unsigned cSegsUsed;
+ /** Pointer to I/O buffer manager. */
+ PIOBUFMGRINT pIoBufMgr;
+} IOBUFDESCINT;
+
+/**
+ * A
+ */
+typedef struct IOBUFMGRBIN
+{
+ /** Index of the next free entry. */
+ unsigned iFree;
+ /** Pointer to the array of free objects for this bin. */
+ void **papvFree;
+} IOBUFMGRBIN;
+typedef IOBUFMGRBIN *PIOBUFMGRBIN;
+
+/**
+ * Internal I/O buffer manager data.
+ */
+typedef struct IOBUFMGRINT
+{
+ /** Critical section protecting the allocation path. */
+ RTCRITSECT CritSectAlloc;
+ /** Flags the manager was created with. */
+ uint32_t fFlags;
+ /** Maximum size of I/O memory to allocate. */
+ size_t cbMax;
+ /** Amount of free memory. */
+ size_t cbFree;
+ /** The order of smallest bin. */
+ uint32_t u32OrderMin;
+ /** The order of largest bin. */
+ uint32_t u32OrderMax;
+ /** Pointer to the base memory of the allocation. */
+ void *pvMem;
+ /** Number of bins for free objects. */
+ uint32_t cBins;
+ /** Flag whether allocation is on hold waiting for everything to be free
+ * to be able to defragment the memory. */
+ bool fAllocSuspended;
+ /** Array of bins. */
+ PIOBUFMGRBIN paBins;
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ /** Pointer to the object state (allocated/free) bitmap. */
+ void *pbmObjState;
+#endif
+ /** Array of pointer entries for the various bins - variable in size. */
+ void *apvObj[1];
+} IOBUFMGRINT;
+
+/* Must be included after IOBUFDESCINT was defined. */
+#define IOBUFDESCINT_DECLARED
+#include "IOBufMgmt.h"
+
+/**
+ * Gets the number of bins required between the given minimum and maximum size
+ * to have a bin for every power of two size inbetween.
+ *
+ * @returns The number of bins required.
+ * @param cbMin The size of the smallest bin.
+ * @param cbMax The size of the largest bin.
+ */
+DECLINLINE(uint32_t) iobufMgrGetBinCount(uint32_t cbMin, uint32_t cbMax)
+{
+ uint32_t u32Max = ASMBitLastSetU32(cbMax);
+ uint32_t u32Min = ASMBitLastSetU32(cbMin);
+
+ Assert(cbMax >= cbMin && cbMax != 0 && cbMin != 0);
+ return u32Max - u32Min + 1;
+}
+
+/**
+ * Returns the number of entries required in the object array to cover all bins.
+ *
+ * @returns Number of entries required in the object array.
+ * @param cbMem Size of the memory buffer.
+ * @param cBins Number of bins available.
+ * @param cbMinBin Minimum object size.
+ */
+DECLINLINE(uint32_t) iobufMgrGetObjCount(size_t cbMem, unsigned cBins, size_t cbMinBin)
+{
+ size_t cObjs = 0;
+ size_t cbBin = cbMinBin;
+
+ while (cBins-- > 0)
+ {
+ cObjs += cbMem / cbBin;
+ cbBin <<= 1; /* The size doubles for every bin. */
+ }
+
+ Assert((uint32_t)cObjs == cObjs);
+ return (uint32_t)cObjs;
+}
+
+DECLINLINE(void) iobufMgrBinObjAdd(PIOBUFMGRBIN pBin, void *pvObj)
+{
+ LogFlowFunc(("pBin=%#p{.iFree=%u} pvObj=%#p\n", pBin, pBin->iFree, pvObj));
+ AssertPtr(pvObj);
+ pBin->papvFree[pBin->iFree] = pvObj;
+ pBin->iFree++;
+ LogFlowFunc(("return pBin=%#p{.iFree=%u}\n", pBin, pBin->iFree));
+}
+
+DECLINLINE(void *) iobufMgrBinObjRemove(PIOBUFMGRBIN pBin)
+{
+ LogFlowFunc(("pBin=%#p{.iFree=%u}\n", pBin, pBin->iFree));
+ Assert(pBin->iFree > 0);
+
+ pBin->iFree--;
+ void *pvObj = pBin->papvFree[pBin->iFree];
+ AssertPtr(pvObj);
+
+ LogFlowFunc(("returns pvObj=%#p pBin=%#p{.iFree=%u}\n", pvObj, pBin, pBin->iFree));
+ return pvObj;
+}
+
+/**
+ * Resets the bins to factory default (memory resigin in the largest bin).
+ *
+ * @returns nothing.
+ * @param pThis The I/O buffer manager instance.
+ */
+static void iobufMgrResetBins(PIOBUFMGRINT pThis)
+{
+ /* Init the bins. */
+ size_t cbMax = pThis->cbMax;
+ size_t iObj = 0;
+ uint32_t cbBin = IOBUFMGR_BIN_SIZE_MIN;
+ for (unsigned i = 0; i < pThis->cBins; i++)
+ {
+ PIOBUFMGRBIN pBin = &pThis->paBins[i];
+ pBin->iFree = 0;
+ pBin->papvFree = &pThis->apvObj[iObj];
+ iObj += cbMax / cbBin;
+
+ /* Init the biggest possible bin with the free objects. */
+ if ( (cbBin << 1) > cbMax
+ || i == pThis->cBins - 1)
+ {
+ uint8_t *pbMem = (uint8_t *)pThis->pvMem;
+ while (cbMax)
+ {
+ iobufMgrBinObjAdd(pBin, pbMem);
+ cbMax -= cbBin;
+ pbMem += cbBin;
+
+ if (cbMax < cbBin) /** @todo Populate smaller bins and don't waste memory. */
+ break;
+ }
+
+ /* Limit the number of available bins. */
+ pThis->cBins = i + 1;
+ break;
+ }
+
+ cbBin <<= 1;
+ }
+}
+
+/**
+ * Allocate one segment from the manager.
+ *
+ * @returns Number of bytes allocated, 0 if there is no free memory.
+ * @param pThis The I/O buffer manager instance.
+ * @param pSeg The segment to fill in on success.
+ * @param cb Maximum number of bytes to allocate.
+ */
+static size_t iobufMgrAllocSegment(PIOBUFMGRINT pThis, PRTSGSEG pSeg, size_t cb)
+{
+ size_t cbAlloc = 0;
+
+ /* Round to the next power of two and get the bin to try first. */
+ uint32_t u32Order = ASMBitLastSetU32((uint32_t)cb) - 1;
+ if (cb & (RT_BIT_32(u32Order) - 1))
+ u32Order++;
+
+ u32Order = RT_CLAMP(u32Order, pThis->u32OrderMin, pThis->u32OrderMax);
+ unsigned iBin = u32Order - pThis->u32OrderMin;
+
+ /*
+ * Check whether the bin can satisfy the request. If not try the next bigger
+ * bin and so on. If there is nothing to find try the smaller bins.
+ */
+ Assert(iBin < pThis->cBins);
+
+ PIOBUFMGRBIN pBin = &pThis->paBins[iBin];
+ /* Reset the bins if there is nothing in the current one but all the memory is marked as free. */
+ if ( pThis->cbFree == pThis->cbMax
+ && pBin->iFree == 0)
+ iobufMgrResetBins(pThis);
+
+ if (pBin->iFree == 0)
+ {
+ unsigned iBinCur = iBin;
+ PIOBUFMGRBIN pBinCur = &pThis->paBins[iBinCur];
+
+ while (iBinCur < pThis->cBins)
+ {
+ if (pBinCur->iFree != 0)
+ {
+ uint8_t *pbMem = (uint8_t *)iobufMgrBinObjRemove(pBinCur);
+ AssertPtr(pbMem);
+
+ /* Always split into half. */
+ while (iBinCur > iBin)
+ {
+ iBinCur--;
+ pBinCur = &pThis->paBins[iBinCur];
+ iobufMgrBinObjAdd(pBinCur, pbMem + (size_t)RT_BIT(iBinCur + pThis->u32OrderMin)); /* (RT_BIT causes weird MSC warning without cast) */
+ }
+
+ /* For the last bin we will get two new memory blocks. */
+ iobufMgrBinObjAdd(pBinCur, pbMem);
+ Assert(pBin == pBinCur);
+ break;
+ }
+
+ pBinCur++;
+ iBinCur++;
+ }
+ }
+
+ /*
+ * If we didn't find something in the higher bins try to accumulate as much as possible from the smaller bins.
+ */
+ if ( pBin->iFree == 0
+ && iBin > 0)
+ {
+#if 1
+ pThis->fAllocSuspended = true;
+#else
+ do
+ {
+ iBin--;
+ pBin = &pThis->paBins[iBin];
+
+ if (pBin->iFree != 0)
+ {
+ pBin->iFree--;
+ pSeg->pvSeg = pBin->papvFree[pBin->iFree];
+ pSeg->cbSeg = (size_t)RT_BIT_32(iBin + pThis->u32OrderMin);
+ AssertPtr(pSeg->pvSeg);
+ cbAlloc = pSeg->cbSeg;
+ break;
+ }
+ }
+ while (iBin > 0);
+#endif
+ }
+ else if (pBin->iFree != 0)
+ {
+ pSeg->pvSeg = iobufMgrBinObjRemove(pBin);
+ pSeg->cbSeg = (size_t)RT_BIT_32(u32Order);
+ cbAlloc = pSeg->cbSeg;
+ AssertPtr(pSeg->pvSeg);
+
+ pThis->cbFree -= cbAlloc;
+
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ /* Mark the objects as allocated. */
+ uint32_t iBinStart = ((uintptr_t)pSeg->pvSeg - (uintptr_t)pThis->pvMem) / IOBUFMGR_BIN_SIZE_MIN;
+ Assert( !(((uintptr_t)pSeg->pvSeg - (uintptr_t)pThis->pvMem) % IOBUFMGR_BIN_SIZE_MIN)
+ && !(pSeg->cbSeg % IOBUFMGR_BIN_SIZE_MIN));
+ uint32_t iBinEnd = iBinStart + (pSeg->cbSeg / IOBUFMGR_BIN_SIZE_MIN);
+ while (iBinStart < iBinEnd)
+ {
+ bool fState = ASMBitTestAndSet(pThis->pbmObjState, iBinStart);
+ //LogFlowFunc(("iBinStart=%u fState=%RTbool -> true\n", iBinStart, fState));
+ AssertMsg(!fState, ("Trying to allocate an already allocated object\n"));
+ iBinStart++;
+ }
+#endif
+ }
+
+ return cbAlloc;
+}
+
+DECLHIDDEN(int) IOBUFMgrCreate(PIOBUFMGR phIoBufMgr, size_t cbMax, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(phIoBufMgr, VERR_INVALID_POINTER);
+ AssertReturn(cbMax, VERR_NOT_IMPLEMENTED);
+
+ /* Allocate the basic structure in one go. */
+ unsigned cBins = iobufMgrGetBinCount(IOBUFMGR_BIN_SIZE_MIN, IOBUFMGR_BIN_SIZE_MAX);
+ uint32_t cObjs = iobufMgrGetObjCount(cbMax, cBins, IOBUFMGR_BIN_SIZE_MIN);
+ PIOBUFMGRINT pThis = (PIOBUFMGRINT)RTMemAllocZ(RT_UOFFSETOF_DYN(IOBUFMGRINT, apvObj[cObjs]) + cBins * sizeof(IOBUFMGRBIN));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->fFlags = fFlags;
+ pThis->cbMax = cbMax;
+ pThis->cbFree = cbMax;
+ pThis->cBins = cBins;
+ pThis->fAllocSuspended = false;
+ pThis->u32OrderMin = ASMBitLastSetU32(IOBUFMGR_BIN_SIZE_MIN) - 1;
+ pThis->u32OrderMax = ASMBitLastSetU32(IOBUFMGR_BIN_SIZE_MAX) - 1;
+ pThis->paBins = (PIOBUFMGRBIN)((uint8_t *)pThis + RT_UOFFSETOF_DYN(IOBUFMGRINT, apvObj[cObjs]));
+
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ pThis->pbmObjState = RTMemAllocZ((cbMax / IOBUFMGR_BIN_SIZE_MIN / 8) + 1);
+ if (!pThis->pbmObjState)
+ rc = VERR_NO_MEMORY;
+#endif
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSectAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->fFlags & IOBUFMGR_F_REQUIRE_NOT_PAGABLE)
+ rc = RTMemSaferAllocZEx(&pThis->pvMem, RT_ALIGN_Z(pThis->cbMax, _4K),
+ RTMEMSAFER_F_REQUIRE_NOT_PAGABLE);
+ else
+ pThis->pvMem = RTMemPageAllocZ(RT_ALIGN_Z(pThis->cbMax, _4K));
+
+ if ( RT_LIKELY(pThis->pvMem)
+ && RT_SUCCESS(rc))
+ {
+ iobufMgrResetBins(pThis);
+
+ *phIoBufMgr = pThis;
+ return VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTCritSectDelete(&pThis->CritSectAlloc);
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+DECLHIDDEN(int) IOBUFMgrDestroy(IOBUFMGR hIoBufMgr)
+{
+ PIOBUFMGRINT pThis = hIoBufMgr;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ int rc = RTCritSectEnter(&pThis->CritSectAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cbFree == pThis->cbMax)
+ {
+ if (pThis->fFlags & IOBUFMGR_F_REQUIRE_NOT_PAGABLE)
+ RTMemSaferFree(pThis->pvMem, RT_ALIGN_Z(pThis->cbMax, _4K));
+ else
+ RTMemPageFree(pThis->pvMem, RT_ALIGN_Z(pThis->cbMax, _4K));
+
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ AssertPtr(pThis->pbmObjState);
+ RTMemFree(pThis->pbmObjState);
+ pThis->pbmObjState = NULL;
+#endif
+
+ RTCritSectLeave(&pThis->CritSectAlloc);
+ RTCritSectDelete(&pThis->CritSectAlloc);
+ RTMemFree(pThis);
+ }
+ else
+ {
+ rc = VERR_INVALID_STATE;
+ RTCritSectLeave(&pThis->CritSectAlloc);
+ }
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(int) IOBUFMgrAllocBuf(IOBUFMGR hIoBufMgr, PIOBUFDESC pIoBufDesc, size_t cbIoBuf,
+ size_t *pcbIoBufAllocated)
+{
+ PIOBUFMGRINT pThis = hIoBufMgr;
+
+ LogFlowFunc(("pThis=%#p pIoBufDesc=%#p cbIoBuf=%zu pcbIoBufAllocated=%#p\n",
+ pThis, pIoBufDesc, cbIoBuf, pcbIoBufAllocated));
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(cbIoBuf > 0, VERR_INVALID_PARAMETER);
+
+ if ( !pThis->cbFree
+ || pThis->fAllocSuspended)
+ return VERR_NO_MEMORY;
+
+ int rc = RTCritSectEnter(&pThis->CritSectAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned iSeg = 0;
+ size_t cbLeft = cbIoBuf;
+ size_t cbIoBufAlloc = 0;
+ PRTSGSEG pSeg = &pIoBufDesc->Int.aSegs[0];
+
+ while ( iSeg < RT_ELEMENTS(pIoBufDesc->Int.aSegs)
+ && cbLeft)
+ {
+ size_t cbAlloc = iobufMgrAllocSegment(pThis, pSeg, cbLeft);
+ if (!cbAlloc)
+ break;
+
+ iSeg++;
+ pSeg++;
+ cbLeft -= RT_MIN(cbAlloc, cbLeft);
+ cbIoBufAlloc += cbAlloc;
+ }
+
+ if (iSeg)
+ RTSgBufInit(&pIoBufDesc->SgBuf, &pIoBufDesc->Int.aSegs[0], iSeg);
+ else
+ rc = VERR_NO_MEMORY;
+
+ pIoBufDesc->Int.cSegsUsed = iSeg;
+ pIoBufDesc->Int.pIoBufMgr = pThis;
+ *pcbIoBufAllocated = cbIoBufAlloc;
+ Assert( (RT_SUCCESS(rc) && *pcbIoBufAllocated > 0)
+ || RT_FAILURE(rc));
+
+ RTCritSectLeave(&pThis->CritSectAlloc);
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(void) IOBUFMgrFreeBuf(PIOBUFDESC pIoBufDesc)
+{
+ PIOBUFMGRINT pThis = pIoBufDesc->Int.pIoBufMgr;
+
+ LogFlowFunc(("pIoBufDesc=%#p{.cSegsUsed=%u}\n", pIoBufDesc, pIoBufDesc->Int.cSegsUsed));
+
+ AssertPtr(pThis);
+
+ int rc = RTCritSectEnter(&pThis->CritSectAlloc);
+ AssertRC(rc);
+
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < pIoBufDesc->Int.cSegsUsed; i++)
+ {
+ PRTSGSEG pSeg = &pIoBufDesc->Int.aSegs[i];
+
+ uint32_t u32Order = ASMBitLastSetU32((uint32_t)pSeg->cbSeg) - 1;
+ unsigned iBin = u32Order - pThis->u32OrderMin;
+
+ Assert(iBin < pThis->cBins);
+ PIOBUFMGRBIN pBin = &pThis->paBins[iBin];
+ iobufMgrBinObjAdd(pBin, pSeg->pvSeg);
+ pThis->cbFree += pSeg->cbSeg;
+
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ /* Mark the objects as free. */
+ uint32_t iBinStart = ((uintptr_t)pSeg->pvSeg - (uintptr_t)pThis->pvMem) / IOBUFMGR_BIN_SIZE_MIN;
+ Assert( !(((uintptr_t)pSeg->pvSeg - (uintptr_t)pThis->pvMem) % IOBUFMGR_BIN_SIZE_MIN)
+ && !(pSeg->cbSeg % IOBUFMGR_BIN_SIZE_MIN));
+ uint32_t iBinEnd = iBinStart + (pSeg->cbSeg / IOBUFMGR_BIN_SIZE_MIN);
+ while (iBinStart < iBinEnd)
+ {
+ bool fState = ASMBitTestAndClear(pThis->pbmObjState, iBinStart);
+ //LogFlowFunc(("iBinStart=%u fState=%RTbool -> false\n", iBinStart, fState));
+ AssertMsg(fState, ("Trying to free a non allocated object\n"));
+ iBinStart++;
+ }
+
+ /* Poison the state. */
+ pSeg->cbSeg = ~0;
+ pSeg->pvSeg = (void *)~(uintptr_t)0;
+#endif
+ }
+
+ if ( pThis->cbFree == pThis->cbMax
+ && pThis->fAllocSuspended)
+ {
+ iobufMgrResetBins(pThis);
+ pThis->fAllocSuspended = false;
+ }
+
+ RTCritSectLeave(&pThis->CritSectAlloc);
+ }
+
+ pIoBufDesc->Int.cSegsUsed = 0;
+#ifdef IOBUFMGR_VERIFY_ALLOCATIONS
+ memset(&pIoBufDesc->SgBuf, 0xff, sizeof(pIoBufDesc->SgBuf));
+#endif
+}
+
diff --git a/src/VBox/Devices/Storage/IOBufMgmt.h b/src/VBox/Devices/Storage/IOBufMgmt.h
new file mode 100644
index 00000000..234bc03e
--- /dev/null
+++ b/src/VBox/Devices/Storage/IOBufMgmt.h
@@ -0,0 +1,111 @@
+/* $Id: IOBufMgmt.h $ */
+/** @file
+ * VBox storage devices: I/O buffer management API.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_IOBufMgmt_h
+#define VBOX_INCLUDED_SRC_Storage_IOBufMgmt_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <iprt/sg.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Opaque I/O buffer manager.
+ */
+typedef struct IOBUFMGRINT *IOBUFMGR;
+/** Pointer to a I/O buffer manager. */
+typedef IOBUFMGR *PIOBUFMGR;
+
+/* NIL I/O buffer manager handle. */
+#define NIL_IOBUFMGR ((IOBUFMGR)0)
+
+#define IOBUFMGR_F_DEFAULT (0)
+/** I/O buffer memory needs to be non pageable (for example because it contains sensitive data
+ * which shouldn't end up in swap unencrypted). */
+#define IOBUFMGR_F_REQUIRE_NOT_PAGABLE RT_BIT(0)
+
+/**
+ * I/O buffer descriptor.
+ */
+typedef struct IOBUFDESC
+{
+ /** S/G buffer. */
+ RTSGBUF SgBuf;
+ /** Internal data */
+ union
+ {
+#ifdef IOBUFDESCINT_DECLARED
+ IOBUFDESCINT Int;
+#endif
+ uint8_t abPadding[HC_ARCH_BITS == 32 ? 88 : 172];
+ };
+} IOBUFDESC;
+/** Pointer to a I/O buffer descriptor. */
+typedef IOBUFDESC *PIOBUFDESC;
+
+/**
+ * Creates I/O buffer manager.
+ *
+ * @returns VBox status code.
+ * @param phIoBufMgr Where to store the handle to the I/O buffer manager on success.
+ * @param cbMax The maximum amount of I/O memory to allow. Trying to allocate more than
+ * this will lead to out of memory errors. 0 for "unlimited" size (only restriction
+ * is the available memory on the host).
+ * @param fFlags Combination of IOBUFMGR_F_*
+ */
+DECLHIDDEN(int) IOBUFMgrCreate(PIOBUFMGR phIoBufMgr, size_t cbMax, uint32_t fFlags);
+
+/**
+ * Destroys the given I/O buffer manager.
+ *
+ * @returns VBox status code.
+ * @retval VERR_INVALID_STATE if there is still memory allocated by the given manager.
+ * @param hIoBufMgr The I/O buffer manager.
+ */
+DECLHIDDEN(int) IOBUFMgrDestroy(IOBUFMGR hIoBufMgr);
+
+/**
+ * Allocates a I/O buffer and fills the descriptor.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NO_MEMORY if there is not enough free memory to satisfy the request
+ * and partial allocations are not allowed or allocating some internal tracking
+ * structures failed.
+ * @param hIoBufMgr The I/O buffer manager.
+ * @param pIoBufDesc The I/O buffer descriptor to initialize on success.
+ * @param cbIoBuf How much to allocate.
+ * @param pcbIoBufAllocated Where to store amount of memory the manager was able to allocate
+ * if there is not enough free memory to satisfy the complete request.
+ * NULL if partial allocations are not supported.
+ */
+DECLHIDDEN(int) IOBUFMgrAllocBuf(IOBUFMGR hIoBufMgr, PIOBUFDESC pIoBufDesc, size_t cbIoBuf,
+ size_t *pcbIoBufAllocated);
+
+/**
+ * Frees a given I/O buffer.
+ *
+ * @returns nothing.
+ * @param pIoBufDesc The I/O buffer descriptor to free.
+ */
+DECLHIDDEN(void) IOBUFMgrFreeBuf(PIOBUFDESC pIoBufDesc);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_IOBufMgmt_h */
diff --git a/src/VBox/Devices/Storage/Makefile.kup b/src/VBox/Devices/Storage/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/Storage/Makefile.kup
diff --git a/src/VBox/Devices/Storage/UsbMsd.cpp b/src/VBox/Devices/Storage/UsbMsd.cpp
new file mode 100644
index 00000000..b2b854ed
--- /dev/null
+++ b/src/VBox/Devices/Storage/UsbMsd.cpp
@@ -0,0 +1,2401 @@
+/* $Id: UsbMsd.cpp $ */
+/** @file
+ * UsbMSD - USB Mass Storage Device Emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_USB_MSD
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name USB MSD string IDs
+ * @{ */
+#define USBMSD_STR_ID_MANUFACTURER 1
+#define USBMSD_STR_ID_PRODUCT_HD 2
+#define USBMSD_STR_ID_PRODUCT_CDROM 3
+/** @} */
+
+/** @name USB MSD vendor and product IDs
+ * @{ */
+#define VBOX_USB_VENDOR 0x80EE
+#define USBMSD_PID_HD 0x0030
+#define USBMSD_PID_CD 0x0031
+/** @} */
+
+/** Saved state version. */
+#define USB_MSD_SAVED_STATE_VERSION 2
+/** Saved state vesion before the cleanup. */
+#define USB_MSD_SAVED_STATE_VERSION_PRE_CLEANUP 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * USB MSD Command Block Wrapper or CBW. The command block
+ * itself (CBWCB) contains protocol-specific data (here SCSI).
+ */
+#pragma pack(1)
+typedef struct USBCBW
+{
+ uint32_t dCBWSignature;
+#define USBCBW_SIGNATURE UINT32_C(0x43425355)
+ uint32_t dCBWTag;
+ uint32_t dCBWDataTransferLength;
+ uint8_t bmCBWFlags;
+#define USBCBW_DIR_MASK RT_BIT(7)
+#define USBCBW_DIR_OUT 0
+#define USBCBW_DIR_IN RT_BIT(7)
+ uint8_t bCBWLun;
+ uint8_t bCBWCBLength;
+ uint8_t CBWCB[16];
+} USBCBW;
+#pragma pack()
+AssertCompileSize(USBCBW, 31);
+/** Pointer to a Command Block Wrapper. */
+typedef USBCBW *PUSBCBW;
+/** Pointer to a const Command Block Wrapper. */
+typedef const USBCBW *PCUSBCBW;
+
+/**
+ * USB MSD Command Status Wrapper or CSW.
+ */
+#pragma pack(1)
+typedef struct USBCSW
+{
+ uint32_t dCSWSignature;
+#define USBCSW_SIGNATURE UINT32_C(0x53425355)
+ uint32_t dCSWTag;
+ uint32_t dCSWDataResidue;
+#define USBCSW_STATUS_OK UINT8_C(0)
+#define USBCSW_STATUS_FAILED UINT8_C(1)
+#define USBCSW_STATUS_PHASE_ERROR UINT8_C(2)
+ uint8_t bCSWStatus;
+} USBCSW;
+#pragma pack()
+AssertCompileSize(USBCSW, 13);
+/** Pointer to a Command Status Wrapper. */
+typedef USBCSW *PUSBCSW;
+/** Pointer to a const Command Status Wrapper. */
+typedef const USBCSW *PCUSBCSW;
+
+
+/**
+ * The USB MSD request state.
+ */
+typedef enum USBMSDREQSTATE
+{
+ /** Invalid status. */
+ USBMSDREQSTATE_INVALID = 0,
+ /** Ready to receive a new SCSI command. */
+ USBMSDREQSTATE_READY,
+ /** Waiting for the host to supply data. */
+ USBMSDREQSTATE_DATA_FROM_HOST,
+ /** The SCSI request is being executed by the driver. */
+ USBMSDREQSTATE_EXECUTING,
+ /** Have (more) data for the host. */
+ USBMSDREQSTATE_DATA_TO_HOST,
+ /** Waiting to supply status information to the host. */
+ USBMSDREQSTATE_STATUS,
+ /** Destroy the request upon completion.
+ * This is set when the SCSI request doesn't complete before for the device or
+ * mass storage reset operation times out. USBMSD::pReq will be set to NULL
+ * and the only reference to this request will be with DrvSCSI. */
+ USBMSDREQSTATE_DESTROY_ON_COMPLETION,
+ /** The end of the valid states. */
+ USBMSDREQSTATE_END,
+ /** 32bit blow up hack. */
+ USBMSDREQSTATE_32BIT_HACK = 0x7fffffff
+} USBMSDREQSTATE;
+
+
+/**
+ * A pending USB MSD request.
+ */
+typedef struct USBMSDREQ
+{
+ /** The state of the request. */
+ USBMSDREQSTATE enmState;
+ /** The I/O requesthandle .*/
+ PDMMEDIAEXIOREQ hIoReq;
+ /** The size of the data buffer. */
+ uint32_t cbBuf;
+ /** Pointer to the data buffer. */
+ uint8_t *pbBuf;
+ /** Current buffer offset. */
+ uint32_t offBuf;
+ /** The current Cbw when we're in the pending state. */
+ USBCBW Cbw;
+ /** The status of a completed SCSI request. */
+ uint8_t iScsiReqStatus;
+} USBMSDREQ;
+/** Pointer to a USB MSD request. */
+typedef USBMSDREQ *PUSBMSDREQ;
+
+
+/**
+ * Endpoint status data.
+ */
+typedef struct USBMSDEP
+{
+ bool fHalted;
+} USBMSDEP;
+/** Pointer to the endpoint status. */
+typedef USBMSDEP *PUSBMSDEP;
+
+
+/**
+ * A URB queue.
+ */
+typedef struct USBMSDURBQUEUE
+{
+ /** The head pointer. */
+ PVUSBURB pHead;
+ /** Where to insert the next entry. */
+ PVUSBURB *ppTail;
+} USBMSDURBQUEUE;
+/** Pointer to a URB queue. */
+typedef USBMSDURBQUEUE *PUSBMSDURBQUEUE;
+/** Pointer to a const URB queue. */
+typedef USBMSDURBQUEUE const *PCUSBMSDURBQUEUE;
+
+
+/**
+ * The USB MSD instance data.
+ */
+typedef struct USBMSD
+{
+ /** Pointer back to the PDM USB Device instance structure. */
+ PPDMUSBINS pUsbIns;
+ /** Critical section protecting the device state. */
+ RTCRITSECT CritSect;
+
+ /** The current configuration.
+ * (0 - default, 1 - the only, i.e configured.) */
+ uint8_t bConfigurationValue;
+ /** Endpoint 0 is the default control pipe, 1 is the host->dev bulk pipe and 2
+ * is the dev->host one. */
+ USBMSDEP aEps[3];
+ /** The current request. */
+ PUSBMSDREQ pReq;
+
+ /** Pending to-host queue.
+ * The URBs waiting here are pending the completion of the current request and
+ * data or status to become available.
+ */
+ USBMSDURBQUEUE ToHostQueue;
+
+ /** Done queue
+ * The URBs stashed here are waiting to be reaped. */
+ USBMSDURBQUEUE DoneQueue;
+ /** Signalled when adding an URB to the done queue and fHaveDoneQueueWaiter
+ * is set. */
+ RTSEMEVENT hEvtDoneQueue;
+ /** Someone is waiting on the done queue. */
+ bool fHaveDoneQueueWaiter;
+
+ /** Whether to signal the reset semaphore when the current request completes. */
+ bool fSignalResetSem;
+ /** Semaphore usbMsdUsbReset waits on when a request is executing at reset
+ * time. Only signalled when fSignalResetSem is set. */
+ RTSEMEVENTMULTI hEvtReset;
+ /** The reset URB.
+ * This is waiting for SCSI request completion before finishing the reset. */
+ PVUSBURB pResetUrb;
+ /** Indicates that PDMUsbHlpAsyncNotificationCompleted should be called when
+ * the MSD is entering the idle state. */
+ volatile bool fSignalIdle;
+
+ /** Indicates that this device is a CD-ROM. */
+ bool fIsCdrom;
+
+ /**
+ * LUN\#0 data.
+ */
+ struct
+ {
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** The media port interface fo LUN\#0. */
+ PDMIMEDIAPORT IMediaPort;
+ /** The extended media port interface for LUN\#0 */
+ PDMIMEDIAEXPORT IMediaExPort;
+
+ /** The base interface for the SCSI driver connected to LUN\#0. */
+ PPDMIBASE pIBase;
+ /** The media interface for th SCSI drver conected to LUN\#0. */
+ PPDMIMEDIA pIMedia;
+ /** The extended media inerface for the SCSI driver connected to LUN\#0. */
+ PPDMIMEDIAEX pIMediaEx;
+ } Lun0;
+
+} USBMSD;
+/** Pointer to the USB MSD instance data. */
+typedef USBMSD *PUSBMSD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const PDMUSBDESCCACHESTRING g_aUsbMsdStrings_en_US[] =
+{
+ { USBMSD_STR_ID_MANUFACTURER, "VirtualBox" },
+ { USBMSD_STR_ID_PRODUCT_HD, "USB Harddisk" },
+ { USBMSD_STR_ID_PRODUCT_CDROM, "USB CD-ROM" }
+};
+
+static const PDMUSBDESCCACHELANG g_aUsbMsdLanguages[] =
+{
+ { 0x0409, RT_ELEMENTS(g_aUsbMsdStrings_en_US), g_aUsbMsdStrings_en_US }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsFS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 64 /* maximum possible */,
+ /* .bInterval = */ 0 /* not applicable for bulk EP */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 64 /* maximum possible */,
+ /* .bInterval = */ 0 /* not applicable for bulk EP */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsHS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 512 /* HS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 512 /* HS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ }
+};
+
+static const VUSBDESCSSEPCOMPANION g_aUsbMsdEpCompanionSS =
+{
+ /* .bLength = */ sizeof(VUSBDESCSSEPCOMPANION),
+ /* .bDescriptorType = */ VUSB_DT_SS_ENDPOINT_COMPANION,
+ /* .bMaxBurst = */ 15 /* we can burst all the way */,
+ /* .bmAttributes = */ 0 /* no streams */,
+ /* .wBytesPerInterval = */ 0 /* not a periodic endpoint */
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsSS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 1024 /* SS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ &g_aUsbMsdEpCompanionSS,
+ /* .cbSsepc = */ sizeof(g_aUsbMsdEpCompanionSS)
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 1024 /* SS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ &g_aUsbMsdEpCompanionSS,
+ /* .cbSsepc = */ sizeof(g_aUsbMsdEpCompanionSS)
+ }
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescFS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsFS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescHS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsHS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescSS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsSS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesFS[] =
+{
+ { &g_UsbMsdInterfaceDescFS, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesHS[] =
+{
+ { &g_UsbMsdInterfaceDescHS, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesSS[] =
+{
+ { &g_UsbMsdInterfaceDescSS, /* .cSettings = */ 1 },
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescFS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesFS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesFS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescHS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesHS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesHS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescSS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesSS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesSS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCDEVICE g_UsbMsdDeviceDesc20 =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceDesc20),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_HD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_HD,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbCdDeviceDesc20 =
+{
+ /* .bLength = */ sizeof(g_UsbCdDeviceDesc20),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_CD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_CDROM,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbMsdDeviceDesc30 =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceDesc30),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x300, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 9 /* 512, the only option for USB3. */,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_HD,
+ /* .bcdDevice = */ 0x0110, /* 1.10 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_HD,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbCdDeviceDesc30 =
+{
+ /* .bLength = */ sizeof(g_UsbCdDeviceDesc30),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x300, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 9 /* 512, the only option for USB3. */,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_CD,
+ /* .bcdDevice = */ 0x0110, /* 1.10 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_CDROM,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDEVICEQUALIFIER g_UsbMsdDeviceQualifier =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceQualifier),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE_QUALIFIER,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .bNumConfigurations = */ 1,
+ /* .bReserved = */ 0
+};
+
+static const struct {
+ VUSBDESCBOS bos;
+ VUSBDESCSSDEVCAP sscap;
+} g_UsbMsdBOS =
+{
+ {
+ /* .bLength = */ sizeof(g_UsbMsdBOS.bos),
+ /* .bDescriptorType = */ VUSB_DT_BOS,
+ /* .wTotalLength = */ sizeof(g_UsbMsdBOS),
+ /* .bNumDeviceCaps = */ 1
+ },
+ {
+ /* .bLength = */ sizeof(VUSBDESCSSDEVCAP),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE_CAPABILITY,
+ /* .bDevCapabilityType = */ VUSB_DCT_SUPERSPEED_USB,
+ /* .bmAttributes = */ 0 /* No LTM. */,
+ /* .wSpeedsSupported = */ 0xe /* Any speed is good. */,
+ /* .bFunctionalitySupport = */ 2 /* Want HS at least. */,
+ /* .bU1DevExitLat = */ 0, /* We are blazingly fast. */
+ /* .wU2DevExitLat = */ 0
+ }
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheFS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescFS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheFS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescFS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheHS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescHS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheHS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescHS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheSS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc30,
+ /* .paConfigs = */ &g_UsbMsdConfigDescSS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheSS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc30,
+ /* .paConfigs = */ &g_UsbMsdConfigDescSS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int usbMsdHandleBulkDevToHost(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb);
+
+
+/**
+ * Initializes an URB queue.
+ *
+ * @param pQueue The URB queue.
+ */
+static void usbMsdQueueInit(PUSBMSDURBQUEUE pQueue)
+{
+ pQueue->pHead = NULL;
+ pQueue->ppTail = &pQueue->pHead;
+}
+
+
+
+/**
+ * Inserts an URB at the end of the queue.
+ *
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to insert.
+ */
+DECLINLINE(void) usbMsdQueueAddTail(PUSBMSDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ pUrb->Dev.pNext = NULL;
+ *pQueue->ppTail = pUrb;
+ pQueue->ppTail = &pUrb->Dev.pNext;
+}
+
+
+/**
+ * Unlinks the head of the queue and returns it.
+ *
+ * @returns The head entry.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(PVUSBURB) usbMsdQueueRemoveHead(PUSBMSDURBQUEUE pQueue)
+{
+ PVUSBURB pUrb = pQueue->pHead;
+ if (pUrb)
+ {
+ PVUSBURB pNext = pUrb->Dev.pNext;
+ pQueue->pHead = pNext;
+ if (!pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ else
+ pUrb->Dev.pNext = NULL;
+ }
+ return pUrb;
+}
+
+
+/**
+ * Removes an URB from anywhere in the queue.
+ *
+ * @returns true if found, false if not.
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to remove.
+ */
+DECLINLINE(bool) usbMsdQueueRemove(PUSBMSDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ PVUSBURB pCur = pQueue->pHead;
+ if (pCur == pUrb)
+ pQueue->pHead = pUrb->Dev.pNext;
+ else
+ {
+ while (pCur)
+ {
+ if (pCur->Dev.pNext == pUrb)
+ {
+ pCur->Dev.pNext = pUrb->Dev.pNext;
+ break;
+ }
+ pCur = pCur->Dev.pNext;
+ }
+ if (!pCur)
+ return false;
+ }
+ if (!pUrb->Dev.pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ return true;
+}
+
+
+#ifdef VBOX_STRICT
+/**
+ * Checks if the queue is empty or not.
+ *
+ * @returns true if it is, false if it isn't.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(bool) usbMsdQueueIsEmpty(PCUSBMSDURBQUEUE pQueue)
+{
+ return pQueue->pHead == NULL;
+}
+#endif /* VBOX_STRICT */
+
+
+/**
+ * Links an URB into the done queue.
+ *
+ * @param pThis The MSD instance.
+ * @param pUrb The URB.
+ */
+static void usbMsdLinkDone(PUSBMSD pThis, PVUSBURB pUrb)
+{
+ usbMsdQueueAddTail(&pThis->DoneQueue, pUrb);
+
+ if (pThis->fHaveDoneQueueWaiter)
+ {
+ int rc = RTSemEventSignal(pThis->hEvtDoneQueue);
+ AssertRC(rc);
+ }
+}
+
+
+
+
+/**
+ * Allocates a new request and does basic init.
+ *
+ * @returns Pointer to the new request. NULL if we're out of memory.
+ * @param pThis The MSD instance.
+ */
+static PUSBMSDREQ usbMsdReqAlloc(PUSBMSD pThis)
+{
+ PUSBMSDREQ pReq = NULL;
+ PDMMEDIAEXIOREQ hIoReq = NULL;
+
+ int rc = pThis->Lun0.pIMediaEx->pfnIoReqAlloc(pThis->Lun0.pIMediaEx, &hIoReq, (void **)&pReq,
+ 0 /* uTag */, PDMIMEDIAEX_F_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->hIoReq = hIoReq;
+ pReq->enmState = USBMSDREQSTATE_READY;
+ pReq->iScsiReqStatus = 0xff;
+ }
+ else
+ LogRel(("usbMsdReqAlloc: Out of memory (%Rrc)\n", rc));
+
+ return pReq;
+}
+
+
+/**
+ * Frees a request.
+ *
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ */
+static void usbMsdReqFree(PUSBMSD pThis, PUSBMSDREQ pReq)
+{
+ /*
+ * Check the input.
+ */
+ AssertReturnVoid( pReq->enmState > USBMSDREQSTATE_INVALID
+ && pReq->enmState != USBMSDREQSTATE_EXECUTING
+ && pReq->enmState < USBMSDREQSTATE_END);
+ PPDMUSBINS pUsbIns = pThis->pUsbIns;
+ AssertPtrReturnVoid(pUsbIns);
+ AssertReturnVoid(PDM_VERSION_ARE_COMPATIBLE(pUsbIns->u32Version, PDM_USBINS_VERSION));
+
+ /*
+ * Invalidate it and free the associated resources.
+ */
+ pReq->enmState = USBMSDREQSTATE_INVALID;
+ pReq->cbBuf = 0;
+ pReq->offBuf = 0;
+
+ if (pReq->pbBuf)
+ {
+ PDMUsbHlpMMHeapFree(pUsbIns, pReq->pbBuf);
+ pReq->pbBuf = NULL;
+ }
+
+ int rc = pThis->Lun0.pIMediaEx->pfnIoReqFree(pThis->Lun0.pIMediaEx, pReq->hIoReq);
+ AssertRC(rc);
+}
+
+
+/**
+ * Prepares a request for execution or data buffering.
+ *
+ * @param pReq The request.
+ * @param pCbw The SCSI command block wrapper.
+ */
+static void usbMsdReqPrepare(PUSBMSDREQ pReq, PCUSBCBW pCbw)
+{
+ /* Copy the CBW */
+ size_t cbCopy = RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]);
+ memcpy(&pReq->Cbw, pCbw, cbCopy);
+ memset((uint8_t *)&pReq->Cbw + cbCopy, 0, sizeof(pReq->Cbw) - cbCopy);
+
+ /* Setup the SCSI request. */
+ pReq->offBuf = 0;
+ pReq->iScsiReqStatus = 0xff;
+}
+
+
+/**
+ * Makes sure that there is sufficient buffer space available.
+ *
+ * @returns Success indicator (true/false)
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ * @param cbBuf The required buffer space.
+ */
+static int usbMsdReqEnsureBuffer(PUSBMSD pThis, PUSBMSDREQ pReq, uint32_t cbBuf)
+{
+ if (RT_LIKELY(pReq->cbBuf >= cbBuf))
+ RT_BZERO(pReq->pbBuf, cbBuf);
+ else
+ {
+ PDMUsbHlpMMHeapFree(pThis->pUsbIns, pReq->pbBuf);
+ pReq->cbBuf = 0;
+
+ cbBuf = RT_ALIGN_Z(cbBuf, 0x1000);
+ pReq->pbBuf = (uint8_t *)PDMUsbHlpMMHeapAllocZ(pThis->pUsbIns, cbBuf);
+ if (!pReq->pbBuf)
+ return false;
+
+ pReq->cbBuf = cbBuf;
+ }
+ return true;
+}
+
+
+/**
+ * Completes the URB with a stalled state, halting the pipe.
+ */
+static int usbMsdCompleteStall(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb, const char *pszWhy)
+{
+ RT_NOREF(pszWhy);
+ Log(("usbMsdCompleteStall/#%u: pUrb=%p:%s: %s\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy));
+
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+
+ /** @todo figure out if the stall is global or pipe-specific or both. */
+ if (pEp)
+ pEp->fHalted = true;
+ else
+ {
+ pThis->aEps[1].fHalted = true;
+ pThis->aEps[2].fHalted = true;
+ }
+
+ usbMsdLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Completes the URB with a OK state.
+ */
+static int usbMsdCompleteOk(PUSBMSD pThis, PVUSBURB pUrb, size_t cbData)
+{
+ Log(("usbMsdCompleteOk/#%u: pUrb=%p:%s cbData=%#zx\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, cbData));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->cbData = (uint32_t)cbData;
+
+ usbMsdLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reset worker for usbMsdUsbReset, usbMsdUsbSetConfiguration and
+ * usbMsdUrbHandleDefaultPipe.
+ *
+ * @returns VBox status code.
+ * @param pThis The MSD instance.
+ * @param pUrb Set when usbMsdUrbHandleDefaultPipe is the
+ * caller.
+ * @param fSetConfig Set when usbMsdUsbSetConfiguration is the
+ * caller.
+ */
+static int usbMsdResetWorker(PUSBMSD pThis, PVUSBURB pUrb, bool fSetConfig)
+{
+ /*
+ * Wait for the any command currently executing to complete before
+ * resetting. (We cannot cancel its execution.) How we do this depends
+ * on the reset method.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ if ( pReq
+ && pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ {
+ /* Don't try to deal with the set config variant nor multiple build-only
+ mass storage resets. */
+ if (pThis->pResetUrb && (pUrb || fSetConfig))
+ {
+ Log(("usbMsdResetWorker: pResetUrb is already %p:%s - stalling\n", pThis->pResetUrb, pThis->pResetUrb->pszDesc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "pResetUrb");
+ }
+
+ /* Bulk-Only Mass Storage Reset: Complete the reset on request completion. */
+ if (pUrb)
+ {
+ pThis->pResetUrb = pUrb;
+ Log(("usbMsdResetWorker: Setting pResetUrb to %p:%s\n", pThis->pResetUrb, pThis->pResetUrb->pszDesc));
+ return VINF_SUCCESS;
+ }
+
+ /* Device reset: Wait for up to 10 ms. If it doesn't work, ditch
+ whole the request structure. We'll allocate a new one when needed. */
+ Log(("usbMsdResetWorker: Waiting for completion...\n"));
+ Assert(!pThis->fSignalResetSem);
+ pThis->fSignalResetSem = true;
+ RTSemEventMultiReset(pThis->hEvtReset);
+ RTCritSectLeave(&pThis->CritSect);
+
+ int rc = RTSemEventMultiWait(pThis->hEvtReset, 10 /*ms*/);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fSignalResetSem = false;
+ if ( RT_FAILURE(rc)
+ || pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ {
+ Log(("usbMsdResetWorker: Didn't complete, ditching the current request (%p)!\n", pReq));
+ Assert(pReq == pThis->pReq);
+ pReq->enmState = USBMSDREQSTATE_DESTROY_ON_COMPLETION;
+ pThis->pReq = NULL;
+ pReq = NULL;
+ }
+ }
+
+ /*
+ * Reset the request and device state.
+ */
+ if (pReq)
+ {
+ pReq->enmState = USBMSDREQSTATE_READY;
+ pReq->iScsiReqStatus = 0xff;
+ }
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = false;
+
+ if (!pUrb && !fSetConfig) /* (only device reset) */
+ pThis->bConfigurationValue = 0; /* default */
+
+ /*
+ * Ditch all pending URBs.
+ */
+ PVUSBURB pCurUrb;
+ while ((pCurUrb = usbMsdQueueRemoveHead(&pThis->ToHostQueue)) != NULL)
+ {
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbMsdLinkDone(pThis, pCurUrb);
+ }
+
+ pCurUrb = pThis->pResetUrb;
+ if (pCurUrb)
+ {
+ pThis->pResetUrb = NULL;
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbMsdLinkDone(pThis, pCurUrb);
+ }
+
+ if (pUrb)
+ return usbMsdCompleteOk(pThis, pUrb, 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Process a completed request.
+ *
+ * @returns nothing.
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ * @param rcReq The completion status.
+ */
+static void usbMsdReqComplete(PUSBMSD pThis, PUSBMSDREQ pReq, int rcReq)
+{
+ RT_NOREF1(rcReq);
+
+ Log(("usbMsdLun0IoReqCompleteNotify: pReq=%p dCBWTag=%#x iScsiReqStatus=%u \n", pReq, pReq->Cbw.dCBWTag, pReq->iScsiReqStatus));
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (pReq->enmState != USBMSDREQSTATE_DESTROY_ON_COMPLETION)
+ {
+ Assert(pReq->enmState == USBMSDREQSTATE_EXECUTING);
+ Assert(pThis->pReq == pReq);
+
+ /*
+ * Advance the state machine. The state machine is not affected by
+ * SCSI errors.
+ */
+ if ((pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT)
+ {
+ pReq->enmState = USBMSDREQSTATE_STATUS;
+ Log(("usbMsdLun0IoReqCompleteNotify: Entering STATUS\n"));
+ }
+ else
+ {
+ pReq->enmState = USBMSDREQSTATE_DATA_TO_HOST;
+ Log(("usbMsdLun0IoReqCompleteNotify: Entering DATA_TO_HOST\n"));
+ }
+
+ /*
+ * Deal with pending to-host URBs.
+ */
+ for (;;)
+ {
+ PVUSBURB pUrb = usbMsdQueueRemoveHead(&pThis->ToHostQueue);
+ if (!pUrb)
+ break;
+
+ /* Process it the normal way. */
+ usbMsdHandleBulkDevToHost(pThis, &pThis->aEps[1], pUrb);
+ }
+ }
+ else
+ {
+ Log(("usbMsdLun0IoReqCompleteNotify: freeing %p\n", pReq));
+ usbMsdReqFree(pThis, pReq);
+ }
+
+ if (pThis->fSignalResetSem)
+ RTSemEventMultiSignal(pThis->hEvtReset);
+
+ if (pThis->pResetUrb)
+ {
+ pThis->pResetUrb = NULL;
+ usbMsdResetWorker(pThis, pThis->pResetUrb, false /*fSetConfig*/);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+ int rc = VINF_SUCCESS;
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ if (RT_UNLIKELY(offDst + cbCopy > pReq->cbBuf))
+ rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+ else
+ {
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, pReq->pbBuf + offDst, cbCopy);
+ Assert(cbCopied == cbCopy); RT_NOREF(cbCopied);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+ int rc = VINF_SUCCESS;
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ if (RT_UNLIKELY(offSrc + cbCopy > pReq->cbBuf))
+ rc = VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+ else
+ {
+ size_t cbCopied = RTSgBufCopyFromBuf(pSgBuf, pReq->pbBuf + offSrc, cbCopy);
+ Assert(cbCopied == cbCopy); RT_NOREF(cbCopied);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF1(hIoReq);
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IMediaExPort);
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ usbMsdReqComplete(pThis, pReq, rcReq);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) usbMsdLun0IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF4(pInterface, hIoReq, pvIoReqAlloc, enmState);
+ AssertLogRelMsgFailed(("This should not be hit because I/O requests should not be suspended\n"));
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) usbMsdLun0MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ RT_NOREF1(pInterface); /** @todo */
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) usbMsdLun0QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IMediaPort);
+ PPDMUSBINS pUsbIns = pThis->pUsbIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pUsbIns->pReg->szName;
+ *piInstance = pUsbIns->iInstance;
+ *piLUN = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) usbMsdLun0QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pThis->Lun0.IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pThis->Lun0.IMediaExPort);
+ return NULL;
+}
+
+
+/**
+ * Checks if all asynchronous I/O is finished.
+ *
+ * Used by usbMsdVMReset, usbMsdVMSuspend and usbMsdVMPowerOff.
+ *
+ * @returns true if quiesced, false if busy.
+ * @param pUsbIns The USB device instance.
+ */
+static bool usbMsdAllAsyncIOIsFinished(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ if ( VALID_PTR(pThis->pReq)
+ && pThis->pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ return false;
+
+ return true;
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by usbMsdVMSuspend and usbMsdVMPowerOff.}
+ */
+static DECLCALLBACK(bool) usbMsdIsAsyncSuspendOrPowerOffDone(PPDMUSBINS pUsbIns)
+{
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ return false;
+
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for usbMsdVMSuspend and usbMsdVMPowerOff.
+ */
+static void usbMsdSuspendOrPowerOff(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ PDMUsbHlpSetAsyncNotification(pUsbIns, usbMsdIsAsyncSuspendOrPowerOffDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ if (pThis->pReq)
+ {
+ usbMsdReqFree(pThis, pThis->pReq);
+ pThis->pReq = NULL;
+ }
+ }
+
+ if (pThis->Lun0.pIMediaEx)
+ pThis->Lun0.pIMediaEx->pfnNotifySuspend(pThis->Lun0.pIMediaEx);
+}
+
+
+/* -=-=-=-=- Saved State -=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMUSBSAVEPREP}
+ */
+static DECLCALLBACK(int) usbMsdSavePrep(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+#ifdef VBOX_STRICT
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ Assert(usbMsdAllAsyncIOIsFinished(pUsbIns));
+ Assert(usbMsdQueueIsEmpty(&pThis->ToHostQueue));
+ Assert(usbMsdQueueIsEmpty(&pThis->DoneQueue));
+#else
+ RT_NOREF(pUsbIns);
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLOADPREP}
+ */
+static DECLCALLBACK(int) usbMsdLoadPrep(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+#ifdef VBOX_STRICT
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ Assert(usbMsdAllAsyncIOIsFinished(pUsbIns));
+ Assert(usbMsdQueueIsEmpty(&pThis->ToHostQueue));
+ Assert(usbMsdQueueIsEmpty(&pThis->DoneQueue));
+#else
+ RT_NOREF(pUsbIns);
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLIVEEXEC}
+ */
+static DECLCALLBACK(int) usbMsdLiveExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ /* config. */
+ SSMR3PutBool(pSSM, pThis->Lun0.pIBase != NULL);
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBSAVEEXEC}
+ */
+static DECLCALLBACK(int) usbMsdSaveExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ int rc;
+
+ /* The config */
+ rc = usbMsdLiveExec(pUsbIns, pSSM, SSM_PASS_FINAL);
+ AssertRCReturn(rc, rc);
+
+ SSMR3PutU8(pSSM, pThis->bConfigurationValue);
+ SSMR3PutBool(pSSM, pThis->aEps[0].fHalted);
+ SSMR3PutBool(pSSM, pThis->aEps[1].fHalted);
+ SSMR3PutBool(pSSM, pThis->aEps[2].fHalted);
+ SSMR3PutBool(pSSM, pThis->pReq != NULL);
+
+ if (pThis->pReq)
+ {
+ PUSBMSDREQ pReq = pThis->pReq;
+
+ SSMR3PutU32(pSSM, pReq->enmState);
+ SSMR3PutU32(pSSM, pReq->cbBuf);
+ if (pReq->cbBuf)
+ {
+ AssertPtr(pReq->pbBuf);
+ SSMR3PutMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+
+ SSMR3PutU32(pSSM, pReq->offBuf);
+ SSMR3PutMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+ SSMR3PutU8(pSSM, pReq->iScsiReqStatus);
+ }
+
+ return SSMR3PutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLOADEXEC}
+ */
+static DECLCALLBACK(int) usbMsdLoadExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ uint32_t u32;
+ int rc;
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Verify config. */
+ bool fInUse;
+ rc = SSMR3GetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pThis->Lun0.pIBase != NULL))
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS,
+ N_("The %s VM is missing a USB mass storage device. Please make sure the source and target VMs have compatible storage configurations"),
+ fInUse ? "target" : "source");
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ /* Restore data. */
+ Assert(!pThis->pReq);
+
+ SSMR3GetU8(pSSM, &pThis->bConfigurationValue);
+ SSMR3GetBool(pSSM, &pThis->aEps[0].fHalted);
+ SSMR3GetBool(pSSM, &pThis->aEps[1].fHalted);
+ SSMR3GetBool(pSSM, &pThis->aEps[2].fHalted);
+ bool fReqAlloc = false;
+ rc = SSMR3GetBool(pSSM, &fReqAlloc);
+ AssertRCReturn(rc, rc);
+ if (fReqAlloc)
+ {
+ PUSBMSDREQ pReq = usbMsdReqAlloc(pThis);
+ AssertReturn(pReq, VERR_NO_MEMORY);
+ pThis->pReq = pReq;
+
+ SSMR3GetU32(pSSM, (uint32_t *)&pReq->enmState);
+ uint32_t cbBuf = 0;
+ rc = SSMR3GetU32(pSSM, &cbBuf);
+ AssertRCReturn(rc, rc);
+ if (cbBuf)
+ {
+ if (usbMsdReqEnsureBuffer(pThis, pReq, cbBuf))
+ {
+ AssertPtr(pReq->pbBuf);
+ Assert(cbBuf == pReq->cbBuf);
+ SSMR3GetMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ SSMR3GetU32(pSSM, &pReq->offBuf);
+ SSMR3GetMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION_PRE_CLEANUP)
+ rc = SSMR3GetU8(pSSM, &pReq->iScsiReqStatus);
+ else
+ {
+ int32_t iScsiReqStatus;
+
+ /* Skip old fields which are unused now or can be determined from the CBW. */
+ SSMR3Skip(pSSM, 4 * 4 + 64);
+ rc = SSMR3GetS32(pSSM, &iScsiReqStatus);
+ pReq->iScsiReqStatus = (uint8_t)iScsiReqStatus;
+ }
+ AssertRCReturn(rc, rc);
+ }
+
+ rc = SSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbReap}
+ */
+static DECLCALLBACK(PVUSBURB) usbMsdUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbReap/#%u: cMillies=%u\n", pUsbIns->iInstance, cMillies));
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ PVUSBURB pUrb = usbMsdQueueRemoveHead(&pThis->DoneQueue);
+ if (!pUrb && cMillies)
+ {
+ /* Wait */
+ pThis->fHaveDoneQueueWaiter = true;
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTSemEventWait(pThis->hEvtDoneQueue, cMillies);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fHaveDoneQueueWaiter = false;
+
+ pUrb = usbMsdQueueRemoveHead(&pThis->DoneQueue);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ if (pUrb)
+ Log(("usbMsdUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ return pUrb;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ */
+static DECLCALLBACK(int) usbMsdWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbReap/#%u:\n", pUsbIns->iInstance));
+
+ return RTSemEventSignal(pThis->hEvtDoneQueue);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ */
+static DECLCALLBACK(int) usbMsdUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbCancel/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Remove the URB from the to-host queue and move it onto the done queue.
+ */
+ if (usbMsdQueueRemove(&pThis->ToHostQueue, pUrb))
+ usbMsdLinkDone(pThis, pUrb);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Wrapper around PDMISCSICONNECTOR::pfnSCSIRequestSend that deals with
+ * SCSI_REQUEST_SENSE.
+ *
+ * @returns VBox status code.
+ * @param pThis The MSD instance data.
+ * @param pReq The MSD request.
+ * @param pszCaller Where we're called from.
+ */
+static int usbMsdSubmitScsiCommand(PUSBMSD pThis, PUSBMSDREQ pReq, const char *pszCaller)
+{
+ RT_NOREF(pszCaller);
+ Log(("%s: Entering EXECUTING (dCBWTag=%#x).\n", pszCaller, pReq->Cbw.dCBWTag));
+ Assert(pReq == pThis->pReq);
+ pReq->enmState = USBMSDREQSTATE_EXECUTING;
+
+ PDMMEDIAEXIOREQSCSITXDIR enmTxDir = pReq->Cbw.dCBWDataTransferLength == 0
+ ? PDMMEDIAEXIOREQSCSITXDIR_NONE
+ : (pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT
+ ? PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE
+ : PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+
+ return pThis->Lun0.pIMediaEx->pfnIoReqSendScsiCmd(pThis->Lun0.pIMediaEx, pReq->hIoReq, pReq->Cbw.bCBWLun,
+ &pReq->Cbw.CBWCB[0], pReq->Cbw.bCBWCBLength, enmTxDir,
+ pReq->Cbw.dCBWDataTransferLength, NULL, 0,
+ &pReq->iScsiReqStatus, 20 * RT_MS_1SEC);
+}
+
+
+/**
+ * Handle requests sent to the outbound (to device) bulk pipe.
+ */
+static int usbMsdHandleBulkHostToDev(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted.
+ */
+ if (RT_UNLIKELY(pEp->fHalted))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Halted pipe");
+
+ /*
+ * Deal with the URB according to the current state.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ USBMSDREQSTATE enmState = pReq ? pReq->enmState : USBMSDREQSTATE_READY;
+ switch (enmState)
+ {
+ case USBMSDREQSTATE_STATUS:
+ LogFlow(("usbMsdHandleBulkHostToDev: Skipping pending status.\n"));
+ pReq->enmState = USBMSDREQSTATE_READY;
+ RT_FALL_THRU();
+
+ /*
+ * We're ready to receive a command. Start off by validating the
+ * incoming request.
+ */
+ case USBMSDREQSTATE_READY:
+ {
+ PCUSBCBW pCbw = (PUSBCBW)&pUrb->abData[0];
+ if (pUrb->cbData < RT_UOFFSETOF(USBCBW, CBWCB[1]))
+ {
+ Log(("usbMsd: Bad CBW: cbData=%#x < min=%#x\n", pUrb->cbData, RT_UOFFSETOF(USBCBW, CBWCB[1]) ));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "BAD CBW");
+ }
+ if (pCbw->dCBWSignature != USBCBW_SIGNATURE)
+ {
+ Log(("usbMsd: CBW: Invalid dCBWSignature value: %#x\n", pCbw->dCBWSignature));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ Log(("usbMsd: CBW: dCBWTag=%#x dCBWDataTransferLength=%#x bmCBWFlags=%#x bCBWLun=%#x bCBWCBLength=%#x cbData=%#x fShortNotOk=%RTbool\n",
+ pCbw->dCBWTag, pCbw->dCBWDataTransferLength, pCbw->bmCBWFlags, pCbw->bCBWLun, pCbw->bCBWCBLength, pUrb->cbData, pUrb->fShortNotOk));
+ if (pCbw->bmCBWFlags & ~USBCBW_DIR_MASK)
+ {
+ Log(("usbMsd: CBW: Bad bmCBWFlags value: %#x\n", pCbw->bmCBWFlags));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+
+ }
+ if (pCbw->bCBWLun != 0)
+ {
+ Log(("usbMsd: CBW: Bad bCBWLun value: %#x\n", pCbw->bCBWLun));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if (pCbw->bCBWCBLength == 0)
+ {
+ Log(("usbMsd: CBW: Bad bCBWCBLength value: %#x\n", pCbw->bCBWCBLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if (pUrb->cbData < RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]))
+ {
+ Log(("usbMsd: CBW: Mismatching cbData and bCBWCBLength values: %#x vs. %#x (%#x)\n",
+ pUrb->cbData, RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]), pCbw->bCBWCBLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if (pCbw->dCBWDataTransferLength > _1M)
+ {
+ Log(("usbMsd: CBW: dCBWDataTransferLength is too large: %#x (%u)\n",
+ pCbw->dCBWDataTransferLength, pCbw->dCBWDataTransferLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Too big transfer");
+ }
+
+ /*
+ * Make sure we've got a request and a sufficient buffer space.
+ *
+ * Note! This will make sure the buffer is ZERO as well, thus
+ * saving us the trouble of clearing the output buffer on
+ * failure later.
+ */
+ if (!pReq)
+ {
+ pReq = usbMsdReqAlloc(pThis);
+ if (!pReq)
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Request allocation failure");
+ pThis->pReq = pReq;
+ }
+ if (!usbMsdReqEnsureBuffer(pThis, pReq, pCbw->dCBWDataTransferLength))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Buffer allocation failure");
+
+ /*
+ * Prepare the request. Kick it off right away if possible.
+ */
+ usbMsdReqPrepare(pReq, pCbw);
+
+ if ( pReq->Cbw.dCBWDataTransferLength == 0
+ || (pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_IN)
+ {
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkHostToDev");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #1");
+ }
+ }
+ else
+ {
+ Log(("usbMsdHandleBulkHostToDev: Entering DATA_FROM_HOST.\n"));
+ pReq->enmState = USBMSDREQSTATE_DATA_FROM_HOST;
+ }
+
+ return usbMsdCompleteOk(pThis, pUrb, pUrb->cbData);
+ }
+
+ /*
+ * Stuff the data into the buffer.
+ */
+ case USBMSDREQSTATE_DATA_FROM_HOST:
+ {
+ uint32_t cbData = pUrb->cbData;
+ uint32_t cbLeft = pReq->Cbw.dCBWDataTransferLength - pReq->offBuf;
+ if (cbData > cbLeft)
+ {
+ Log(("usbMsd: Too much data: cbData=%#x offBuf=%#x dCBWDataTransferLength=%#x cbLeft=%#x\n",
+ cbData, pReq->offBuf, pReq->Cbw.dCBWDataTransferLength, cbLeft));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Too much data");
+ }
+ memcpy(&pReq->pbBuf[pReq->offBuf], &pUrb->abData[0], cbData);
+ pReq->offBuf += cbData;
+
+ if (pReq->offBuf == pReq->Cbw.dCBWDataTransferLength)
+ {
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkHostToDev");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #2");
+ }
+ }
+ return usbMsdCompleteOk(pThis, pUrb, cbData);
+ }
+
+ /*
+ * Bad state, stall.
+ */
+ case USBMSDREQSTATE_DATA_TO_HOST:
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state H2D: DATA_TO_HOST");
+
+ case USBMSDREQSTATE_EXECUTING:
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state H2D: EXECUTING");
+
+ default:
+ AssertMsgFailed(("enmState=%d\n", enmState));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state (H2D)");
+ }
+}
+
+
+/**
+ * Handle requests sent to the inbound (to host) bulk pipe.
+ */
+static int usbMsdHandleBulkDevToHost(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted OR if there is no
+ * pending request yet.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ if (RT_UNLIKELY(pEp->fHalted || !pReq))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, pEp->fHalted ? "Halted pipe" : "No request");
+
+ /*
+ * Deal with the URB according to the state.
+ */
+ switch (pReq->enmState)
+ {
+ /*
+ * We've data left to transfer to the host.
+ */
+ case USBMSDREQSTATE_DATA_TO_HOST:
+ {
+ uint32_t cbData = pUrb->cbData;
+ uint32_t cbCopy = pReq->Cbw.dCBWDataTransferLength - pReq->offBuf;
+ if (cbData <= cbCopy)
+ cbCopy = cbData;
+ else if (pUrb->fShortNotOk)
+ {
+ Log(("usbMsd: Requested more data that we've got; cbData=%#x offBuf=%#x dCBWDataTransferLength=%#x cbLeft=%#x\n",
+ cbData, pReq->offBuf, pReq->Cbw.dCBWDataTransferLength, cbCopy));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Data underrun");
+ }
+ memcpy(&pUrb->abData[0], &pReq->pbBuf[pReq->offBuf], cbCopy);
+ pReq->offBuf += cbCopy;
+
+ if (pReq->offBuf == pReq->Cbw.dCBWDataTransferLength)
+ {
+ Log(("usbMsdHandleBulkDevToHost: Entering STATUS\n"));
+ pReq->enmState = USBMSDREQSTATE_STATUS;
+ }
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBMSDREQSTATE_STATUS:
+ {
+ if ((pUrb->cbData < sizeof(USBCSW)) || (pUrb->cbData > sizeof(USBCSW) && pUrb->fShortNotOk))
+ {
+ Log(("usbMsd: Unexpected status request size: %#x (expected %#x), fShortNotOK=%RTbool\n", pUrb->cbData, sizeof(USBCSW), pUrb->fShortNotOk));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Invalid CSW size");
+ }
+
+ /* Enter a CSW into the URB data buffer. */
+ PUSBCSW pCsw = (PUSBCSW)&pUrb->abData[0];
+ pCsw->dCSWSignature = USBCSW_SIGNATURE;
+ pCsw->dCSWTag = pReq->Cbw.dCBWTag;
+ pCsw->bCSWStatus = pReq->iScsiReqStatus == SCSI_STATUS_OK
+ ? USBCSW_STATUS_OK
+ : pReq->iScsiReqStatus < 0xff
+ ? USBCSW_STATUS_FAILED
+ : USBCSW_STATUS_PHASE_ERROR;
+ /** @todo the following is not always accurate; VSCSI needs
+ * to implement residual counts properly! */
+ if ((pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT)
+ pCsw->dCSWDataResidue = pCsw->bCSWStatus == USBCSW_STATUS_OK
+ ? 0
+ : pReq->Cbw.dCBWDataTransferLength;
+ else
+ pCsw->dCSWDataResidue = pCsw->bCSWStatus == USBCSW_STATUS_OK
+ ? 0
+ : pReq->Cbw.dCBWDataTransferLength;
+ Log(("usbMsd: CSW: dCSWTag=%#x bCSWStatus=%d dCSWDataResidue=%#x\n",
+ pCsw->dCSWTag, pCsw->bCSWStatus, pCsw->dCSWDataResidue));
+
+ Log(("usbMsdHandleBulkDevToHost: Entering READY\n"));
+ pReq->enmState = USBMSDREQSTATE_READY;
+ return usbMsdCompleteOk(pThis, pUrb, sizeof(*pCsw));
+ }
+
+ /*
+ * Status request before we've received all (or even any) data.
+ * Linux 2.4.31 does this sometimes. The recommended behavior is to
+ * to accept the current data amount and execute the request. (The
+ * alternative behavior is to stall.)
+ */
+ case USBMSDREQSTATE_DATA_FROM_HOST:
+ {
+ if (pUrb->cbData != sizeof(USBCSW))
+ {
+ Log(("usbMsdHandleBulkDevToHost: DATA_FROM_HOST; cbData=%#x -> stall\n", pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Invalid CSW size");
+ }
+
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkDevToHost");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #3");
+ }
+ }
+ RT_FALL_THRU();
+
+ /*
+ * The SCSI command is still pending, queue the URB awaiting its
+ * completion.
+ */
+ case USBMSDREQSTATE_EXECUTING:
+ usbMsdQueueAddTail(&pThis->ToHostQueue, pUrb);
+ LogFlow(("usbMsdHandleBulkDevToHost: Added %p:%s to the to-host queue\n", pUrb, pUrb->pszDesc));
+ return VINF_SUCCESS;
+
+ /*
+ * Bad states, stall.
+ */
+ case USBMSDREQSTATE_READY:
+ Log(("usbMsdHandleBulkDevToHost: enmState=READ(%d) (cbData=%#x)\n", pReq->enmState, pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state D2H: READY");
+
+ default:
+ Log(("usbMsdHandleBulkDevToHost: enmState=%d cbData=%#x\n", pReq->enmState, pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Really bad state (D2H)!");
+ }
+}
+
+
+/**
+ * Handles request send to the default control pipe.
+ */
+static int usbMsdHandleDefaultPipe(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ AssertReturn(pUrb->cbData >= sizeof(*pSetup), VERR_VUSB_FAILED_TO_QUEUE_URB);
+
+ if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_STANDARD)
+ {
+ switch (pSetup->bRequest)
+ {
+ case VUSB_REQ_GET_DESCRIPTOR:
+ {
+ if (pSetup->bmRequestType != (VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST))
+ {
+ Log(("usbMsd: Bad GET_DESCRIPTOR req: bmRequestType=%#x\n", pSetup->bmRequestType));
+ return usbMsdCompleteStall(pThis, pEp, pUrb, "Bad GET_DESCRIPTOR");
+ }
+
+ switch (pSetup->wValue >> 8)
+ {
+ uint32_t cbCopy;
+
+ case VUSB_DT_STRING:
+ Log(("usbMsd: GET_DESCRIPTOR DT_STRING wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ case VUSB_DT_DEVICE_QUALIFIER:
+ Log(("usbMsd: GET_DESCRIPTOR DT_DEVICE_QUALIFIER wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbMsdDeviceQualifier));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbMsdDeviceQualifier, cbCopy);
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ case VUSB_DT_BOS:
+ Log(("usbMsd: GET_DESCRIPTOR DT_BOS wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbMsdBOS));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbMsdBOS, cbCopy);
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ default:
+ Log(("usbMsd: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ case VUSB_REQ_CLEAR_FEATURE:
+ break;
+ }
+
+ /** @todo implement this. */
+ Log(("usbMsd: Implement standard request: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+
+ usbMsdCompleteStall(pThis, pEp, pUrb, "TODO: standard request stuff");
+ }
+ /* 3.1 Bulk-Only Mass Storage Reset */
+ else if ( pSetup->bmRequestType == (VUSB_REQ_CLASS | VUSB_TO_INTERFACE)
+ && pSetup->bRequest == 0xff
+ && !pSetup->wValue
+ && !pSetup->wLength
+ && pSetup->wIndex == 0)
+ {
+ Log(("usbMsdHandleDefaultPipe: Bulk-Only Mass Storage Reset\n"));
+ return usbMsdResetWorker(pThis, pUrb, false /*fSetConfig*/);
+ }
+ /* 3.2 Get Max LUN, may stall if we like (but we don't). */
+ else if ( pSetup->bmRequestType == (VUSB_REQ_CLASS | VUSB_TO_INTERFACE | VUSB_DIR_TO_HOST)
+ && pSetup->bRequest == 0xfe
+ && !pSetup->wValue
+ && pSetup->wLength == 1
+ && pSetup->wIndex == 0)
+ {
+ *(uint8_t *)(pSetup + 1) = 0; /* max lun is 0 */
+ usbMsdCompleteOk(pThis, pUrb, 1);
+ }
+ else
+ {
+ Log(("usbMsd: Unknown control msg: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ return usbMsdCompleteStall(pThis, pEp, pUrb, "Unknown control msg");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbMsdQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdQueue/#%u: pUrb=%p:%s EndPt=%#x\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc, pUrb->EndPt));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Parse on a per end-point basis.
+ */
+ int rc;
+ switch (pUrb->EndPt)
+ {
+ case 0:
+ rc = usbMsdHandleDefaultPipe(pThis, &pThis->aEps[0], pUrb);
+ break;
+
+ case 0x81:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x01:
+ rc = usbMsdHandleBulkDevToHost(pThis, &pThis->aEps[1], pUrb);
+ break;
+
+ case 0x02:
+ rc = usbMsdHandleBulkHostToDev(pThis, &pThis->aEps[2], pUrb);
+ break;
+
+ default:
+ AssertMsgFailed(("EndPt=%d\n", pUrb->EndPt));
+ rc = VERR_VUSB_FAILED_TO_QUEUE_URB;
+ break;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint}
+ */
+static DECLCALLBACK(int) usbMsdUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n", pUsbIns->iInstance, uEndpoint));
+
+ if ((uEndpoint & ~0x80) < RT_ELEMENTS(pThis->aEps))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->aEps[(uEndpoint & ~0x80)].fHalted = false;
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface}
+ */
+static DECLCALLBACK(int) usbMsdUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
+{
+ RT_NOREF(pUsbIns, bInterfaceNumber, bAlternateSetting);
+ LogFlow(("usbMsdUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting));
+ Assert(bAlternateSetting == 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration}
+ */
+static DECLCALLBACK(int) usbMsdUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
+ const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
+{
+ RT_NOREF(pvOldCfgDesc, pvOldIfState, pvNewCfgDesc);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbSetConfiguration/#%u: bConfigurationValue=%u\n", pUsbIns->iInstance, bConfigurationValue));
+ Assert(bConfigurationValue == 1);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * If the same config is applied more than once, it's a kind of reset.
+ */
+ if (pThis->bConfigurationValue == bConfigurationValue)
+ usbMsdResetWorker(pThis, NULL, true /*fSetConfig*/); /** @todo figure out the exact difference */
+ pThis->bConfigurationValue = bConfigurationValue;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbMsdUsbGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance));
+ if (pThis->pUsbIns->enmSpeed == VUSB_SPEED_SUPER)
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheSS : &g_UsbMsdDescCacheSS;
+ else if (pThis->pUsbIns->enmSpeed == VUSB_SPEED_HIGH)
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheHS : &g_UsbMsdDescCacheHS;
+ else
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheFS : &g_UsbMsdDescCacheFS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbReset}
+ */
+static DECLCALLBACK(int) usbMsdUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
+{
+ RT_NOREF(fResetOnLinux);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbReset/#%u:\n", pUsbIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnVMSuspend}
+ */
+static DECLCALLBACK(void) usbMsdVMSuspend(PPDMUSBINS pUsbIns)
+{
+ LogFlow(("usbMsdVMSuspend/#%u:\n", pUsbIns->iInstance));
+ usbMsdSuspendOrPowerOff(pUsbIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnVMSuspend}
+ */
+static DECLCALLBACK(void) usbMsdVMPowerOff(PPDMUSBINS pUsbIns)
+{
+ LogFlow(("usbMsdVMPowerOff/#%u:\n", pUsbIns->iInstance));
+ usbMsdSuspendOrPowerOff(pUsbIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDriverAttach}
+ */
+static DECLCALLBACK(int) usbMsdDriverAttach(PPDMUSBINS pUsbIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ LogFlow(("usbMsdDriverAttach/#%u:\n", pUsbIns->iInstance));
+
+ AssertMsg(iLUN == 0, ("UsbMsd: No other LUN than 0 is supported\n"));
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("UsbMsd: Device does not support hotplugging\n"));
+
+ /* the usual paranoia */
+ AssertRelease(!pThis->Lun0.pIBase);
+ AssertRelease(!pThis->Lun0.pIMedia);
+ AssertRelease(!pThis->Lun0.pIMediaEx);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ int rc = PDMUsbHlpDriverAttach(pUsbIns, iLUN, &pThis->Lun0.IBase, &pThis->Lun0.pIBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get media and extended media interface. */
+ pThis->Lun0.pIMedia = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIA);
+ AssertMsgReturn(pThis->Lun0.pIMedia, ("Missing media interface below\n"), VERR_PDM_MISSING_INTERFACE);
+ pThis->Lun0.pIMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIAEX);
+ AssertMsgReturn(pThis->Lun0.pIMediaEx, ("Missing extended media interface below\n"), VERR_PDM_MISSING_INTERFACE);
+
+ rc = pThis->Lun0.pIMediaEx->pfnIoReqAllocSizeSet(pThis->Lun0.pIMediaEx, sizeof(USBMSDREQ));
+ AssertMsgRCReturn(rc, ("MSD failed to set I/O request size!\n"), VERR_PDM_MISSING_INTERFACE);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pThis->Lun0.pIBase = NULL;
+ pThis->Lun0.pIMedia = NULL;
+ pThis->Lun0.pIMediaEx = NULL;
+ }
+
+ pThis->fIsCdrom = false;
+ PDMMEDIATYPE enmType = pThis->Lun0.pIMedia->pfnGetType(pThis->Lun0.pIMedia);
+ /* Anything else will be reported as a hard disk. */
+ if (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD)
+ pThis->fIsCdrom = true;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDriverDetach}
+ */
+static DECLCALLBACK(void) usbMsdDriverDetach(PPDMUSBINS pUsbIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(iLUN, fFlags);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ LogFlow(("usbMsdDriverDetach/#%u:\n", pUsbIns->iInstance));
+
+ AssertMsg(iLUN == 0, ("UsbMsd: No other LUN than 0 is supported\n"));
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("UsbMsd: Device does not support hotplugging\n"));
+
+ if (pThis->pReq)
+ {
+ usbMsdReqFree(pThis, pThis->pReq);
+ pThis->pReq = NULL;
+ }
+
+ /*
+ * Zero some important members.
+ */
+ pThis->Lun0.pIBase = NULL;
+ pThis->Lun0.pIMedia = NULL;
+ pThis->Lun0.pIMediaEx = NULL;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by usbMsdVMReset.}
+ */
+static DECLCALLBACK(bool) usbMsdIsAsyncResetDone(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ return false;
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+ AssertRC(rc);
+ return true;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) usbMsdVMReset(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ PDMUsbHlpSetAsyncNotification(pUsbIns, usbMsdIsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDestruct}
+ */
+static DECLCALLBACK(void) usbMsdDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdDestruct/#%u:\n", pUsbIns->iInstance));
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+ }
+
+ if (pThis->hEvtDoneQueue != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hEvtDoneQueue);
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ }
+
+ if (pThis->hEvtReset != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThis->hEvtReset);
+ pThis->hEvtReset = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) usbMsdConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ RT_NOREF(pCfgGlobal);
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ Log(("usbMsdConstruct/#%u:\n", iInstance));
+
+ /*
+ * Perform the basic structure initialization first so the destructor
+ * will not misbehave.
+ */
+ pThis->pUsbIns = pUsbIns;
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ pThis->hEvtReset = NIL_RTSEMEVENTMULTI;
+ pThis->Lun0.IBase.pfnQueryInterface = usbMsdLun0QueryInterface;
+ pThis->Lun0.IMediaPort.pfnQueryDeviceLocation = usbMsdLun0QueryDeviceLocation;
+ pThis->Lun0.IMediaExPort.pfnIoReqCompleteNotify = usbMsdLun0IoReqCompleteNotify;
+ pThis->Lun0.IMediaExPort.pfnIoReqCopyFromBuf = usbMsdLun0IoReqCopyFromBuf;
+ pThis->Lun0.IMediaExPort.pfnIoReqCopyToBuf = usbMsdLun0IoReqCopyToBuf;
+ pThis->Lun0.IMediaExPort.pfnIoReqQueryDiscardRanges = NULL;
+ pThis->Lun0.IMediaExPort.pfnIoReqStateChanged = usbMsdLun0IoReqStateChanged;
+ pThis->Lun0.IMediaExPort.pfnMediumEjected = usbMsdLun0MediumEjected;
+ usbMsdQueueInit(&pThis->ToHostQueue);
+ usbMsdQueueInit(&pThis->DoneQueue);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventCreate(&pThis->hEvtDoneQueue);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventMultiCreate(&pThis->hEvtReset);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read the configuration.
+ */
+ rc = CFGMR3ValidateConfig(pCfg, "/", "", "", "UsbMsd", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach the SCSI driver.
+ */
+ rc = PDMUsbHlpDriverAttach(pUsbIns, 0 /*iLun*/, &pThis->Lun0.IBase, &pThis->Lun0.pIBase, "SCSI Port");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("MSD failed to attach SCSI driver"));
+ pThis->Lun0.pIMedia = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIA);
+ if (!pThis->Lun0.pIMedia)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
+ N_("MSD failed to query the PDMIMEDIA from the driver below it"));
+ pThis->Lun0.pIMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIAEX);
+ if (!pThis->Lun0.pIMediaEx)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
+ N_("MSD failed to query the PDMIMEDIAEX from the driver below it"));
+
+ /*
+ * Find out what kind of device we are.
+ */
+ pThis->fIsCdrom = false;
+ PDMMEDIATYPE enmType = pThis->Lun0.pIMedia->pfnGetType(pThis->Lun0.pIMedia);
+ /* Anything else will be reported as a hard disk. */
+ if (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD)
+ pThis->fIsCdrom = true;
+
+ rc = pThis->Lun0.pIMediaEx->pfnIoReqAllocSizeSet(pThis->Lun0.pIMediaEx, sizeof(USBMSDREQ));
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("MSD failed to set I/O request size!"));
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMUsbHlpSSMRegister(pUsbIns, USB_MSD_SAVED_STATE_VERSION, sizeof(*pThis),
+ NULL, usbMsdLiveExec, NULL,
+ usbMsdSavePrep, usbMsdSaveExec, NULL,
+ usbMsdLoadPrep, usbMsdLoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS,
+ N_("MSD failed to register SSM save state handlers"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The USB Mass Storage Device (MSD) registration record.
+ */
+const PDMUSBREG g_UsbMsd =
+{
+ /* u32Version */
+ PDM_USBREG_VERSION,
+ /* szName */
+ "Msd",
+ /* pszDescription */
+ "USB Mass Storage Device, one LUN.",
+ /* fFlags */
+ PDM_USBREG_HIGHSPEED_CAPABLE | PDM_USBREG_SUPERSPEED_CAPABLE
+ | PDM_USBREG_SAVED_STATE_SUPPORTED,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(USBMSD),
+ /* pfnConstruct */
+ usbMsdConstruct,
+ /* pfnDestruct */
+ usbMsdDestruct,
+ /* pfnVMInitComplete */
+ NULL,
+ /* pfnVMPowerOn */
+ NULL,
+ /* pfnVMReset */
+ usbMsdVMReset,
+ /* pfnVMSuspend */
+ usbMsdVMSuspend,
+ /* pfnVMResume */
+ NULL,
+ /* pfnVMPowerOff */
+ usbMsdVMPowerOff,
+ /* pfnHotPlugged */
+ NULL,
+ /* pfnHotUnplugged */
+ NULL,
+ /* pfnDriverAttach */
+ usbMsdDriverAttach,
+ /* pfnDriverDetach */
+ usbMsdDriverDetach,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnUsbReset */
+ usbMsdUsbReset,
+ /* pfnUsbGetCachedDescriptors */
+ usbMsdUsbGetDescriptorCache,
+ /* pfnUsbSetConfiguration */
+ usbMsdUsbSetConfiguration,
+ /* pfnUsbSetInterface */
+ usbMsdUsbSetInterface,
+ /* pfnUsbClearHaltedEndpoint */
+ usbMsdUsbClearHaltedEndpoint,
+ /* pfnUrbNew */
+ NULL/*usbMsdUrbNew*/,
+ /* pfnQueue */
+ usbMsdQueue,
+ /* pfnUrbCancel */
+ usbMsdUrbCancel,
+ /* pfnUrbReap */
+ usbMsdUrbReap,
+ /* pfnWakeup */
+ usbMsdWakeup,
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/VBoxSCSI.cpp b/src/VBox/Devices/Storage/VBoxSCSI.cpp
new file mode 100644
index 00000000..d46ed764
--- /dev/null
+++ b/src/VBox/Devices/Storage/VBoxSCSI.cpp
@@ -0,0 +1,532 @@
+/* $Id: VBoxSCSI.cpp $ */
+/** @file
+ * VBox storage devices - Simple SCSI interface for BIOS access.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+//#define DEBUG
+#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC /** @todo Create extra group. */
+
+#if defined(IN_R0) || defined(IN_RC)
+# error This device has no R0 or RC components
+#endif
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/version.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+
+#include "VBoxSCSI.h"
+
+
+/**
+ * Resets the state.
+ */
+static void vboxscsiReset(PVBOXSCSI pVBoxSCSI, bool fEverything)
+{
+ if (fEverything)
+ {
+ pVBoxSCSI->regIdentify = 0;
+ pVBoxSCSI->fBusy = false;
+ }
+ pVBoxSCSI->cbCDB = 0;
+ RT_ZERO(pVBoxSCSI->abCDB);
+ pVBoxSCSI->iCDB = 0;
+ pVBoxSCSI->rcCompletion = 0;
+ pVBoxSCSI->uTargetDevice = 0;
+ pVBoxSCSI->cbBuf = 0;
+ pVBoxSCSI->cbBufLeft = 0;
+ pVBoxSCSI->iBuf = 0;
+ if (pVBoxSCSI->pbBuf)
+ RTMemFree(pVBoxSCSI->pbBuf);
+ pVBoxSCSI->pbBuf = NULL;
+ pVBoxSCSI->enmState = VBOXSCSISTATE_NO_COMMAND;
+}
+
+/**
+ * Initializes the state for the SCSI interface.
+ *
+ * @returns VBox status code.
+ * @param pVBoxSCSI Pointer to the unitialized SCSI state.
+ */
+int vboxscsiInitialize(PVBOXSCSI pVBoxSCSI)
+{
+ pVBoxSCSI->pbBuf = NULL;
+ vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads a register value.
+ *
+ * @returns VBox status code.
+ * @param pVBoxSCSI Pointer to the SCSI state.
+ * @param iRegister Index of the register to read.
+ * @param pu32Value Where to store the content of the register.
+ */
+int vboxscsiReadRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint32_t *pu32Value)
+{
+ uint8_t uVal = 0;
+
+ switch (iRegister)
+ {
+ case 0:
+ {
+ if (ASMAtomicReadBool(&pVBoxSCSI->fBusy) == true)
+ {
+ uVal |= VBOX_SCSI_BUSY;
+ /* There is an I/O operation in progress.
+ * Yield the execution thread to let the I/O thread make progress.
+ */
+ RTThreadYield();
+ }
+ if (pVBoxSCSI->rcCompletion)
+ uVal |= VBOX_SCSI_ERROR;
+ break;
+ }
+ case 1:
+ {
+ /* If we're not in the 'command ready' state, there may not even be a buffer yet. */
+ if ( pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY
+ && pVBoxSCSI->cbBufLeft > 0)
+ {
+ AssertMsg(pVBoxSCSI->pbBuf, ("pBuf is NULL\n"));
+ Assert(!pVBoxSCSI->fBusy);
+ uVal = pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf];
+ pVBoxSCSI->iBuf++;
+ pVBoxSCSI->cbBufLeft--;
+
+ /* When the guest reads the last byte from the data in buffer, clear
+ everything and reset command buffer. */
+ if (pVBoxSCSI->cbBufLeft == 0)
+ vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
+ }
+ break;
+ }
+ case 2:
+ {
+ uVal = pVBoxSCSI->regIdentify;
+ break;
+ }
+ case 3:
+ {
+ uVal = pVBoxSCSI->rcCompletion;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid register to read from %u\n", iRegister));
+ }
+
+ *pu32Value = uVal;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to a register.
+ *
+ * @returns VBox status code.
+ * @retval VERR_MORE_DATA if a command is ready to be sent to the SCSI driver.
+ * @param pVBoxSCSI Pointer to the SCSI state.
+ * @param iRegister Index of the register to write to.
+ * @param uVal Value to write.
+ */
+int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (iRegister)
+ {
+ case 0:
+ {
+ if (pVBoxSCSI->enmState == VBOXSCSISTATE_NO_COMMAND)
+ {
+ pVBoxSCSI->enmState = VBOXSCSISTATE_READ_TXDIR;
+ pVBoxSCSI->uTargetDevice = uVal;
+ }
+ else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_TXDIR)
+ {
+ if (uVal != VBOXSCSI_TXDIR_FROM_DEVICE && uVal != VBOXSCSI_TXDIR_TO_DEVICE)
+ vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
+ else
+ {
+ pVBoxSCSI->enmState = VBOXSCSISTATE_READ_CDB_SIZE_BUFHI;
+ pVBoxSCSI->uTxDir = uVal;
+ }
+ }
+ else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_CDB_SIZE_BUFHI)
+ {
+ uint8_t cbCDB = uVal & 0x0F;
+
+ if (cbCDB == 0)
+ cbCDB = 16;
+ if (cbCDB > VBOXSCSI_CDB_SIZE_MAX)
+ vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
+ else
+ {
+ pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_LSB;
+ pVBoxSCSI->cbCDB = cbCDB;
+ pVBoxSCSI->cbBuf = (uVal & 0xF0) << 12; /* Bits 16-19 of buffer size. */
+ }
+ }
+ else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_LSB)
+ {
+ pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_MID;
+ pVBoxSCSI->cbBuf |= uVal; /* Bits 0-7 of buffer size. */
+ }
+ else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_MID)
+ {
+ pVBoxSCSI->enmState = VBOXSCSISTATE_READ_COMMAND;
+ pVBoxSCSI->cbBuf |= (((uint16_t)uVal) << 8); /* Bits 8-15 of buffer size. */
+ }
+ else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_COMMAND)
+ {
+ pVBoxSCSI->abCDB[pVBoxSCSI->iCDB] = uVal;
+ pVBoxSCSI->iCDB++;
+
+ /* Check if we have all necessary command data. */
+ if (pVBoxSCSI->iCDB == pVBoxSCSI->cbCDB)
+ {
+ Log(("%s: Command ready for processing\n", __FUNCTION__));
+ pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY;
+ pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf;
+ if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
+ {
+ /* This is a write allocate buffer. */
+ pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
+ if (!pVBoxSCSI->pbBuf)
+ return VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* This is a read from the device. */
+ ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
+ rc = VERR_MORE_DATA; /** @todo Better return value to indicate ready command? */
+ }
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid state %d\n", pVBoxSCSI->enmState));
+ break;
+ }
+
+ case 1:
+ {
+ if ( pVBoxSCSI->enmState != VBOXSCSISTATE_COMMAND_READY
+ || pVBoxSCSI->uTxDir != VBOXSCSI_TXDIR_TO_DEVICE)
+ {
+ /* Reset the state */
+ vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
+ }
+ else if (pVBoxSCSI->cbBufLeft > 0)
+ {
+ pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf++] = uVal;
+ pVBoxSCSI->cbBufLeft--;
+ if (pVBoxSCSI->cbBufLeft == 0)
+ {
+ rc = VERR_MORE_DATA;
+ ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
+ }
+ }
+ /* else: Ignore extra data, request pending or something. */
+ break;
+ }
+
+ case 2:
+ {
+ pVBoxSCSI->regIdentify = uVal;
+ break;
+ }
+
+ case 3:
+ {
+ /* Reset */
+ vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid register to write to %u\n", iRegister));
+ }
+
+ return rc;
+}
+
+/**
+ * Sets up a SCSI request which the owning SCSI device can process.
+ *
+ * @returns VBox status code.
+ * @param pVBoxSCSI Pointer to the SCSI state.
+ * @param puLun Where to store the LUN on success.
+ * @param ppbCdb Where to store the pointer to the CDB on success.
+ * @param pcbCdb Where to store the size of the CDB on success.
+ * @param pcbBuf Where to store th size of the data buffer on success.
+ * @param puTargetDevice Where to store the target device ID.
+ */
+int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, uint32_t *puLun, uint8_t **ppbCdb,
+ size_t *pcbCdb, size_t *pcbBuf, uint32_t *puTargetDevice)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pVBoxSCSI=%#p puTargetDevice=%#p\n", pVBoxSCSI, puTargetDevice));
+
+ AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state %u\n", pVBoxSCSI->enmState));
+
+ /* Clear any errors from a previous request. */
+ pVBoxSCSI->rcCompletion = 0;
+
+ if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
+ {
+ if (pVBoxSCSI->pbBuf)
+ RTMemFree(pVBoxSCSI->pbBuf);
+
+ pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
+ if (!pVBoxSCSI->pbBuf)
+ return VERR_NO_MEMORY;
+ }
+
+ *puLun = 0;
+ *ppbCdb = &pVBoxSCSI->abCDB[0];
+ *pcbCdb = pVBoxSCSI->cbCDB;
+ *pcbBuf = pVBoxSCSI->cbBuf;
+ *puTargetDevice = pVBoxSCSI->uTargetDevice;
+
+ return rc;
+}
+
+/**
+ * Notifies the device that a request finished and the incoming data
+ * is ready at the incoming data port.
+ */
+int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, int rcCompletion)
+{
+ LogFlowFunc(("pVBoxSCSI=%#p\n", pVBoxSCSI));
+
+ if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
+ vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
+
+ pVBoxSCSI->rcCompletion = rcCompletion;
+
+ ASMAtomicXchgBool(&pVBoxSCSI->fBusy, false);
+
+ return VINF_SUCCESS;
+}
+
+size_t vboxscsiCopyToBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ AssertPtrReturn(pVBoxSCSI->pbBuf, 0);
+ AssertReturn(cbSkip + cbCopy <= pVBoxSCSI->cbBuf, 0);
+
+ void *pvBuf = pVBoxSCSI->pbBuf + cbSkip;
+ return RTSgBufCopyToBuf(pSgBuf, pvBuf, cbCopy);
+}
+
+size_t vboxscsiCopyFromBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ AssertPtrReturn(pVBoxSCSI->pbBuf, 0);
+ AssertReturn(cbSkip + cbCopy <= pVBoxSCSI->cbBuf, 0);
+
+ void *pvBuf = pVBoxSCSI->pbBuf + cbSkip;
+ return RTSgBufCopyFromBuf(pSgBuf, pvBuf, cbCopy);
+}
+
+int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+ uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pDevIns);
+ LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfers=%u cb=%u\n",
+ pDevIns, pVBoxSCSI, iRegister, *pcTransfers, cb));
+
+ /*
+ * Check preconditions, fall back to non-string I/O handler.
+ */
+ Assert(*pcTransfers > 0);
+
+ /* Read string only valid for data in register. */
+ AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be read from with string!\n"), VINF_SUCCESS);
+
+ /* Accesses without a valid buffer will be ignored. */
+ AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
+
+ /* Check state. */
+ AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
+ Assert(!pVBoxSCSI->fBusy);
+
+ /*
+ * Also ignore attempts to read more data than is available.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbTransfer = *pcTransfers * cb;
+ if (pVBoxSCSI->cbBufLeft > 0)
+ {
+ Assert(cbTransfer <= pVBoxSCSI->cbBuf);
+ if (cbTransfer > pVBoxSCSI->cbBuf)
+ {
+ memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf);
+ cbTransfer = pVBoxSCSI->cbBuf; /* Ignore excess data (not supposed to happen). */
+ }
+
+ /* Copy the data and adance the buffer position. */
+ memcpy(pbDst, pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, cbTransfer);
+
+ /* Advance current buffer position. */
+ pVBoxSCSI->iBuf += cbTransfer;
+ pVBoxSCSI->cbBufLeft -= cbTransfer;
+
+ /* When the guest reads the last byte from the data in buffer, clear
+ everything and reset command buffer. */
+ if (pVBoxSCSI->cbBufLeft == 0)
+ vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
+ }
+ else
+ {
+ AssertFailed();
+ memset(pbDst, 0, cbTransfer);
+ }
+ *pcTransfers = 0;
+
+ return rc;
+}
+
+int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+ uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
+{
+ RT_NOREF(pDevIns);
+
+ /*
+ * Check preconditions, fall back to non-string I/O handler.
+ */
+ Assert(*pcTransfers > 0);
+ /* Write string only valid for data in/out register. */
+ AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be written to with string!\n"), VINF_SUCCESS);
+
+ /* Accesses without a valid buffer will be ignored. */
+ AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
+
+ /* State machine assumptions. */
+ AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
+ AssertReturn(pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE, VINF_SUCCESS);
+
+ /*
+ * Ignore excess data (not supposed to happen).
+ */
+ int rc = VINF_SUCCESS;
+ if (pVBoxSCSI->cbBufLeft > 0)
+ {
+ uint32_t cbTransfer = RT_MIN(*pcTransfers * cb, pVBoxSCSI->cbBufLeft);
+
+ /* Copy the data and adance the buffer position. */
+ memcpy(pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, pbSrc, cbTransfer);
+ pVBoxSCSI->iBuf += cbTransfer;
+ pVBoxSCSI->cbBufLeft -= cbTransfer;
+
+ /* If we've reached the end, tell the caller to submit the command. */
+ if (pVBoxSCSI->cbBufLeft == 0)
+ {
+ ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
+ rc = VERR_MORE_DATA;
+ }
+ }
+ else
+ AssertFailed();
+ *pcTransfers = 0;
+
+ return rc;
+}
+
+void vboxscsiSetRequestRedo(PVBOXSCSI pVBoxSCSI)
+{
+ AssertMsg(pVBoxSCSI->fBusy, ("No request to redo\n"));
+
+ if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
+ {
+ AssertPtr(pVBoxSCSI->pbBuf);
+ }
+}
+
+DECLHIDDEN(int) vboxscsiR3LoadExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
+{
+ SSMR3GetU8 (pSSM, &pVBoxSCSI->regIdentify);
+ SSMR3GetU8 (pSSM, &pVBoxSCSI->uTargetDevice);
+ SSMR3GetU8 (pSSM, &pVBoxSCSI->uTxDir);
+ SSMR3GetU8 (pSSM, &pVBoxSCSI->cbCDB);
+
+ /*
+ * The CDB buffer was increased with r104155 in trunk (backported to 5.0
+ * in r104311) without bumping the SSM state versions which leaves us
+ * with broken saved state restoring for older VirtualBox releases
+ * (up to 5.0.10).
+ */
+ if ( ( SSMR3HandleRevision(pSSM) < 104311
+ && SSMR3HandleVersion(pSSM) < VBOX_FULL_VERSION_MAKE(5, 0, 12))
+ || ( SSMR3HandleRevision(pSSM) < 104155
+ && SSMR3HandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 0, 51)))
+ {
+ memset(&pVBoxSCSI->abCDB[0], 0, sizeof(pVBoxSCSI->abCDB));
+ SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], 12);
+ }
+ else
+ SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], sizeof(pVBoxSCSI->abCDB));
+
+ SSMR3GetU8 (pSSM, &pVBoxSCSI->iCDB);
+ SSMR3GetU32 (pSSM, &pVBoxSCSI->cbBufLeft);
+ SSMR3GetU32 (pSSM, &pVBoxSCSI->iBuf);
+ SSMR3GetBool(pSSM, (bool *)&pVBoxSCSI->fBusy);
+ SSMR3GetU8 (pSSM, (uint8_t *)&pVBoxSCSI->enmState);
+
+ /*
+ * Old saved states only save the size of the buffer left to read/write.
+ * To avoid changing the saved state version we can just calculate the original
+ * buffer size from the offset and remaining size.
+ */
+ pVBoxSCSI->cbBuf = pVBoxSCSI->cbBufLeft + pVBoxSCSI->iBuf;
+
+ if (pVBoxSCSI->cbBuf)
+ {
+ pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
+ if (!pVBoxSCSI->pbBuf)
+ return VERR_NO_MEMORY;
+
+ SSMR3GetMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
+ }
+
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) vboxscsiR3SaveExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
+{
+ SSMR3PutU8 (pSSM, pVBoxSCSI->regIdentify);
+ SSMR3PutU8 (pSSM, pVBoxSCSI->uTargetDevice);
+ SSMR3PutU8 (pSSM, pVBoxSCSI->uTxDir);
+ SSMR3PutU8 (pSSM, pVBoxSCSI->cbCDB);
+ SSMR3PutMem (pSSM, pVBoxSCSI->abCDB, sizeof(pVBoxSCSI->abCDB));
+ SSMR3PutU8 (pSSM, pVBoxSCSI->iCDB);
+ SSMR3PutU32 (pSSM, pVBoxSCSI->cbBufLeft);
+ SSMR3PutU32 (pSSM, pVBoxSCSI->iBuf);
+ SSMR3PutBool (pSSM, pVBoxSCSI->fBusy);
+ SSMR3PutU8 (pSSM, pVBoxSCSI->enmState);
+
+ if (pVBoxSCSI->cbBuf)
+ SSMR3PutMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Devices/Storage/VBoxSCSI.h b/src/VBox/Devices/Storage/VBoxSCSI.h
new file mode 100644
index 00000000..4d559d7b
--- /dev/null
+++ b/src/VBox/Devices/Storage/VBoxSCSI.h
@@ -0,0 +1,158 @@
+/* $Id: VBoxSCSI.h $ */
+/** @file
+ * VBox storage devices - Simple SCSI interface for BIOS access.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/** @page pg_drv_scsi Simple SCSI interface for BIOS access.
+ *
+ * This is a simple interface to access SCSI devices from the BIOS which is
+ * shared between the BusLogic and the LsiLogic SCSI host adapters to simplify
+ * the BIOS part.
+ *
+ * The first interface (if available) will be starting at port 0x430 and
+ * each will occupy 4 ports. The ports are used as described below:
+ *
+ * +--------+--------+----------+
+ * | Offset | Access | Purpose |
+ * +--------+--------+----------+
+ * | 0 | Write | Command |
+ * +--------+--------+----------+
+ * | 0 | Read | Status |
+ * +--------+--------+----------+
+ * | 1 | Write | Data in |
+ * +--------+--------+----------+
+ * | 1 | Read | Data out |
+ * +--------+--------+----------+
+ * | 2 | R/W | Detect |
+ * +--------+--------+----------+
+ * | 3 | Read | SCSI rc |
+ * +--------+--------+----------+
+ * | 3 | Write | Reset |
+ * +--------+--------+----------+
+ *
+ * The register at port 0 receives the SCSI CDB issued from the driver when
+ * writing to it but before writing the actual CDB the first write gives the
+ * size of the CDB in bytes.
+ *
+ * Reading the port at offset 0 gives status information about the adapter. If
+ * the busy bit is set the adapter is processing a previous issued request if it is
+ * cleared the command finished and the adapter can process another request.
+ * The driver has to poll this bit because the adapter will not assert an IRQ
+ * for simplicity reasons.
+ *
+ * The register at offset 2 is to detect if a host adapter is available. If the
+ * driver writes a value to this port and gets the same value after reading it
+ * again the adapter is available.
+ *
+ * Any write to the register at offset 3 causes the interface to be reset. A
+ * read returns the SCSI status code of the last operation.
+ *
+ * This part has no R0 or RC components.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VBoxSCSI_h
+#define VBOX_INCLUDED_SRC_Storage_VBoxSCSI_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+//#define DEBUG
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/scsi.h>
+
+typedef enum VBOXSCSISTATE
+{
+ VBOXSCSISTATE_NO_COMMAND = 0x00,
+ VBOXSCSISTATE_READ_TXDIR = 0x01,
+ VBOXSCSISTATE_READ_CDB_SIZE_BUFHI = 0x02,
+ VBOXSCSISTATE_READ_BUFFER_SIZE_LSB = 0x03,
+ VBOXSCSISTATE_READ_BUFFER_SIZE_MID = 0x04,
+ VBOXSCSISTATE_READ_COMMAND = 0x05,
+ VBOXSCSISTATE_COMMAND_READY = 0x06
+} VBOXSCSISTATE;
+
+#define VBOXSCSI_TXDIR_FROM_DEVICE 0
+#define VBOXSCSI_TXDIR_TO_DEVICE 1
+
+/** Maximum CDB size the BIOS driver sends. */
+#define VBOXSCSI_CDB_SIZE_MAX 16
+
+typedef struct VBOXSCSI
+{
+ /** The identify register. */
+ uint8_t regIdentify;
+ /** The target device. */
+ uint8_t uTargetDevice;
+ /** Transfer direction. */
+ uint8_t uTxDir;
+ /** The size of the CDB we are issuing. */
+ uint8_t cbCDB;
+ /** The command to issue. */
+ uint8_t abCDB[VBOXSCSI_CDB_SIZE_MAX + 4];
+ /** Current position in the array. */
+ uint8_t iCDB;
+
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0;
+#endif
+
+ /** Pointer to the buffer holding the data. */
+ R3PTRTYPE(uint8_t *) pbBuf;
+ /** Size of the buffer in bytes. */
+ uint32_t cbBuf;
+ /** The number of bytes left to read/write in the
+ * buffer. It is decremented when the guest (BIOS) accesses
+ * the buffer data. */
+ uint32_t cbBufLeft;
+ /** Current position in the buffer (offBuf if you like). */
+ uint32_t iBuf;
+ /** The result code of last operation. */
+ int32_t rcCompletion;
+ /** Flag whether a request is pending. */
+ volatile bool fBusy;
+ /** The state we are in when fetching a command from the BIOS. */
+ VBOXSCSISTATE enmState;
+} VBOXSCSI, *PVBOXSCSI;
+
+#define VBOX_SCSI_BUSY RT_BIT(0)
+#define VBOX_SCSI_ERROR RT_BIT(1)
+
+#ifdef IN_RING3
+RT_C_DECLS_BEGIN
+int vboxscsiInitialize(PVBOXSCSI pVBoxSCSI);
+int vboxscsiReadRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint32_t *pu32Value);
+int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal);
+int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, uint32_t *puLun, uint8_t **ppbCdb, size_t *pcbCdb,
+ size_t *pcbBuf, uint32_t *puTargetDevice);
+int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, int rcCompletion);
+size_t vboxscsiCopyToBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy);
+size_t vboxscsiCopyFromBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy);
+void vboxscsiSetRequestRedo(PVBOXSCSI pVBoxSCSI);
+int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+ uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb);
+int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
+ uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb);
+
+DECLHIDDEN(int) vboxscsiR3LoadExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM);
+DECLHIDDEN(int) vboxscsiR3SaveExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM);
+RT_C_DECLS_END
+#endif /* IN_RING3 */
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VBoxSCSI_h */
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp
new file mode 100644
index 00000000..ad632bbc
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp
@@ -0,0 +1,418 @@
+/* $Id: VSCSIDevice.cpp $ */
+/** @file
+ * Virtual SCSI driver: Device handling
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "VSCSIInternal.h"
+
+/**
+ * Checks if a specific LUN exists fir the SCSI device
+ *
+ * @returns true if the LUN is present
+ * false otherwise
+ * @param pVScsiDevice The SCSI device instance.
+ * @param iLun The LUN to check for.
+ */
+DECLINLINE(bool) vscsiDeviceLunIsPresent(PVSCSIDEVICEINT pVScsiDevice, uint32_t iLun)
+{
+ return ( iLun < pVScsiDevice->cLunsMax
+ && pVScsiDevice->papVScsiLun[iLun] != NULL);
+}
+
+/**
+ * Process a request common for all device types.
+ *
+ * @returns Flag whether we could handle the request.
+ * @param pVScsiDevice The virtual SCSI device instance.
+ * @param pVScsiReq The SCSi request.
+ * @param prcReq The final return value if the request was handled.
+ */
+static bool vscsiDeviceReqProcess(PVSCSIDEVICEINT pVScsiDevice, PVSCSIREQINT pVScsiReq,
+ int *prcReq)
+{
+ bool fProcessed = true;
+
+ switch (pVScsiReq->pbCDB[0])
+ {
+ case SCSI_INQUIRY:
+ {
+ if (!vscsiDeviceLunIsPresent(pVScsiDevice, pVScsiReq->iLun))
+ {
+ size_t cbData;
+ SCSIINQUIRYDATA ScsiInquiryReply;
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[3]));
+ memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+ ScsiInquiryReply.cbAdditional = 31;
+ ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
+ ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
+ cbData = RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, (uint8_t *)&ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+ *prcReq = vscsiReqSenseOkSet(&pVScsiDevice->VScsiSense, pVScsiReq);
+ }
+ else
+ fProcessed = false; /* Let the LUN process the request because it will provide LUN specific data */
+
+ break;
+ }
+ case SCSI_REPORT_LUNS:
+ {
+ /*
+ * If allocation length is less than 16 bytes SPC compliant devices have
+ * to return an error.
+ */
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U32(&pVScsiReq->pbCDB[6]));
+ if (pVScsiReq->cbXfer < 16)
+ *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ else
+ {
+ size_t cbData;
+ uint8_t aReply[16]; /* We report only one LUN. */
+
+ memset(aReply, 0, sizeof(aReply));
+ scsiH2BE_U32(&aReply[0], 8); /* List length starts at position 0. */
+ cbData = RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ if (cbData < 16)
+ *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ else
+ *prcReq = vscsiReqSenseOkSet(&pVScsiDevice->VScsiSense, pVScsiReq);
+ }
+ break;
+ }
+ case SCSI_TEST_UNIT_READY:
+ {
+ if ( vscsiDeviceLunIsPresent(pVScsiDevice, pVScsiReq->iLun)
+ && pVScsiDevice->papVScsiLun[pVScsiReq->iLun]->fReady)
+ *prcReq = vscsiReqSenseOkSet(&pVScsiDevice->VScsiSense, pVScsiReq);
+ else
+ fProcessed = false; /* The LUN (if present) will provide details. */
+ break;
+ }
+ case SCSI_REQUEST_SENSE:
+ {
+ vscsiReqSetXferSize(pVScsiReq, pVScsiReq->pbCDB[4]);
+
+ /* Descriptor format sense data is not supported and results in an error. */
+ if ((pVScsiReq->pbCDB[1] & 0x1) != 0)
+ *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ else
+ *prcReq = vscsiReqSenseCmd(&pVScsiDevice->VScsiSense, pVScsiReq);
+ break;
+ }
+#if 0
+ case SCSI_MAINTENANCE_IN:
+ {
+ if (pVScsiReq->pbCDB[1] == SCSI_MAINTENANCE_IN_REPORT_SUPP_OPC)
+ {
+ /*
+ * If the LUN is present and has the CDB info set we will execute the command, otherwise
+ * just fail with an illegal request error.
+ */
+ if (vscsiDeviceLunIsPresent(pVScsiDevice, pVScsiReq->iLun))
+ {
+ PVSCSILUNINT pVScsiLun = pVScsiDevice->papVScsiLun[pVScsiReq->iLun];
+ if (pVScsiLun->pVScsiLunDesc->paSupOpcInfo)
+ {
+ bool fTimeoutDesc = RT_BOOL(pVScsiReq->pbCDB[2] & 0x80);
+ uint8_t u8ReportMode = pVScsiReq->pbCDB[2] & 0x7;
+ uint8_t u8Opc = pVScsiReq->pbCDB[3];
+ uint16_t u16SvcAction = scsiBE2H_U16(&pVScsiReq->pbCDB[4]);
+ uint16_t cbData = scsiBE2H_U16(&pVScsiReq->pbCDB[6]);
+
+ switch (u8ReportMode)
+ {
+ case 0:
+ *prcReq = vscsiDeviceReportAllSupportedOpc(pVScsiLun, pVScsiReq, fTimeoutDesc, cbData);
+ break;
+ case 1:
+ *prcReq = vscsiDeviceReportOpc(pVScsiLun, pVScsiReq, u8Opc, fTimeoutDesc, cbData);
+ break;
+ case 2:
+ *prcReq = vscsiDeviceReportOpc(pVScsiLun, pVScsiReq, u8Opc, fTimeoutDesc, cbData);
+ break;
+ default:
+ *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq,
+ SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ }
+ else
+ *prcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+ }
+ else
+ *prcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+ }
+ else
+ fProcessed = false; /* Might also be the SEND KEY MMC command. */
+ }
+#endif
+ default:
+ fProcessed = false;
+ }
+
+ return fProcessed;
+}
+
+
+void vscsiDeviceReqComplete(PVSCSIDEVICEINT pVScsiDevice, PVSCSIREQINT pVScsiReq,
+ int rcScsiCode, bool fRedoPossible, int rcReq)
+{
+ pVScsiDevice->pfnVScsiReqCompleted(pVScsiDevice, pVScsiDevice->pvVScsiDeviceUser,
+ pVScsiReq->pvVScsiReqUser, rcScsiCode, fRedoPossible,
+ rcReq, pVScsiReq->cbXfer);
+
+ if (pVScsiReq->pvLun)
+ {
+ if (vscsiDeviceLunIsPresent(pVScsiDevice, pVScsiReq->iLun))
+ {
+ PVSCSILUNINT pVScsiLun = pVScsiDevice->papVScsiLun[pVScsiReq->iLun];
+ pVScsiLun->pVScsiLunDesc->pfnVScsiLunReqFree(pVScsiLun, pVScsiReq, pVScsiReq->pvLun);
+ }
+ else
+ AssertLogRelMsgFailed(("vscsiDeviceReqComplete: LUN %u for VSCSI request %#p is not present but there is LUN specific data allocated\n",
+ pVScsiReq->iLun, pVScsiReq));
+
+ pVScsiReq->pvLun = NULL;
+ }
+
+ RTMemCacheFree(pVScsiDevice->hCacheReq, pVScsiReq);
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceCreate(PVSCSIDEVICE phVScsiDevice,
+ PFNVSCSIREQCOMPLETED pfnVScsiReqCompleted,
+ void *pvVScsiDeviceUser)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIDEVICEINT pVScsiDevice = NULL;
+
+ AssertPtrReturn(phVScsiDevice, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnVScsiReqCompleted, VERR_INVALID_POINTER);
+
+ pVScsiDevice = (PVSCSIDEVICEINT)RTMemAllocZ(sizeof(VSCSIDEVICEINT));
+ if (!pVScsiDevice)
+ return VERR_NO_MEMORY;
+
+ pVScsiDevice->pfnVScsiReqCompleted = pfnVScsiReqCompleted;
+ pVScsiDevice->pvVScsiDeviceUser = pvVScsiDeviceUser;
+ pVScsiDevice->cLunsAttached = 0;
+ pVScsiDevice->cLunsMax = 0;
+ pVScsiDevice->papVScsiLun = NULL;
+ vscsiSenseInit(&pVScsiDevice->VScsiSense);
+
+ rc = RTMemCacheCreate(&pVScsiDevice->hCacheReq, sizeof(VSCSIREQINT), 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ *phVScsiDevice = pVScsiDevice;
+ LogFlow(("%s: hVScsiDevice=%#p -> VINF_SUCCESS\n", __FUNCTION__, pVScsiDevice));
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pVScsiDevice);
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceDestroy(VSCSIDEVICE hVScsiDevice)
+{
+ AssertPtrReturn(hVScsiDevice, VERR_INVALID_HANDLE);
+
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+
+ if (pVScsiDevice->cLunsAttached > 0)
+ return VERR_VSCSI_LUN_ATTACHED_TO_DEVICE;
+
+ if (pVScsiDevice->papVScsiLun)
+ RTMemFree(pVScsiDevice->papVScsiLun);
+
+ RTMemCacheDestroy(pVScsiDevice->hCacheReq);
+ RTMemFree(pVScsiDevice);
+
+ return VINF_SUCCESS;;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceLunAttach(VSCSIDEVICE hVScsiDevice, VSCSILUN hVScsiLun, uint32_t iLun)
+{
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+ PVSCSILUNINT pVScsiLun = (PVSCSILUNINT)hVScsiLun;
+ int rc = VINF_SUCCESS;
+
+ /* Parameter checks */
+ AssertPtrReturn(pVScsiDevice, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pVScsiLun, VERR_INVALID_HANDLE);
+ AssertReturn(iLun < VSCSI_DEVICE_LUN_MAX, VERR_VSCSI_LUN_INVALID);
+ AssertReturn(!pVScsiLun->pVScsiDevice, VERR_VSCSI_LUN_ATTACHED_TO_DEVICE);
+
+ if (iLun >= pVScsiDevice->cLunsMax)
+ {
+ PPVSCSILUNINT papLunOld = pVScsiDevice->papVScsiLun;
+
+ pVScsiDevice->papVScsiLun = (PPVSCSILUNINT)RTMemAllocZ((iLun + 1) * sizeof(PVSCSILUNINT));
+ if (pVScsiDevice->papVScsiLun)
+ {
+ for (uint32_t i = 0; i < pVScsiDevice->cLunsMax; i++)
+ pVScsiDevice->papVScsiLun[i] = papLunOld[i];
+
+ if (papLunOld)
+ RTMemFree(papLunOld);
+
+ pVScsiDevice->cLunsMax = iLun + 1;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pVScsiLun->pVScsiDevice = pVScsiDevice;
+ pVScsiDevice->papVScsiLun[iLun] = pVScsiLun;
+ pVScsiDevice->cLunsAttached++;
+ }
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceLunDetach(VSCSIDEVICE hVScsiDevice, uint32_t iLun,
+ PVSCSILUN phVScsiLun)
+{
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+
+ /* Parameter checks */
+ AssertPtrReturn(pVScsiDevice, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVScsiLun, VERR_INVALID_POINTER);
+ AssertReturn(iLun < VSCSI_DEVICE_LUN_MAX, VERR_VSCSI_LUN_INVALID);
+ AssertReturn(iLun < pVScsiDevice->cLunsMax, VERR_VSCSI_LUN_NOT_ATTACHED);
+ AssertPtrReturn(pVScsiDevice->papVScsiLun[iLun], VERR_VSCSI_LUN_NOT_ATTACHED);
+
+ PVSCSILUNINT pVScsiLun = pVScsiDevice->papVScsiLun[iLun];
+
+ pVScsiLun->pVScsiDevice = NULL;
+ *phVScsiLun = pVScsiLun;
+ pVScsiDevice->papVScsiLun[iLun] = NULL;
+ pVScsiDevice->cLunsAttached--;
+
+ return VINF_SUCCESS;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceLunQueryType(VSCSIDEVICE hVScsiDevice, uint32_t iLun,
+ PVSCSILUNTYPE pEnmLunType)
+{
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+
+ /* Parameter checks */
+ AssertPtrReturn(pVScsiDevice, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pEnmLunType, VERR_INVALID_POINTER);
+ AssertReturn(iLun < VSCSI_DEVICE_LUN_MAX, VERR_VSCSI_LUN_INVALID);
+ AssertReturn(iLun < pVScsiDevice->cLunsMax, VERR_VSCSI_LUN_NOT_ATTACHED);
+ AssertPtrReturn(pVScsiDevice->papVScsiLun[iLun], VERR_VSCSI_LUN_NOT_ATTACHED);
+
+ PVSCSILUNINT hVScsiLun = pVScsiDevice->papVScsiLun[iLun];
+ *pEnmLunType = hVScsiLun->pVScsiLunDesc->enmLunType;
+
+ return VINF_SUCCESS;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceReqEnqueue(VSCSIDEVICE hVScsiDevice, VSCSIREQ hVScsiReq)
+{
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+ PVSCSIREQINT pVScsiReq = (PVSCSIREQINT)hVScsiReq;
+ int rc = VINF_SUCCESS;
+
+ /* Parameter checks */
+ AssertPtrReturn(pVScsiDevice, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pVScsiReq, VERR_INVALID_HANDLE);
+
+ /* Check if this request can be handled by us */
+ int rcReq;
+ bool fProcessed = vscsiDeviceReqProcess(pVScsiDevice, pVScsiReq, &rcReq);
+ if (!fProcessed)
+ {
+ /* Pass to the LUN driver */
+ if (vscsiDeviceLunIsPresent(pVScsiDevice, pVScsiReq->iLun))
+ {
+ PVSCSILUNINT pVScsiLun = pVScsiDevice->papVScsiLun[pVScsiReq->iLun];
+ rc = pVScsiLun->pVScsiLunDesc->pfnVScsiLunReqProcess(pVScsiLun, pVScsiReq);
+ }
+ else
+ {
+ /* LUN not present, report error. */
+ vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq,
+ SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_LOGICAL_UNIT_DOES_NOT_RESPOND_TO_SELECTION,
+ 0x00);
+
+ vscsiDeviceReqComplete(pVScsiDevice, pVScsiReq,
+ SCSI_STATUS_CHECK_CONDITION, false, VINF_SUCCESS);
+ }
+ }
+ else
+ vscsiDeviceReqComplete(pVScsiDevice, pVScsiReq,
+ rcReq, false, VINF_SUCCESS);
+
+ return rc;
+}
+
+
+VBOXDDU_DECL(int) VSCSIDeviceReqCreate(VSCSIDEVICE hVScsiDevice, PVSCSIREQ phVScsiReq,
+ uint32_t iLun, uint8_t *pbCDB, size_t cbCDB,
+ size_t cbSGList, unsigned cSGListEntries,
+ PCRTSGSEG paSGList, uint8_t *pbSense,
+ size_t cbSense, void *pvVScsiReqUser)
+{
+ RT_NOREF1(cbSGList);
+ PVSCSIDEVICEINT pVScsiDevice = (PVSCSIDEVICEINT)hVScsiDevice;
+ PVSCSIREQINT pVScsiReq = NULL;
+
+ /* Parameter checks */
+ AssertPtrReturn(pVScsiDevice, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVScsiReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pbCDB, VERR_INVALID_PARAMETER);
+ AssertReturn(cbCDB > 0, VERR_INVALID_PARAMETER);
+
+ pVScsiReq = (PVSCSIREQINT)RTMemCacheAlloc(pVScsiDevice->hCacheReq);
+ if (!pVScsiReq)
+ return VERR_NO_MEMORY;
+
+ pVScsiReq->iLun = iLun;
+ pVScsiReq->pbCDB = pbCDB;
+ pVScsiReq->cbCDB = cbCDB;
+ pVScsiReq->pbSense = pbSense;
+ pVScsiReq->cbSense = cbSense;
+ pVScsiReq->pvVScsiReqUser = pvVScsiReqUser;
+ pVScsiReq->cbXfer = 0;
+ pVScsiReq->pvLun = NULL;
+ RTSgBufInit(&pVScsiReq->SgBuf, paSGList, cSGListEntries);
+
+ *phVScsiReq = pVScsiReq;
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h b/src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h
new file mode 100644
index 00000000..a62e626d
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h
@@ -0,0 +1,693 @@
+/* $Id: VSCSIInternal.h $ */
+/** @file
+ * Virtual SCSI driver: Internal defines
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIInternal_h
+#define VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vscsi.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+#include <iprt/err.h>
+#include <iprt/memcache.h>
+#include <iprt/sg.h>
+#include <iprt/list.h>
+
+#include "VSCSIVpdPages.h"
+
+/** Pointer to an internal virtual SCSI device. */
+typedef VSCSIDEVICEINT *PVSCSIDEVICEINT;
+/** Pointer to an internal virtual SCSI device LUN. */
+typedef VSCSILUNINT *PVSCSILUNINT;
+/** Pointer to an internal virtual SCSI device LUN pointer. */
+typedef PVSCSILUNINT *PPVSCSILUNINT;
+/** Pointer to a virtual SCSI LUN descriptor. */
+typedef struct VSCSILUNDESC *PVSCSILUNDESC;
+/** Pointer to a virtual SCSI request. */
+typedef VSCSIREQINT *PVSCSIREQINT;
+/** Pointer to a virtual SCSI I/O request. */
+typedef VSCSIIOREQINT *PVSCSIIOREQINT;
+/** Pointer to virtual SCSI sense data state. */
+typedef struct VSCSISENSE *PVSCSISENSE;
+
+/**
+ * Virtual SCSI sense data handling.
+ */
+typedef struct VSCSISENSE
+{
+ /** Buffer holding the sense data. */
+ uint8_t abSenseBuf[32];
+} VSCSISENSE;
+
+/**
+ * Virtual SCSI device.
+ */
+typedef struct VSCSIDEVICEINT
+{
+ /** Request completion callback */
+ PFNVSCSIREQCOMPLETED pfnVScsiReqCompleted;
+ /** Opaque user data. */
+ void *pvVScsiDeviceUser;
+ /** Number of LUNs currently attached. */
+ uint32_t cLunsAttached;
+ /** How many LUNs are fitting in the array. */
+ uint32_t cLunsMax;
+ /** Request cache */
+ RTMEMCACHE hCacheReq;
+ /** Sense data handling. */
+ VSCSISENSE VScsiSense;
+ /** Pointer to the array of LUN handles.
+ * The index is the LUN id. */
+ PPVSCSILUNINT papVScsiLun;
+} VSCSIDEVICEINT;
+
+/**
+ * Virtual SCSI device LUN.
+ */
+typedef struct VSCSILUNINT
+{
+ /** Pointer to the parent SCSI device. */
+ PVSCSIDEVICEINT pVScsiDevice;
+ /** Opaque user data */
+ void *pvVScsiLunUser;
+ /** I/O callback table */
+ PVSCSILUNIOCALLBACKS pVScsiLunIoCallbacks;
+ /** Pointer to the LUN type descriptor. */
+ PVSCSILUNDESC pVScsiLunDesc;
+ /** Flag indicating whether LUN is ready. */
+ bool fReady;
+ /** Flag indicating media presence in LUN. */
+ bool fMediaPresent;
+ /** Flags of supported features. */
+ uint64_t fFeatures;
+ /** I/O request processing data */
+ struct
+ {
+ /** Number of outstanding tasks on this LUN. */
+ volatile uint32_t cReqOutstanding;
+ } IoReq;
+} VSCSILUNINT;
+
+/**
+ * Virtual SCSI request.
+ */
+typedef struct VSCSIREQINT
+{
+ /** The LUN the request is for. */
+ uint32_t iLun;
+ /** The CDB */
+ uint8_t *pbCDB;
+ /** Size of the CDB */
+ size_t cbCDB;
+ /** S/G buffer. */
+ RTSGBUF SgBuf;
+ /** Pointer to the sense buffer. */
+ uint8_t *pbSense;
+ /** Size of the sense buffer */
+ size_t cbSense;
+ /** Opaque user data associated with this request */
+ void *pvVScsiReqUser;
+ /** Transfer size determined from the CDB. */
+ size_t cbXfer;
+ /** Pointer to the opaque data which may be allocated by the LUN
+ * the request is for. */
+ void *pvLun;
+} VSCSIREQINT;
+
+/**
+ * Virtual SCSI I/O request.
+ */
+typedef struct VSCSIIOREQINT
+{
+ /** The associated request. */
+ PVSCSIREQINT pVScsiReq;
+ /** Lun for this I/O request. */
+ PVSCSILUNINT pVScsiLun;
+ /** Transfer direction */
+ VSCSIIOREQTXDIR enmTxDir;
+ /** Direction dependent data. */
+ union
+ {
+ /** Read/Write request. */
+ struct
+ {
+ /** Start offset */
+ uint64_t uOffset;
+ /** Number of bytes to transfer */
+ size_t cbTransfer;
+ /** Number of bytes the S/G list holds */
+ size_t cbSeg;
+ /** Number of segments. */
+ unsigned cSeg;
+ /** Segment array. */
+ PCRTSGSEG paSeg;
+ } Io;
+ /** Unmap request. */
+ struct
+ {
+ /** Array of ranges to unmap. */
+ PRTRANGE paRanges;
+ /** Number of ranges. */
+ unsigned cRanges;
+ } Unmap;
+ } u;
+} VSCSIIOREQINT;
+
+/**
+ * VPD page pool.
+ */
+typedef struct VSCSIVPDPOOL
+{
+ /** List of registered pages (VSCSIVPDPAGE). */
+ RTLISTANCHOR ListPages;
+} VSCSIVPDPOOL;
+/** Pointer to the VSCSI VPD page pool. */
+typedef VSCSIVPDPOOL *PVSCSIVPDPOOL;
+
+/**
+ * Supported operation code information entry.
+ */
+typedef struct VSCSILUNSUPOPC
+{
+ /** The operation code. */
+ uint8_t u8Opc;
+ /** Service action code if required as indicated by
+ * VSCSI_LUN_SUP_OPC_SVC_ACTION_REQUIRED */
+ uint16_t u16SvcAction;
+ /** Flags. */
+ uint32_t fFlags;
+ /** Readable description for the op code. */
+ const char *pszOpc;
+ /** The length of the CDB for this operation code. */
+ uint8_t cbCdb;
+ /** Pointer to the CDB usage data. */
+ uint8_t *pbCdbUsage;
+ /* The operation specific valuefor the timeout descriptor. */
+ uint8_t u8OpcTimeoutSpec;
+ /** The nominal processing timeout in seconds. */
+ uint16_t cNominalProcessingTimeout;
+ /** The recommend timeout in seconds. */
+ uint16_t cRecommendTimeout;
+} VSCSILUNSUPOPC;
+/** Pointer to a operation code information entry. */
+typedef VSCSILUNSUPOPC *PVSCSILUNSUPOPC;
+/** Pointer to a const operation code information entry. */
+typedef const VSCSILUNSUPOPC *PCVSCSILUNSUPOPC;
+
+/** @name Flags for the supported operation code infromation entries.
+ * @{ */
+/** Flag indicating wheter the service action member is valid and should be
+ * evaluated to find the desired opcode information. */
+#define VSCSI_LUN_SUP_OPC_SVC_ACTION_REQUIRED RT_BIT_32(0)
+/** Flag whether the values for the timeout descriptor are valid. */
+#define VSCSI_LUN_SUP_OPC_TIMEOUT_DESC_VALID RT_BIT_32(1)
+/** @} */
+
+/** @name Support macros to create supported operation code information entries.
+ * @{ */
+#define VSCSI_LUN_SUP_OPC(a_u8Opc, a_pszOpc, a_cbCdb, a_pbCdbUsage) \
+ { a_u8Opc, 0, 0, a_pszOpc, a_cbCdb, a_pbCdbUsage, 0, 0, 0}
+#define VSCSI_LUN_SUP_OPC_SVC(a_u8Opc, a_u16SvcAction, a_pszOpc, a_cbCdb, a_pbCdbUsage) \
+ { a_u8Opc, a_u16SvcAction, VSCSI_LUN_SUP_OPC_SVC_ACTION_REQUIRED, a_pszOpc, a_cbCdb, a_pbCdbUsage, 0, 0, 0}
+/** @} */
+
+/**
+ * Virtual SCSI LUN descriptor.
+ */
+typedef struct VSCSILUNDESC
+{
+ /** Device type this descriptor emulates. */
+ VSCSILUNTYPE enmLunType;
+ /** Descriptor name */
+ const char *pcszDescName;
+ /** LUN type size */
+ size_t cbLun;
+ /** Number of entries in the supported operation codes array. */
+ uint32_t cSupOpcInfo;
+ /** Pointer to the array of supported operation codes for the
+ * REPORT RUPPORTED OPERATION CODES command handled by the generic
+ * device driver - optional.
+ */
+ PCVSCSILUNSUPOPC paSupOpcInfo;
+
+ /**
+ * Initialise a Lun instance.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The SCSI LUN instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnVScsiLunInit, (PVSCSILUNINT pVScsiLun));
+
+ /**
+ * Destroy a Lun instance.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The SCSI LUN instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnVScsiLunDestroy, (PVSCSILUNINT pVScsiLun));
+
+ /**
+ * Processes a SCSI request.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The SCSI LUN instance.
+ * @param pVScsiReq The SCSi request to process.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnVScsiLunReqProcess, (PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq));
+
+ /**
+ * Frees additional allocated resources for the given request if it was allocated before.
+ *
+ * @returns void.
+ * @param pVScsiLun The SCSI LUN instance.
+ * @param pVScsiReq The SCSI request.
+ * @param pvScsiReqLun The opaque data allocated previously.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnVScsiLunReqFree, (PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ void *pvScsiReqLun));
+
+ /**
+ * Informs about a medium being inserted - optional.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The SCSI LUN instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnVScsiLunMediumInserted, (PVSCSILUNINT pVScsiLun));
+
+ /**
+ * Informs about a medium being removed - optional.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The SCSI LUN instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnVScsiLunMediumRemoved, (PVSCSILUNINT pVScsiLun));
+
+} VSCSILUNDESC;
+
+/** Maximum number of LUNs a device can have. */
+#define VSCSI_DEVICE_LUN_MAX 128
+
+/**
+ * Completes a SCSI request and calls the completion handler.
+ *
+ * @returns nothing.
+ * @param pVScsiDevice The virtual SCSI device.
+ * @param pVScsiReq The request which completed.
+ * @param rcScsiCode The status code
+ * One of the SCSI_STATUS_* #defines.
+ * @param fRedoPossible Flag whether redo is possible.
+ * @param rcReq Informational return code of the request.
+ */
+void vscsiDeviceReqComplete(PVSCSIDEVICEINT pVScsiDevice, PVSCSIREQINT pVScsiReq,
+ int rcScsiCode, bool fRedoPossible, int rcReq);
+
+/**
+ * Init the sense data state.
+ *
+ * @returns nothing.
+ * @param pVScsiSense The SCSI sense data state to init.
+ */
+void vscsiSenseInit(PVSCSISENSE pVScsiSense);
+
+/**
+ * Sets a ok sense code.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiSense The SCSI sense state to use.
+ * @param pVScsiReq The SCSI request.
+ */
+int vscsiReqSenseOkSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq);
+
+/**
+ * Sets an error sense code.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiSense The SCSI sense state to use.
+ * @param pVScsiReq The SCSI request.
+ * @param uSCSISenseKey The SCSI sense key to set.
+ * @param uSCSIASC The ASC value.
+ * @param uSCSIASC The ASCQ value.
+ */
+int vscsiReqSenseErrorSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey,
+ uint8_t uSCSIASC, uint8_t uSCSIASCQ);
+
+/**
+ * Sets an error sense code with additional information.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiSense The SCSI sense state to use.
+ * @param pVScsiReq The SCSI request.
+ * @param uSCSISenseKey The SCSI sense key to set.
+ * @param uSCSIASC The ASC value.
+ * @param uSCSIASC The ASCQ value.
+ * @param uInfo The 32-bit sense information.
+ */
+int vscsiReqSenseErrorInfoSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey,
+ uint8_t uSCSIASC, uint8_t uSCSIASCQ, uint32_t uInfo);
+
+/**
+ * Process a request sense command.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiSense The SCSI sense state to use.
+ * @param pVScsiReq The SCSI request.
+ */
+int vscsiReqSenseCmd(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq);
+
+/**
+ * Inits the VPD page pool.
+ *
+ * @returns VBox status code.
+ * @param pVScsiVpdPool The VPD page pool to initialize.
+ */
+int vscsiVpdPagePoolInit(PVSCSIVPDPOOL pVScsiVpdPool);
+
+/**
+ * Destroys the given VPD page pool freeing all pages in it.
+ *
+ * @returns nothing.
+ * @param pVScsiVpdPool The VPD page pool to destroy.
+ */
+void vscsiVpdPagePoolDestroy(PVSCSIVPDPOOL pVScsiVpdPool);
+
+/**
+ * Allocates a new page in the VPD page pool with the given number.
+ *
+ * @returns VBox status code.
+ * @retval VERR_ALREADY_EXIST if the page number is in use.
+ * @param pVScsiVpdPool The VPD page pool the page will belong to.
+ * @param uPage The page number, must be unique.
+ * @param cbPage Size of the page in bytes.
+ * @param ppbPage Where to store the pointer to the raw page data on success.
+ */
+int vscsiVpdPagePoolAllocNewPage(PVSCSIVPDPOOL pVScsiVpdPool, uint8_t uPage, size_t cbPage, uint8_t **ppbPage);
+
+/**
+ * Queries the given page from the pool and cpies it to the buffer given
+ * by the SCSI request.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_FOUND if the page is not in the pool.
+ * @param pVScsiVpdPool The VPD page pool to use.
+ * @param pVScsiReq The SCSI request.
+ * @param uPage Page to query.
+ */
+int vscsiVpdPagePoolQueryPage(PVSCSIVPDPOOL pVScsiVpdPool, PVSCSIREQINT pVScsiReq, uint8_t uPage);
+
+/**
+ * Inits the I/O request related state for the LUN.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN instance.
+ */
+int vscsiIoReqInit(PVSCSILUNINT pVScsiLun);
+
+/**
+ * Enqueues a new flush request
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN instance which issued the request.
+ * @param pVScsiReq The virtual SCSI request associated with the flush.
+ */
+int vscsiIoReqFlushEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq);
+
+/**
+ * Enqueue a new data transfer request.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN instance which issued the request.
+ * @param pVScsiReq The virtual SCSI request associated with the transfer.
+ * @param enmTxDir Transfer direction.
+ * @param uOffset Start offset of the transfer.
+ * @param cbTransfer Number of bytes to transfer.
+ */
+int vscsiIoReqTransferEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ VSCSIIOREQTXDIR enmTxDir, uint64_t uOffset,
+ size_t cbTransfer);
+
+/**
+ * Enqueue a new data transfer request - extended variant.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN instance which issued the request.
+ * @param pVScsiReq The virtual SCSI request associated with the transfer.
+ * @param enmTxDir Transfer direction.
+ * @param uOffset Start offset of the transfer.
+ * @param paSegs Pointer to the array holding the memory buffer segments.
+ * @param cSegs Number of segments in the array.
+ * @param cbTransfer Number of bytes to transfer.
+ */
+int vscsiIoReqTransferEnqueueEx(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ VSCSIIOREQTXDIR enmTxDir, uint64_t uOffset,
+ PCRTSGSEG paSegs, unsigned cSegs, size_t cbTransfer);
+
+/**
+ * Enqueue a new unmap request.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN instance which issued the request.
+ * @param pVScsiReq The virtual SCSI request associated with the transfer.
+ * @param paRanges The array of ranges to unmap.
+ * @param cRanges Number of ranges in the array.
+ */
+int vscsiIoReqUnmapEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ PRTRANGE paRanges, unsigned cRanges);
+
+/**
+ * Returns the current number of outstanding tasks on the given LUN.
+ *
+ * @returns Number of outstanding tasks.
+ * @param pVScsiLun The LUN to check.
+ */
+uint32_t vscsiIoReqOutstandingCountGet(PVSCSILUNINT pVScsiLun);
+
+/**
+ * Sets the transfer size for the given request.
+ *
+ * @returns nothing.
+ * @param pVScsiReq The SCSI request.
+ * @param cbXfer The transfer size for the request.
+ */
+DECLINLINE(void) vscsiReqSetXferSize(PVSCSIREQINT pVScsiReq, size_t cbXfer)
+{
+ pVScsiReq->cbXfer = cbXfer;
+}
+
+/**
+ * Wrapper for the set I/O request allocation size I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param cbVScsiIoReqAlloc The additional size for the request to allocate.
+ */
+DECLINLINE(int) vscsiLunReqAllocSizeSet(PVSCSILUNINT pVScsiLun, size_t cbVScsiIoReqAlloc)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunReqAllocSizeSet(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ cbVScsiIoReqAlloc);
+}
+
+/**
+ * Wrapper for the allocate I/O request I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param u64Tag A unique tag to assign to the request.
+ * @param ppVScsiIoReq Where to store the pointer to the request on success.
+ */
+DECLINLINE(int) vscsiLunReqAlloc(PVSCSILUNINT pVScsiLun, uint64_t u64Tag, PVSCSIIOREQINT *ppVScsiIoReq)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunReqAlloc(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ u64Tag, ppVScsiIoReq);
+}
+
+/**
+ * Wrapper for the free I/O request I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param pVScsiIoReq The request to free.
+ */
+DECLINLINE(int) vscsiLunReqFree(PVSCSILUNINT pVScsiLun, PVSCSIIOREQINT pVScsiIoReq)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunReqFree(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ pVScsiIoReq);
+}
+
+/**
+ * Wrapper for the get medium region count I/O callback.
+ *
+ * @returns Number of regions for the underlying medium.
+ * @param pVScsiLun The LUN.
+ */
+DECLINLINE(uint32_t) vscsiLunMediumGetRegionCount(PVSCSILUNINT pVScsiLun)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunMediumGetRegionCount(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser);
+}
+
+/**
+ * Wrapper for the query medium region properties I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param uRegion The region index to query the properties of.
+ * @param pu64LbaStart Where to store the starting LBA for the region on success.
+ * @param pcBlocks Where to store the number of blocks for the region on success.
+ * @param pcbBlock Where to store the size of one block in bytes on success.
+ * @param penmDataForm WHere to store the data form for the region on success.
+ */
+DECLINLINE(int) vscsiLunMediumQueryRegionProperties(PVSCSILUNINT pVScsiLun, uint32_t uRegion,
+ uint64_t *pu64LbaStart, uint64_t *pcBlocks,
+ uint64_t *pcbBlock, PVDREGIONDATAFORM penmDataForm)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunMediumQueryRegionProperties(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ uRegion, pu64LbaStart,
+ pcBlocks, pcbBlock,
+ penmDataForm);
+}
+
+/**
+ * Wrapper for the query medium region properties for LBA I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param uRegion The region index to query the properties of.
+ * @param pu64LbaStart Where to store the starting LBA for the region on success.
+ * @param pcBlocks Where to store the number of blocks for the region on success.
+ * @param pcbBlock Where to store the size of one block in bytes on success.
+ * @param penmDataForm WHere to store the data form for the region on success.
+ */
+DECLINLINE(int) vscsiLunMediumQueryRegionPropertiesForLba(PVSCSILUNINT pVScsiLun, uint64_t u64LbaStart, uint32_t *puRegion,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunMediumQueryRegionPropertiesForLba(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ u64LbaStart, puRegion,
+ pcBlocks, pcbBlock,
+ penmDataForm);
+}
+
+/**
+ * Wrapper for the get medium lock/unlock I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param bool The new medium lock state.
+ */
+DECLINLINE(int) vscsiLunMediumSetLock(PVSCSILUNINT pVScsiLun, bool fLocked)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunMediumSetLock(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ fLocked);
+}
+
+/**
+ * Wrapper for the eject medium I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ */
+DECLINLINE(int) vscsiLunMediumEject(PVSCSILUNINT pVScsiLun)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunMediumEject(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser);
+}
+
+/**
+ * Wrapper for the I/O request enqueue I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param pVScsiIoReq The I/O request to enqueue.
+ */
+DECLINLINE(int) vscsiLunReqTransferEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIIOREQINT pVScsiIoReq)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunReqTransferEnqueue(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ pVScsiIoReq);
+}
+
+/**
+ * Wrapper for the get feature flags I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param pfFeatures Where to sthre supported flags on success.
+ */
+DECLINLINE(int) vscsiLunGetFeatureFlags(PVSCSILUNINT pVScsiLun, uint64_t *pfFeatures)
+{
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunGetFeatureFlags(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ pfFeatures);
+}
+
+/**
+ * Wrapper for the query INQUIRY strings I/O callback.
+ *
+ * @returns VBox status code.
+ * @param pVScsiLun The LUN.
+ * @param ppszVendorId Where to store the pointer to the vendor ID string to report.
+ * @param ppszProductId Where to store the pointer to the product ID string to report.
+ * @param ppszProductLevel Where to store the pointer to the revision string to report.
+ */
+DECLINLINE(int) vscsiLunQueryInqStrings(PVSCSILUNINT pVScsiLun, const char **ppszVendorId,
+ const char **ppszProductId, const char **ppszProductLevel)
+{
+ if (pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunQueryInqStrings)
+ {
+ return pVScsiLun->pVScsiLunIoCallbacks->pfnVScsiLunQueryInqStrings(pVScsiLun,
+ pVScsiLun->pvVScsiLunUser,
+ ppszVendorId, ppszProductId,
+ ppszProductLevel);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Wrapper around vscsiReqSenseOkSet()
+ */
+DECLINLINE(int) vscsiLunReqSenseOkSet(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq)
+{
+ return vscsiReqSenseOkSet(&pVScsiLun->pVScsiDevice->VScsiSense, pVScsiReq);
+}
+
+/**
+ * Wrapper around vscsiReqSenseErrorSet()
+ */
+DECLINLINE(int) vscsiLunReqSenseErrorSet(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey, uint8_t uSCSIASC, uint8_t uSCSIASCQ)
+{
+ return vscsiReqSenseErrorSet(&pVScsiLun->pVScsiDevice->VScsiSense, pVScsiReq, uSCSISenseKey, uSCSIASC, uSCSIASCQ);
+}
+
+/**
+ * Wrapper around vscsiReqSenseErrorInfoSet()
+ */
+DECLINLINE(int) vscsiLunReqSenseErrorInfoSet(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey, uint8_t uSCSIASC, uint8_t uSCSIASCQ, uint32_t uInfo)
+{
+ return vscsiReqSenseErrorInfoSet(&pVScsiLun->pVScsiDevice->VScsiSense, pVScsiReq, uSCSISenseKey, uSCSIASC, uSCSIASCQ, uInfo);
+}
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIInternal_h */
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp
new file mode 100644
index 00000000..35cdc9f6
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp
@@ -0,0 +1,257 @@
+/* $Id: VSCSIIoReq.cpp $ */
+/** @file
+ * Virtual SCSI driver: I/O request handling.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+
+#include "VSCSIInternal.h"
+
+int vscsiIoReqInit(PVSCSILUNINT pVScsiLun)
+{
+ return vscsiLunReqAllocSizeSet(pVScsiLun, sizeof(VSCSIIOREQINT));
+}
+
+int vscsiIoReqFlushEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIIOREQINT pVScsiIoReq = NULL;
+
+ rc = vscsiLunReqAlloc(pVScsiLun, (uintptr_t)pVScsiReq, &pVScsiIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ pVScsiIoReq->pVScsiReq = pVScsiReq;
+ pVScsiIoReq->pVScsiLun = pVScsiLun;
+ pVScsiIoReq->enmTxDir = VSCSIIOREQTXDIR_FLUSH;
+
+ ASMAtomicIncU32(&pVScsiLun->IoReq.cReqOutstanding);
+
+ rc = vscsiLunReqTransferEnqueue(pVScsiLun, pVScsiIoReq);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicDecU32(&pVScsiLun->IoReq.cReqOutstanding);
+ vscsiLunReqFree(pVScsiLun, pVScsiIoReq);
+ }
+ }
+
+ return rc;
+}
+
+
+int vscsiIoReqTransferEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ VSCSIIOREQTXDIR enmTxDir, uint64_t uOffset,
+ size_t cbTransfer)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIIOREQINT pVScsiIoReq = NULL;
+
+ LogFlowFunc(("pVScsiLun=%#p pVScsiReq=%#p enmTxDir=%u uOffset=%llu cbTransfer=%u\n",
+ pVScsiLun, pVScsiReq, enmTxDir, uOffset, cbTransfer));
+
+ rc = vscsiLunReqAlloc(pVScsiLun, (uintptr_t)pVScsiReq, &pVScsiIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ pVScsiIoReq->pVScsiReq = pVScsiReq;
+ pVScsiIoReq->pVScsiLun = pVScsiLun;
+ pVScsiIoReq->enmTxDir = enmTxDir;
+ pVScsiIoReq->u.Io.uOffset = uOffset;
+ pVScsiIoReq->u.Io.cbTransfer = cbTransfer;
+ pVScsiIoReq->u.Io.paSeg = pVScsiReq->SgBuf.paSegs;
+ pVScsiIoReq->u.Io.cSeg = pVScsiReq->SgBuf.cSegs;
+
+ ASMAtomicIncU32(&pVScsiLun->IoReq.cReqOutstanding);
+
+ rc = vscsiLunReqTransferEnqueue(pVScsiLun, pVScsiIoReq);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicDecU32(&pVScsiLun->IoReq.cReqOutstanding);
+ vscsiLunReqFree(pVScsiLun, pVScsiIoReq);
+ }
+ }
+
+ return rc;
+}
+
+
+int vscsiIoReqTransferEnqueueEx(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ VSCSIIOREQTXDIR enmTxDir, uint64_t uOffset,
+ PCRTSGSEG paSegs, unsigned cSegs, size_t cbTransfer)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIIOREQINT pVScsiIoReq = NULL;
+
+ LogFlowFunc(("pVScsiLun=%#p pVScsiReq=%#p enmTxDir=%u uOffset=%llu cbTransfer=%u\n",
+ pVScsiLun, pVScsiReq, enmTxDir, uOffset, cbTransfer));
+
+ rc = vscsiLunReqAlloc(pVScsiLun, (uintptr_t)pVScsiReq, &pVScsiIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ pVScsiIoReq->pVScsiReq = pVScsiReq;
+ pVScsiIoReq->pVScsiLun = pVScsiLun;
+ pVScsiIoReq->enmTxDir = enmTxDir;
+ pVScsiIoReq->u.Io.uOffset = uOffset;
+ pVScsiIoReq->u.Io.cbTransfer = cbTransfer;
+ pVScsiIoReq->u.Io.paSeg = paSegs;
+ pVScsiIoReq->u.Io.cSeg = cSegs;
+
+ ASMAtomicIncU32(&pVScsiLun->IoReq.cReqOutstanding);
+
+ rc = vscsiLunReqTransferEnqueue(pVScsiLun, pVScsiIoReq);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicDecU32(&pVScsiLun->IoReq.cReqOutstanding);
+ vscsiLunReqFree(pVScsiLun, pVScsiIoReq);
+ }
+ }
+
+ return rc;
+}
+
+
+int vscsiIoReqUnmapEnqueue(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ PRTRANGE paRanges, unsigned cRanges)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIIOREQINT pVScsiIoReq = NULL;
+
+ LogFlowFunc(("pVScsiLun=%#p pVScsiReq=%#p paRanges=%#p cRanges=%u\n",
+ pVScsiLun, pVScsiReq, paRanges, cRanges));
+
+ rc = vscsiLunReqAlloc(pVScsiLun, (uintptr_t)pVScsiReq, &pVScsiIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ pVScsiIoReq->pVScsiReq = pVScsiReq;
+ pVScsiIoReq->pVScsiLun = pVScsiLun;
+ pVScsiIoReq->enmTxDir = VSCSIIOREQTXDIR_UNMAP;
+ pVScsiIoReq->u.Unmap.paRanges = paRanges;
+ pVScsiIoReq->u.Unmap.cRanges = cRanges;
+
+ ASMAtomicIncU32(&pVScsiLun->IoReq.cReqOutstanding);
+
+ rc = vscsiLunReqTransferEnqueue(pVScsiLun, pVScsiIoReq);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicDecU32(&pVScsiLun->IoReq.cReqOutstanding);
+ vscsiLunReqFree(pVScsiLun, pVScsiIoReq);
+ }
+ }
+
+ return rc;
+}
+
+
+uint32_t vscsiIoReqOutstandingCountGet(PVSCSILUNINT pVScsiLun)
+{
+ return ASMAtomicReadU32(&pVScsiLun->IoReq.cReqOutstanding);
+}
+
+
+VBOXDDU_DECL(int) VSCSIIoReqCompleted(VSCSIIOREQ hVScsiIoReq, int rcIoReq, bool fRedoPossible)
+{
+ PVSCSIIOREQINT pVScsiIoReq = hVScsiIoReq;
+ PVSCSILUNINT pVScsiLun;
+ PVSCSIREQINT pVScsiReq;
+ int rcReq = SCSI_STATUS_OK;
+
+ AssertPtrReturn(pVScsiIoReq, VERR_INVALID_HANDLE);
+
+ LogFlowFunc(("hVScsiIoReq=%#p rcIoReq=%Rrc\n", hVScsiIoReq, rcIoReq));
+
+ pVScsiLun = pVScsiIoReq->pVScsiLun;
+ pVScsiReq = pVScsiIoReq->pVScsiReq;
+
+ AssertMsg(pVScsiLun->IoReq.cReqOutstanding > 0,
+ ("Unregistered I/O request completed\n"));
+
+ ASMAtomicDecU32(&pVScsiLun->IoReq.cReqOutstanding);
+
+ if (RT_SUCCESS(rcIoReq))
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ else if (!fRedoPossible)
+ {
+ /** @todo Not 100% correct for the write case as the 0x00 ASCQ for write errors
+ * is not used for SBC devices. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_MEDIUM_ERROR,
+ pVScsiIoReq->enmTxDir == VSCSIIOREQTXDIR_READ
+ ? SCSI_ASC_READ_ERROR
+ : SCSI_ASC_WRITE_ERROR,
+ 0x00);
+ }
+ else
+ rcReq = SCSI_STATUS_CHECK_CONDITION;
+
+ if (pVScsiIoReq->enmTxDir == VSCSIIOREQTXDIR_UNMAP)
+ RTMemFree(pVScsiIoReq->u.Unmap.paRanges);
+
+ /* Free the I/O request */
+ vscsiLunReqFree(pVScsiLun, pVScsiIoReq);
+
+ /* Notify completion of the SCSI request. */
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, fRedoPossible, rcIoReq);
+
+ return VINF_SUCCESS;
+}
+
+
+VBOXDDU_DECL(VSCSIIOREQTXDIR) VSCSIIoReqTxDirGet(VSCSIIOREQ hVScsiIoReq)
+{
+ PVSCSIIOREQINT pVScsiIoReq = hVScsiIoReq;
+
+ AssertPtrReturn(pVScsiIoReq, VSCSIIOREQTXDIR_INVALID);
+
+ return pVScsiIoReq->enmTxDir;
+}
+
+
+VBOXDDU_DECL(int) VSCSIIoReqParamsGet(VSCSIIOREQ hVScsiIoReq, uint64_t *puOffset,
+ size_t *pcbTransfer, unsigned *pcSeg,
+ size_t *pcbSeg, PCRTSGSEG *ppaSeg)
+{
+ PVSCSIIOREQINT pVScsiIoReq = hVScsiIoReq;
+
+ AssertPtrReturn(pVScsiIoReq, VERR_INVALID_HANDLE);
+ AssertReturn( pVScsiIoReq->enmTxDir != VSCSIIOREQTXDIR_FLUSH
+ && pVScsiIoReq->enmTxDir != VSCSIIOREQTXDIR_UNMAP,
+ VERR_NOT_SUPPORTED);
+
+ *puOffset = pVScsiIoReq->u.Io.uOffset;
+ *pcbTransfer = pVScsiIoReq->u.Io.cbTransfer;
+ *pcSeg = pVScsiIoReq->u.Io.cSeg;
+ *pcbSeg = pVScsiIoReq->u.Io.cbSeg;
+ *ppaSeg = pVScsiIoReq->u.Io.paSeg;
+
+ return VINF_SUCCESS;
+}
+
+VBOXDDU_DECL(int) VSCSIIoReqUnmapParamsGet(VSCSIIOREQ hVScsiIoReq, PCRTRANGE *ppaRanges,
+ unsigned *pcRanges)
+{
+ PVSCSIIOREQINT pVScsiIoReq = hVScsiIoReq;
+
+ AssertPtrReturn(pVScsiIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pVScsiIoReq->enmTxDir == VSCSIIOREQTXDIR_UNMAP, VERR_NOT_SUPPORTED);
+
+ *ppaRanges = pVScsiIoReq->u.Unmap.paRanges;
+ *pcRanges = pVScsiIoReq->u.Unmap.cRanges;
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp
new file mode 100644
index 00000000..0c9c5d05
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp
@@ -0,0 +1,174 @@
+/* $Id: VSCSILun.cpp $ */
+/** @file
+ * Virtual SCSI driver: LUN handling
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+
+#include "VSCSIInternal.h"
+
+/** SBC descriptor */
+extern VSCSILUNDESC g_VScsiLunTypeSbc;
+/** MMC descriptor */
+extern VSCSILUNDESC g_VScsiLunTypeMmc;
+/** SSC descriptor */
+extern VSCSILUNDESC g_VScsiLunTypeSsc;
+
+/**
+ * Array of supported SCSI LUN types.
+ */
+static PVSCSILUNDESC g_aVScsiLunTypesSupported[] =
+{
+ &g_VScsiLunTypeSbc,
+ &g_VScsiLunTypeMmc,
+#ifdef VBOX_WITH_VSCSI_SSC
+ &g_VScsiLunTypeSsc,
+#endif
+};
+
+VBOXDDU_DECL(int) VSCSILunCreate(PVSCSILUN phVScsiLun, VSCSILUNTYPE enmLunType,
+ PVSCSILUNIOCALLBACKS pVScsiLunIoCallbacks,
+ void *pvVScsiLunUser)
+{
+ PVSCSILUNINT pVScsiLun = NULL;
+ PVSCSILUNDESC pVScsiLunDesc = NULL;
+
+ AssertPtrReturn(phVScsiLun, VERR_INVALID_POINTER);
+ AssertReturn( enmLunType > VSCSILUNTYPE_INVALID
+ && enmLunType < VSCSILUNTYPE_LAST, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pVScsiLunIoCallbacks, VERR_INVALID_PARAMETER);
+
+ for (unsigned idxLunType = 0; idxLunType < RT_ELEMENTS(g_aVScsiLunTypesSupported); idxLunType++)
+ {
+ if (g_aVScsiLunTypesSupported[idxLunType]->enmLunType == enmLunType)
+ {
+ pVScsiLunDesc = g_aVScsiLunTypesSupported[idxLunType];
+ break;
+ }
+ }
+
+ if (!pVScsiLunDesc)
+ return VERR_VSCSI_LUN_TYPE_NOT_SUPPORTED;
+
+ pVScsiLun = (PVSCSILUNINT)RTMemAllocZ(pVScsiLunDesc->cbLun);
+ if (!pVScsiLun)
+ return VERR_NO_MEMORY;
+
+ pVScsiLun->pVScsiDevice = NULL;
+ pVScsiLun->pvVScsiLunUser = pvVScsiLunUser;
+ pVScsiLun->pVScsiLunIoCallbacks = pVScsiLunIoCallbacks;
+ pVScsiLun->pVScsiLunDesc = pVScsiLunDesc;
+
+ int rc = vscsiIoReqInit(pVScsiLun);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vscsiLunGetFeatureFlags(pVScsiLun, &pVScsiLun->fFeatures);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pVScsiLunDesc->pfnVScsiLunInit(pVScsiLun);
+ if (RT_SUCCESS(rc))
+ {
+ *phVScsiLun = pVScsiLun;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ RTMemFree(pVScsiLun);
+
+ return rc;
+}
+
+/**
+ * Destroy virtual SCSI LUN.
+ *
+ * @returns VBox status code.
+ * @param hVScsiLun The virtual SCSI LUN handle to destroy.
+ */
+VBOXDDU_DECL(int) VSCSILunDestroy(VSCSILUN hVScsiLun)
+{
+ PVSCSILUNINT pVScsiLun = (PVSCSILUNINT)hVScsiLun;
+
+ AssertPtrReturn(pVScsiLun, VERR_INVALID_HANDLE);
+ AssertReturn(!pVScsiLun->pVScsiDevice, VERR_VSCSI_LUN_ATTACHED_TO_DEVICE);
+ AssertReturn(vscsiIoReqOutstandingCountGet(pVScsiLun) == 0, VERR_VSCSI_LUN_BUSY);
+
+ int rc = pVScsiLun->pVScsiLunDesc->pfnVScsiLunDestroy(pVScsiLun);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Make LUN invalid */
+ pVScsiLun->pvVScsiLunUser = NULL;
+ pVScsiLun->pVScsiLunIoCallbacks = NULL;
+ pVScsiLun->pVScsiLunDesc = NULL;
+
+ RTMemFree(pVScsiLun);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notify virtual SCSI LUN of media being mounted.
+ *
+ * @returns VBox status code.
+ * @param hVScsiLun The virtual SCSI LUN
+ * mounting the medium.
+ */
+VBOXDDU_DECL(int) VSCSILunMountNotify(VSCSILUN hVScsiLun)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSILUNINT pVScsiLun = (PVSCSILUNINT)hVScsiLun;
+
+ LogFlowFunc(("hVScsiLun=%p\n", hVScsiLun));
+ AssertPtrReturn(pVScsiLun, VERR_INVALID_HANDLE);
+ AssertReturn(vscsiIoReqOutstandingCountGet(pVScsiLun) == 0, VERR_VSCSI_LUN_BUSY);
+
+ /* Mark the LUN as not ready so that LUN specific code can do its job. */
+ pVScsiLun->fReady = false;
+ pVScsiLun->fMediaPresent = true;
+ if (pVScsiLun->pVScsiLunDesc->pfnVScsiLunMediumInserted)
+ rc = pVScsiLun->pVScsiLunDesc->pfnVScsiLunMediumInserted(pVScsiLun);
+
+ return rc;
+}
+
+/**
+ * Notify virtual SCSI LUN of media being unmounted.
+ *
+ * @returns VBox status code.
+ * @param hVScsiLun The virtual SCSI LUN
+ * mounting the medium.
+ */
+VBOXDDU_DECL(int) VSCSILunUnmountNotify(VSCSILUN hVScsiLun)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSILUNINT pVScsiLun = (PVSCSILUNINT)hVScsiLun;
+
+ LogFlowFunc(("hVScsiLun=%p\n", hVScsiLun));
+ AssertPtrReturn(pVScsiLun, VERR_INVALID_HANDLE);
+ AssertReturn(vscsiIoReqOutstandingCountGet(pVScsiLun) == 0, VERR_VSCSI_LUN_BUSY);
+
+ pVScsiLun->fReady = false;
+ pVScsiLun->fMediaPresent = false;
+ if (pVScsiLun->pVScsiLunDesc->pfnVScsiLunMediumRemoved)
+ rc = pVScsiLun->pVScsiLunDesc->pfnVScsiLunMediumRemoved(pVScsiLun);
+
+ return rc;
+}
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp
new file mode 100644
index 00000000..093167b4
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp
@@ -0,0 +1,1757 @@
+/* $Id: VSCSILunMmc.cpp $ */
+/** @file
+ * Virtual SCSI driver: MMC LUN implementation (CD/DVD-ROM)
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "VSCSIInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Different event status types.
+ */
+typedef enum MMCEVENTSTATUSTYPE
+{
+ /** Medium event status not changed. */
+ MMCEVENTSTATUSTYPE_UNCHANGED = 0,
+ /** New medium inserted. */
+ MMCEVENTSTATUSTYPE_MEDIA_NEW,
+ /** Medium removed. */
+ MMCEVENTSTATUSTYPE_MEDIA_REMOVED,
+ /** Medium was removed + new medium was inserted. */
+ MMCEVENTSTATUSTYPE_MEDIA_CHANGED,
+ /** Medium eject requested (eject button pressed). */
+ MMCEVENTSTATUSTYPE_MEDIA_EJECT_REQUESTED,
+ /** 32bit hack. */
+ MMCEVENTSTATUSTYPE_32BIT_HACK = 0x7fffffff
+} MMCEVENTSTATUSTYPE;
+
+/** @name Media track types.
+ * @{ */
+/** Unknown media type. */
+#define MMC_MEDIA_TYPE_UNKNOWN 0
+/** Door closed, no media. */
+#define MMC_MEDIA_TYPE_NO_DISC 0x70
+/** @} */
+
+
+/**
+ * MMC LUN instance
+ */
+typedef struct VSCSILUNMMC
+{
+ /** Core LUN structure */
+ VSCSILUNINT Core;
+ /** Size of the virtual disk. */
+ uint64_t cSectors;
+ /** Medium locked indicator. */
+ bool fLocked;
+ /** Media event status. */
+ volatile MMCEVENTSTATUSTYPE MediaEventStatus;
+ /** Media track type. */
+ volatile uint32_t u32MediaTrackType;
+} VSCSILUNMMC, *PVSCSILUNMMC;
+
+
+/**
+ * Callback to fill a feature for a GET CONFIGURATION request.
+ *
+ * @returns Number of bytes used for this feature in the buffer.
+ * @param pbBuf The buffer to use.
+ * @param cbBuf Size of the buffer.
+ */
+typedef DECLCALLBACK(size_t) FNVSCSILUNMMCFILLFEATURE(uint8_t *pbBuf, size_t cbBuf);
+/** Pointer to a fill feature callback. */
+typedef FNVSCSILUNMMCFILLFEATURE *PFNVSCSILUNMMCFILLFEATURE;
+
+/**
+ * VSCSI MMC feature descriptor.
+ */
+typedef struct VSCSILUNMMCFEATURE
+{
+ /** The feature number. */
+ uint16_t u16Feat;
+ /** The callback to call for this feature. */
+ PFNVSCSILUNMMCFILLFEATURE pfnFeatureFill;
+} VSCSILUNMMCFEATURE;
+/** Pointer to a VSCSI MMC feature descriptor. */
+typedef VSCSILUNMMCFEATURE *PVSCSILUNMMCFEATURE;
+/** Pointer to a const VSCSI MMC feature descriptor. */
+typedef const VSCSILUNMMCFEATURE *PCVSCSILUNMMCFEATURE;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureListProfiles(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureCore(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureMorphing(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureRemovableMedium(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureRandomReadable(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureCDRead(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeaturePowerManagement(uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureTimeout(uint8_t *pbBuf, size_t cbBuf);
+RT_C_DECLS_END
+
+/**
+ * List of supported MMC features.
+ */
+static const VSCSILUNMMCFEATURE g_aVScsiMmcFeatures[] =
+{
+ { 0x0000, vscsiLunMmcGetConfigurationFillFeatureListProfiles},
+ { 0x0001, vscsiLunMmcGetConfigurationFillFeatureCore},
+ { 0x0002, vscsiLunMmcGetConfigurationFillFeatureMorphing},
+ { 0x0003, vscsiLunMmcGetConfigurationFillFeatureRemovableMedium},
+ { 0x0010, vscsiLunMmcGetConfigurationFillFeatureRandomReadable},
+ { 0x001e, vscsiLunMmcGetConfigurationFillFeatureCDRead},
+ { 0x0100, vscsiLunMmcGetConfigurationFillFeaturePowerManagement},
+ { 0x0105, vscsiLunMmcGetConfigurationFillFeatureTimeout}
+};
+
+/* Fabricate normal TOC information. */
+static int mmcReadTOCNormal(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq, uint16_t cbMaxTransfer, bool fMSF)
+{
+ uint8_t aReply[2+99*8 + 32]; RT_ZERO(aReply); /* Maximum possible reply plus some safety. */
+ uint8_t *pbBuf = aReply;
+ uint8_t *q;
+ uint8_t iStartTrack;
+ uint32_t cbSize;
+ uint32_t cTracks = vscsiLunMediumGetRegionCount(pVScsiLun);
+
+ iStartTrack = pVScsiReq->pbCDB[6];
+ if (iStartTrack == 0)
+ iStartTrack = 1;
+ if (iStartTrack > cTracks && iStartTrack != 0xaa)
+ return vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+
+ q = pbBuf + 2;
+ *q++ = iStartTrack; /* first track number */
+ *q++ = cTracks; /* last track number */
+ for (uint32_t iTrack = iStartTrack; iTrack <= cTracks; iTrack++)
+ {
+ uint64_t uLbaStart = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+
+ int rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, iTrack - 1, &uLbaStart,
+ NULL, NULL, &enmDataForm);
+ if (rc == VERR_NOT_FOUND || rc == VERR_MEDIA_NOT_PRESENT)
+ return vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY,
+ SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ else
+ AssertRC(rc);
+
+ *q++ = 0; /* reserved */
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ *q++ = 0x10; /* ADR, control */
+ else
+ *q++ = 0x14; /* ADR, control */
+
+ *q++ = (uint8_t)iTrack; /* track number */
+ *q++ = 0; /* reserved */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+
+ /* Query start and length of last track to get the start of the lead out track. */
+ uint64_t uLbaStart = 0;
+ uint64_t cBlocks = 0;
+
+ int rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, cTracks - 1, &uLbaStart,
+ &cBlocks, NULL, NULL);
+ if (rc == VERR_NOT_FOUND || rc == VERR_MEDIA_NOT_PRESENT)
+ return vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY,
+ SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ else
+ AssertRC(rc);
+
+ uLbaStart += cBlocks;
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ cbSize = q - pbBuf;
+ Assert(cbSize <= sizeof(aReply));
+ scsiH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < cbMaxTransfer)
+ cbMaxTransfer = cbSize;
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, cbMaxTransfer);
+ return vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+}
+
+/* Fabricate session information. */
+static int mmcReadTOCMulti(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq, uint16_t cbMaxTransfer, bool fMSF)
+{
+ RT_NOREF1(cbMaxTransfer);
+ uint8_t aReply[32];
+ uint8_t *pbBuf = aReply;
+
+ /* multi session: only a single session defined */
+ memset(pbBuf, 0, 12);
+ pbBuf[1] = 0x0a;
+ pbBuf[2] = 0x01; /* first complete session number */
+ pbBuf[3] = 0x01; /* last complete session number */
+
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+ int rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, 0, NULL,
+ NULL, NULL, &enmDataForm);
+ if (rc == VERR_NOT_FOUND || rc == VERR_MEDIA_NOT_PRESENT)
+ return vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY,
+ SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ else
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ pbBuf[5] = 0x10; /* ADR, control */
+ else
+ pbBuf[5] = 0x14; /* ADR, control */
+
+ pbBuf[6] = 1; /* first track in last complete session */
+
+ if (fMSF)
+ {
+ pbBuf[8] = 0; /* reserved */
+ scsiLBA2MSF(pbBuf + 8, 0);
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(pbBuf + 8, 0);
+ }
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, 12);
+
+ return vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+}
+
+/**
+ * Create raw TOC data information.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLun The LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ * @param fMSF Flag whether to use MSF format to encode sector numbers.
+ */
+static int mmcReadTOCRaw(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq, uint16_t cbMaxTransfer, bool fMSF)
+{
+ PVSCSILUNMMC pVScsiLunMmc = (PVSCSILUNMMC)pVScsiLun;
+ uint8_t aReply[50]; /* Counted a maximum of 45 bytes but better be on the safe side. */
+ uint32_t cbSize;
+ uint8_t *pbBuf = &aReply[0] + 2;
+
+ *pbBuf++ = 1; /* first session */
+ *pbBuf++ = 1; /* last session */
+
+ *pbBuf++ = 1; /* session number */
+ *pbBuf++ = 0x14; /* data track */
+ *pbBuf++ = 0; /* track number */
+ *pbBuf++ = 0xa0; /* first track in program area */
+ *pbBuf++ = 0; /* min */
+ *pbBuf++ = 0; /* sec */
+ *pbBuf++ = 0; /* frame */
+ *pbBuf++ = 0;
+ *pbBuf++ = 1; /* first track */
+ *pbBuf++ = 0x00; /* disk type CD-DA or CD data */
+ *pbBuf++ = 0;
+
+ *pbBuf++ = 1; /* session number */
+ *pbBuf++ = 0x14; /* data track */
+ *pbBuf++ = 0; /* track number */
+ *pbBuf++ = 0xa1; /* last track in program area */
+ *pbBuf++ = 0; /* min */
+ *pbBuf++ = 0; /* sec */
+ *pbBuf++ = 0; /* frame */
+ *pbBuf++ = 0;
+ *pbBuf++ = 1; /* last track */
+ *pbBuf++ = 0;
+ *pbBuf++ = 0;
+
+ *pbBuf++ = 1; /* session number */
+ *pbBuf++ = 0x14; /* data track */
+ *pbBuf++ = 0; /* track number */
+ *pbBuf++ = 0xa2; /* lead-out */
+ *pbBuf++ = 0; /* min */
+ *pbBuf++ = 0; /* sec */
+ *pbBuf++ = 0; /* frame */
+ if (fMSF)
+ {
+ *pbBuf++ = 0; /* reserved */
+ scsiLBA2MSF(pbBuf, pVScsiLunMmc->cSectors);
+ pbBuf += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(pbBuf, pVScsiLunMmc->cSectors);
+ pbBuf += 4;
+ }
+
+ *pbBuf++ = 1; /* session number */
+ *pbBuf++ = 0x14; /* ADR, control */
+ *pbBuf++ = 0; /* track number */
+ *pbBuf++ = 1; /* point */
+ *pbBuf++ = 0; /* min */
+ *pbBuf++ = 0; /* sec */
+ *pbBuf++ = 0; /* frame */
+ if (fMSF)
+ {
+ *pbBuf++ = 0; /* reserved */
+ scsiLBA2MSF(pbBuf, 0);
+ pbBuf += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(pbBuf, 0);
+ pbBuf += 4;
+ }
+
+ cbSize = pbBuf - aReply;
+ scsiH2BE_U16(&aReply[0], cbSize - 2);
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, cbSize));
+ return vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureListProfiles(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 3*4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x0); /* feature 0: list of profiles supported */
+ pbBuf[2] = (0 << 2) | (1 << 1) | (1 << 0); /* version 0, persistent, current */
+ pbBuf[3] = 8; /* additional bytes for profiles */
+ /* The MMC-3 spec says that DVD-ROM read capability should be reported
+ * before CD-ROM read capability. */
+ scsiH2BE_U16(pbBuf + 4, 0x10); /* profile: read-only DVD */
+ pbBuf[6] = (0 << 0); /* NOT current profile */
+ scsiH2BE_U16(pbBuf + 8, 0x08); /* profile: read only CD */
+ pbBuf[10] = (1 << 0); /* current profile */
+
+ return 3*4; /* Header + 2 profiles entries */
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureCore(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1); /* feature 0001h: Core Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U16(pbBuf + 4, 0x00000002); /* Physical interface ATAPI. */
+ pbBuf[8] = RT_BIT(0); /* DBE */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureMorphing(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x2); /* feature 0002h: Morphing Feature */
+ pbBuf[2] = (0x1 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = RT_BIT(1) | 0x0; /* OCEvent | !ASYNC */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureRemovableMedium(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x3); /* feature 0003h: Removable Medium Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ /* Tray type loading | Load | Eject | !Pvnt Jmpr | !DBML | Lock */
+ pbBuf[4] = (0x2 << 5) | RT_BIT(4) | RT_BIT(3) | (0x0 << 2) | (0x0 << 1) | RT_BIT(0);
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureRandomReadable(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x10); /* feature 0010h: Random Readable Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U32(pbBuf + 4, 2048); /* Logical block size. */
+ scsiH2BE_U16(pbBuf + 8, 0x10); /* Blocking (0x10 for DVD, CD is not defined). */
+ pbBuf[10] = 0; /* PP not present */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureCDRead(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1e); /* feature 001Eh: CD Read Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+ pbBuf[4] = (0x0 << 7) | (0x0 << 1) | 0x0; /* !DAP | !C2-Flags | !CD-Text. */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeaturePowerManagement(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x100); /* feature 0100h: Power Management Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+
+ return 4;
+}
+
+static DECLCALLBACK(size_t) vscsiLunMmcGetConfigurationFillFeatureTimeout(uint8_t *pbBuf, size_t cbBuf)
+{
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x105); /* feature 0105h: Timeout Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = 0x0; /* !Group3 */
+
+ return 8;
+}
+
+/**
+ * Processes the GET CONFIGURATION SCSI request.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLunMmc The MMC LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ */
+static int vscsiLunMmcGetConfiguration(PVSCSILUNMMC pVScsiLunMmc, PVSCSIREQINT pVScsiReq, size_t cbMaxTransfer)
+{
+ uint8_t aReply[80];
+ uint8_t *pbBuf = &aReply[0];
+ size_t cbBuf = sizeof(aReply);
+ size_t cbCopied = 0;
+ uint16_t u16Sfn = scsiBE2H_U16(&pVScsiReq->pbCDB[2]);
+ uint8_t u8Rt = pVScsiReq->pbCDB[1] & 0x03;
+
+ /* Accept valid request types only. */
+ if (u8Rt == 3)
+ return vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+
+ /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+ * way to differentiate them right now is based on the image size). */
+ if (pVScsiLunMmc->cSectors)
+ scsiH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
+ else
+ scsiH2BE_U16(pbBuf + 6, 0x00); /* current profile: none -> no media */
+ cbBuf -= 8;
+ pbBuf += 8;
+
+ if (u8Rt == 0x2)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aVScsiMmcFeatures); i++)
+ {
+ if (g_aVScsiMmcFeatures[i].u16Feat == u16Sfn)
+ {
+ cbCopied = g_aVScsiMmcFeatures[i].pfnFeatureFill(pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aVScsiMmcFeatures); i++)
+ {
+ if (g_aVScsiMmcFeatures[i].u16Feat > u16Sfn)
+ {
+ cbCopied = g_aVScsiMmcFeatures[i].pfnFeatureFill(pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ }
+ }
+ }
+
+ /* Set data length now. */
+ scsiH2BE_U32(&aReply[0], (uint32_t)(sizeof(aReply) - cbBuf));
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, sizeof(aReply) - cbBuf));
+ return vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+}
+
+/**
+ * Processes the READ DVD STRUCTURE SCSI request.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLunMmc The MMC LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ */
+static int vscsiLunMmcReadDvdStructure(PVSCSILUNMMC pVScsiLunMmc, PVSCSIREQINT pVScsiReq, size_t cbMaxTransfer)
+{
+ uint8_t aReply[25]; /* Counted a maximum of 20 bytes but better be on the safe side. */
+
+ RT_ZERO(aReply);
+
+ /* Act according to the indicated format. */
+ switch (pVScsiReq->pbCDB[7])
+ {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x0e:
+ case 0x0f:
+ case 0x10:
+ case 0x11:
+ case 0x30:
+ case 0x31:
+ case 0xff:
+ if (pVScsiReq->pbCDB[1] == 0)
+ {
+ int uASC = SCSI_ASC_NONE;
+
+ switch (pVScsiReq->pbCDB[7])
+ {
+ case 0x0: /* Physical format information */
+ {
+ uint8_t uLayer = pVScsiReq->pbCDB[6];
+ uint64_t cTotalSectors;
+
+ if (uLayer != 0)
+ {
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+ }
+
+ cTotalSectors = pVScsiLunMmc->cSectors;
+ cTotalSectors >>= 2;
+ if (cTotalSectors == 0)
+ {
+ uASC = -SCSI_ASC_MEDIUM_NOT_PRESENT;
+ break;
+ }
+
+ aReply[4] = 1; /* DVD-ROM, part version 1 */
+ aReply[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ aReply[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ aReply[7] = 0; /* default densities */
+
+ /* FIXME: 0x30000 per spec? */
+ scsiH2BE_U32(&aReply[8], 0); /* start sector */
+ scsiH2BE_U32(&aReply[12], cTotalSectors - 1); /* end sector */
+ scsiH2BE_U32(&aReply[16], cTotalSectors - 1); /* l0 end sector */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U32(&aReply[0], 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ }
+ case 0x01: /* DVD copyright information */
+ aReply[4] = 0; /* no copyright data */
+ aReply[5] = 0; /* no region restrictions */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(&aReply[0], 4 + 2);
+
+ /* 4 byte header + 4 byte data */
+ uASC = (4 + 4);
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+
+ case 0x04: /* DVD disc manufacturing information */
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(&aReply[0], 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ case 0xff:
+ /*
+ * This lists all the command capabilities above. Add new ones
+ * in order and update the length and buffer return values.
+ */
+
+ aReply[4] = 0x00; /* Physical format */
+ aReply[5] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16(&aReply[6], 2048 + 4);
+
+ aReply[8] = 0x01; /* Copyright info */
+ aReply[9] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16(&aReply[10], 4 + 4);
+
+ aReply[12] = 0x03; /* BCA info */
+ aReply[13] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16(&aReply[14], 188 + 4);
+
+ aReply[16] = 0x04; /* Manufacturing info */
+ aReply[17] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16(&aReply[18], 2048 + 4);
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(&aReply[0], 16 + 2);
+
+ /* data written + 4 byte header */
+ uASC = (16 + 4);
+ break;
+ default: /** @todo formats beyond DVD-ROM requires */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ }
+
+ if (uASC < 0)
+ return vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ -uASC, 0x00);
+ break;
+ }
+ /** @todo BD support, fall through for now */
+ RT_FALL_THRU();
+
+ /* Generic disk structures */
+ case 0x80: /** @todo AACS volume identifier */
+ case 0x81: /** @todo AACS media serial number */
+ case 0x82: /** @todo AACS media identifier */
+ case 0x83: /** @todo AACS media key block */
+ case 0x90: /** @todo List of recognized format layers */
+ case 0xc0: /** @todo Write protection status */
+ default:
+ return vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, sizeof(aReply)));
+ return vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+}
+
+/**
+ * Processes the MODE SENSE 10 SCSI request.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLunMmc The MMC LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ */
+static int vscsiLunMmcModeSense10(PVSCSILUNMMC pVScsiLunMmc, PVSCSIREQINT pVScsiReq, size_t cbMaxTransfer)
+{
+ int rcReq;
+ uint8_t uPageControl = pVScsiReq->pbCDB[2] >> 6;
+ uint8_t uPageCode = pVScsiReq->pbCDB[2] & 0x3f;
+
+ switch (uPageControl)
+ {
+ case SCSI_PAGECONTROL_CURRENT:
+ switch (uPageCode)
+ {
+ case SCSI_MODEPAGE_ERROR_RECOVERY:
+ {
+ uint8_t aReply[16];
+
+ scsiH2BE_U16(&aReply[0], 16 + 6);
+ aReply[2] = (uint8_t)pVScsiLunMmc->u32MediaTrackType;
+ aReply[3] = 0;
+ aReply[4] = 0;
+ aReply[5] = 0;
+ aReply[6] = 0;
+ aReply[7] = 0;
+
+ aReply[8] = 0x01;
+ aReply[9] = 0x06;
+ aReply[10] = 0x00;
+ aReply[11] = 0x05;
+ aReply[12] = 0x00;
+ aReply[13] = 0x00;
+ aReply[14] = 0x00;
+ aReply[15] = 0x00;
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, sizeof(aReply)));
+ rcReq = vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+ break;
+ }
+ case SCSI_MODEPAGE_CD_STATUS:
+ {
+ uint8_t aReply[40];
+
+ scsiH2BE_U16(&aReply[0], 38);
+ aReply[2] = (uint8_t)pVScsiLunMmc->u32MediaTrackType;
+ aReply[3] = 0;
+ aReply[4] = 0;
+ aReply[5] = 0;
+ aReply[6] = 0;
+ aReply[7] = 0;
+
+ aReply[8] = 0x2a;
+ aReply[9] = 30; /* page length */
+ aReply[10] = 0x08; /* DVD-ROM read support */
+ aReply[11] = 0x00; /* no write support */
+ /* The following claims we support audio play. This is obviously false,
+ * but the Linux generic CDROM support makes many features depend on this
+ * capability. If it's not set, this causes many things to be disabled. */
+ aReply[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+ aReply[13] = 0x00; /* no subchannel reads supported */
+ aReply[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+ if (pVScsiLunMmc->fLocked)
+ aReply[14] |= 1 << 1; /* report lock state */
+ aReply[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+ scsiH2BE_U16(&aReply[16], 5632); /* (obsolete) claim 32x speed support */
+ scsiH2BE_U16(&aReply[18], 2); /* number of audio volume levels */
+ scsiH2BE_U16(&aReply[20], 128); /* buffer size supported in Kbyte - We don't have a buffer because we write directly into guest memory.
+ Just write some dummy value. */
+ scsiH2BE_U16(&aReply[22], 5632); /* (obsolete) current read speed 32x */
+ aReply[24] = 0; /* reserved */
+ aReply[25] = 0; /* reserved for digital audio (see idx 15) */
+ scsiH2BE_U16(&aReply[26], 0); /* (obsolete) maximum write speed */
+ scsiH2BE_U16(&aReply[28], 0); /* (obsolete) current write speed */
+ scsiH2BE_U16(&aReply[30], 0); /* copy management revision supported 0=no CSS */
+ aReply[32] = 0; /* reserved */
+ aReply[33] = 0; /* reserved */
+ aReply[34] = 0; /* reserved */
+ aReply[35] = 1; /* rotation control CAV */
+ scsiH2BE_U16(&aReply[36], 0); /* current write speed */
+ scsiH2BE_U16(&aReply[38], 0); /* number of write speed performance descriptors */
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, sizeof(aReply)));
+ rcReq = vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+ break;
+ }
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ }
+ break;
+ case SCSI_PAGECONTROL_CHANGEABLE:
+ case SCSI_PAGECONTROL_DEFAULT:
+ rcReq = vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ default:
+ case SCSI_PAGECONTROL_SAVED:
+ rcReq = vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED, 0x00);
+ break;
+ }
+
+ return rcReq;
+}
+
+/**
+ * Processes the GET EVENT STATUS NOTIFICATION SCSI request.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLunMmc The MMC LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ */
+static int vscsiLunMmcGetEventStatusNotification(PVSCSILUNMMC pVScsiLunMmc, PVSCSIREQINT pVScsiReq,
+ size_t cbMaxTransfer)
+{
+ uint32_t OldStatus;
+ uint32_t NewStatus;
+ uint8_t aReply[8];
+ RT_ZERO(aReply);
+
+ LogFlowFunc(("pVScsiLunMmc=%#p pVScsiReq=%#p cbMaxTransfer=%zu\n",
+ pVScsiLunMmc, pVScsiReq, cbMaxTransfer));
+
+ do
+ {
+ OldStatus = ASMAtomicReadU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus);
+ NewStatus = MMCEVENTSTATUSTYPE_UNCHANGED;
+
+ switch (OldStatus)
+ {
+ case MMCEVENTSTATUSTYPE_MEDIA_NEW:
+ /* mount */
+ scsiH2BE_U16(&aReply[0], 6);
+ aReply[2] = 0x04; /* media */
+ aReply[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ aReply[4] = 0x02; /* new medium */
+ aReply[5] = 0x02; /* medium present / door closed */
+ aReply[6] = 0x00;
+ aReply[7] = 0x00;
+ pVScsiLunMmc->Core.fReady = true;
+ break;
+
+ case MMCEVENTSTATUSTYPE_MEDIA_CHANGED:
+ case MMCEVENTSTATUSTYPE_MEDIA_REMOVED:
+ /* umount */
+ scsiH2BE_U16(&aReply[0], 6);
+ aReply[2] = 0x04; /* media */
+ aReply[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ aReply[4] = 0x03; /* media removal */
+ aReply[5] = 0x00; /* medium absent / door closed */
+ aReply[6] = 0x00;
+ aReply[7] = 0x00;
+ if (OldStatus == MMCEVENTSTATUSTYPE_MEDIA_CHANGED)
+ NewStatus = MMCEVENTSTATUSTYPE_MEDIA_NEW;
+ break;
+
+ case MMCEVENTSTATUSTYPE_MEDIA_EJECT_REQUESTED: /* currently unused */
+ scsiH2BE_U16(&aReply[0], 6);
+ aReply[2] = 0x04; /* media */
+ aReply[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ aReply[4] = 0x01; /* eject requested (eject button pressed) */
+ aReply[5] = 0x02; /* medium present / door closed */
+ aReply[6] = 0x00;
+ aReply[7] = 0x00;
+ break;
+
+ case MMCEVENTSTATUSTYPE_UNCHANGED:
+ default:
+ scsiH2BE_U16(&aReply[0], 6);
+ aReply[2] = 0x01; /* operational change request / notification */
+ aReply[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ aReply[4] = 0x00;
+ aReply[5] = 0x00;
+ aReply[6] = 0x00;
+ aReply[7] = 0x00;
+ break;
+ }
+
+ LogFlowFunc(("OldStatus=%u NewStatus=%u\n", OldStatus, NewStatus));
+
+ } while (!ASMAtomicCmpXchgU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus, NewStatus, OldStatus));
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(cbMaxTransfer, sizeof(aReply)));
+ return vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+}
+
+/**
+ * Processes a READ TRACK INFORMATION SCSI request.
+ *
+ * @returns SCSI status code.
+ * @param pVScsiLunMmc The MMC LUN instance.
+ * @param pVScsiReq The VSCSI request.
+ * @param cbMaxTransfer The maximum transfer size.
+ */
+static int vscsiLunMmcReadTrackInformation(PVSCSILUNMMC pVScsiLunMmc, PVSCSIREQINT pVScsiReq,
+ size_t cbMaxTransfer)
+{
+ int rcReq;
+ uint32_t u32LogAddr = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ uint8_t u8LogAddrType = pVScsiReq->pbCDB[1] & 0x03;
+
+ int rc = VINF_SUCCESS;
+ uint64_t u64LbaStart = 0;
+ uint32_t uRegion = 0;
+ uint64_t cBlocks = 0;
+ uint64_t cbBlock = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+
+ switch (u8LogAddrType)
+ {
+ case 0x00:
+ rc = vscsiLunMediumQueryRegionPropertiesForLba(&pVScsiLunMmc->Core, u32LogAddr, &uRegion,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = vscsiLunMediumQueryRegionProperties(&pVScsiLunMmc->Core, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ break;
+ case 0x01:
+ {
+ if (u32LogAddr >= 1)
+ {
+ uRegion = u32LogAddr - 1;
+ rc = vscsiLunMediumQueryRegionProperties(&pVScsiLunMmc->Core, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ }
+ else
+ rc = VERR_NOT_FOUND; /** @todo Return lead-in information. */
+ break;
+ }
+ case 0x02:
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t u8DataMode = 0xf; /* Unknown data mode. */
+ uint8_t u8TrackMode = 0;
+ uint8_t aReply[36];
+ RT_ZERO(aReply);
+
+ switch (enmDataForm)
+ {
+ case VDREGIONDATAFORM_MODE1_2048:
+ case VDREGIONDATAFORM_MODE1_2352:
+ case VDREGIONDATAFORM_MODE1_0:
+ u8DataMode = 1;
+ break;
+ case VDREGIONDATAFORM_XA_2336:
+ case VDREGIONDATAFORM_XA_2352:
+ case VDREGIONDATAFORM_XA_0:
+ case VDREGIONDATAFORM_MODE2_2336:
+ case VDREGIONDATAFORM_MODE2_2352:
+ case VDREGIONDATAFORM_MODE2_0:
+ u8DataMode = 2;
+ break;
+ default:
+ u8DataMode = 0xf;
+ }
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ u8TrackMode = 0x0;
+ else
+ u8TrackMode = 0x4;
+
+ scsiH2BE_U16(&aReply[0], 34);
+ aReply[2] = uRegion + 1; /* track number (LSB) */
+ aReply[3] = 1; /* session number (LSB) */
+ aReply[5] = (0 << 5) | (0 << 4) | u8TrackMode; /* not damaged, primary copy, data track */
+ aReply[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | u8DataMode; /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
+ aReply[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+ scsiH2BE_U32(&aReply[8], (uint32_t)u64LbaStart); /* track start address is 0 */
+ scsiH2BE_U32(&aReply[24], (uint32_t)cBlocks); /* track size */
+ aReply[32] = 0; /* track number (MSB) */
+ aReply[33] = 0; /* session number (MSB) */
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(sizeof(aReply), cbMaxTransfer));
+ rcReq = vscsiLunReqSenseOkSet(&pVScsiLunMmc->Core, pVScsiReq);
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(&pVScsiLunMmc->Core, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+
+ return rcReq;
+}
+
+static DECLCALLBACK(int) vscsiLunMmcInit(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNMMC pVScsiLunMmc = (PVSCSILUNMMC)pVScsiLun;
+ int rc = VINF_SUCCESS;
+
+ ASMAtomicWriteU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus, MMCEVENTSTATUSTYPE_UNCHANGED);
+ pVScsiLunMmc->u32MediaTrackType = MMC_MEDIA_TYPE_UNKNOWN;
+ pVScsiLunMmc->cSectors = 0;
+
+ uint32_t cTracks = vscsiLunMediumGetRegionCount(pVScsiLun);
+ if (cTracks)
+ {
+ for (uint32_t i = 0; i < cTracks; i++)
+ {
+ uint64_t cBlocks = 0;
+ rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, i, NULL, &cBlocks,
+ NULL, NULL);
+ AssertRC(rc);
+
+ pVScsiLunMmc->cSectors += cBlocks;
+ }
+
+ pVScsiLunMmc->Core.fMediaPresent = true;
+ pVScsiLunMmc->Core.fReady = false;
+ }
+ else
+ {
+ pVScsiLunMmc->Core.fMediaPresent = false;
+ pVScsiLunMmc->Core.fReady = false;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vscsiLunMmcDestroy(PVSCSILUNINT pVScsiLun)
+{
+ RT_NOREF1(pVScsiLun);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vscsiLunMmcReqProcess(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq)
+{
+ PVSCSILUNMMC pVScsiLunMmc = (PVSCSILUNMMC)pVScsiLun;
+ VSCSIIOREQTXDIR enmTxDir = VSCSIIOREQTXDIR_INVALID;
+ uint64_t uLbaStart = 0;
+ uint32_t cSectorTransfer = 0;
+ size_t cbSector = 0;
+ int rc = VINF_SUCCESS;
+ int rcReq = SCSI_STATUS_OK;
+ unsigned uCmd = pVScsiReq->pbCDB[0];
+ PCRTSGSEG paSegs = pVScsiReq->SgBuf.paSegs;
+ unsigned cSegs = pVScsiReq->SgBuf.cSegs;
+
+ LogFlowFunc(("pVScsiLun=%#p{.fReady=%RTbool, .fMediaPresent=%RTbool} pVScsiReq=%#p{.pbCdb[0]=%#x}\n",
+ pVScsiLun, pVScsiLun->fReady, pVScsiLun->fMediaPresent, pVScsiReq, uCmd));
+
+ /*
+ * GET CONFIGURATION, GET EVENT/STATUS NOTIFICATION, INQUIRY, and REQUEST SENSE commands
+ * operate even when a unit attention condition exists for initiator; every other command
+ * needs to report CHECK CONDITION in that case.
+ */
+ if ( !pVScsiLunMmc->Core.fReady
+ && uCmd != SCSI_INQUIRY
+ && uCmd != SCSI_GET_CONFIGURATION
+ && uCmd != SCSI_GET_EVENT_STATUS_NOTIFICATION)
+ {
+ /*
+ * A note on media changes: As long as a medium is not present, the unit remains in
+ * the 'not ready' state. Technically the unit becomes 'ready' soon after a medium
+ * is inserted; however, we internally keep the 'not ready' state until we've had
+ * a chance to report the UNIT ATTENTION status indicating a media change.
+ */
+ if (pVScsiLunMmc->Core.fMediaPresent)
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_UNIT_ATTENTION,
+ SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED, 0x00);
+ pVScsiLunMmc->Core.fReady = true;
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY,
+ SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ }
+ else
+ {
+ switch (uCmd)
+ {
+ case SCSI_TEST_UNIT_READY:
+ Assert(!pVScsiLunMmc->Core.fReady); /* Only should get here if LUN isn't ready. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ break;
+
+ case SCSI_INQUIRY:
+ {
+ SCSIINQUIRYDATA ScsiInquiryReply;
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[3]));
+ memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+
+ ScsiInquiryReply.cbAdditional = 31;
+ ScsiInquiryReply.fRMB = 1; /* Removable. */
+ ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_CD_DVD;
+ ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ ScsiInquiryReply.u3AnsiVersion = 0x05; /* MMC-?? compliant */
+ ScsiInquiryReply.fCmdQue = 1; /* Command queuing supported. */
+ ScsiInquiryReply.fWBus16 = 1;
+
+ const char *pszVendorId = "VBOX";
+ const char *pszProductId = "CD-ROM";
+ const char *pszProductLevel = "1.0";
+ int rcTmp = vscsiLunQueryInqStrings(pVScsiLun, &pszVendorId, &pszProductId, &pszProductLevel);
+ Assert(RT_SUCCESS(rcTmp) || rcTmp == VERR_NOT_FOUND); RT_NOREF(rcTmp);
+
+ scsiPadStrS(ScsiInquiryReply.achVendorId, pszVendorId, 8);
+ scsiPadStrS(ScsiInquiryReply.achProductId, pszProductId, 16);
+ scsiPadStrS(ScsiInquiryReply.achProductLevel, pszProductLevel, 4);
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, (uint8_t *)&ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_CAPACITY:
+ {
+ uint8_t aReply[8];
+ memset(aReply, 0, sizeof(aReply));
+ vscsiReqSetXferSize(pVScsiReq, sizeof(aReply));
+
+ /*
+ * If sector size exceeds the maximum value that is
+ * able to be stored in 4 bytes return 0xffffffff in this field
+ */
+ if (pVScsiLunMmc->cSectors > UINT32_C(0xffffffff))
+ scsiH2BE_U32(aReply, UINT32_C(0xffffffff));
+ else
+ scsiH2BE_U32(aReply, pVScsiLunMmc->cSectors - 1);
+ scsiH2BE_U32(&aReply[4], _2K);
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_MODE_SENSE_6:
+ {
+ uint8_t uModePage = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t aReply[24];
+ uint8_t *pu8ReplyPos;
+ bool fValid = false;
+
+ vscsiReqSetXferSize(pVScsiReq, pVScsiReq->pbCDB[4]);
+ memset(aReply, 0, sizeof(aReply));
+ aReply[0] = 4; /* Reply length 4. */
+ aReply[1] = 0; /* Default media type. */
+ aReply[2] = RT_BIT(4); /* Caching supported. */
+ aReply[3] = 0; /* Block descriptor length. */
+
+ pu8ReplyPos = aReply + 4;
+
+ if ((uModePage == 0x08) || (uModePage == 0x3f))
+ {
+ memset(pu8ReplyPos, 0, 20);
+ *pu8ReplyPos++ = 0x08; /* Page code. */
+ *pu8ReplyPos++ = 0x12; /* Size of the page. */
+ *pu8ReplyPos++ = 0x4; /* Write cache enabled. */
+ fValid = true;
+ } else if (uModePage == 0) {
+ fValid = true;
+ }
+
+ /* Querying unknown pages must fail. */
+ if (fValid) {
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ } else {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_MODE_SENSE_10:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcModeSense10(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_SEEK_10:
+ {
+ uint32_t uLba = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ if (uLba > pVScsiLunMmc->cSectors)
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ else
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_MODE_SELECT_6:
+ {
+ /** @todo implement!! */
+ vscsiReqSetXferSize(pVScsiReq, pVScsiReq->pbCDB[4]);
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_6:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = ((uint64_t) pVScsiReq->pbCDB[3]
+ | (pVScsiReq->pbCDB[2] << 8)
+ | ((pVScsiReq->pbCDB[1] & 0x1f) << 16));
+ cSectorTransfer = pVScsiReq->pbCDB[4];
+ cbSector = _2K;
+ break;
+ }
+ case SCSI_READ_10:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ cbSector = _2K;
+ break;
+ }
+ case SCSI_READ_12:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[6]);
+ cbSector = _2K;
+ break;
+ }
+ case SCSI_READ_16:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U64(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[10]);
+ cbSector = _2K;
+ break;
+ }
+ case SCSI_READ_CD:
+ {
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = (pVScsiReq->pbCDB[6] << 16) | (pVScsiReq->pbCDB[7] << 8) | pVScsiReq->pbCDB[8];
+
+ /*
+ * If the LBA is in an audio track we are required to ignore pretty much all
+ * of the channel selection values (except 0x00) and map everything to 0x10
+ * which means read user data with a sector size of 2352 bytes.
+ *
+ * (MMC-6 chapter 6.19.2.6)
+ */
+ uint8_t uChnSel = pVScsiReq->pbCDB[9] & 0xf8;
+ VDREGIONDATAFORM enmDataForm;
+ uint64_t cbSectorRegion = 0;
+ rc = vscsiLunMediumQueryRegionPropertiesForLba(pVScsiLun, uLbaStart,
+ NULL, NULL, &cbSectorRegion,
+ &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ {
+ if (uChnSel == 0)
+ {
+ /* nothing */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ }
+ else
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ cbSector = 2352;
+ }
+ }
+ else
+ {
+ switch (uChnSel)
+ {
+ case 0x00:
+ /* nothing */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ case 0x10:
+ /* normal read */
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ cbSector = _2K;
+ break;
+ case 0xf8:
+ {
+ if (cbSectorRegion == 2048)
+ {
+ /*
+ * Read all data, sector size is 2352.
+ * Rearrange the buffer and fill the gaps with the sync bytes.
+ */
+ /* Count the number of segments for the buffer we require. */
+ RTSGBUF SgBuf;
+ bool fBufTooSmall = false;
+ uint32_t cSegsNew = 0;
+ RTSgBufClone(&SgBuf, &pVScsiReq->SgBuf);
+ for (uint32_t uLba = (uint32_t)uLbaStart; uLba < uLbaStart + cSectorTransfer; uLba++)
+ {
+ size_t cbTmp = RTSgBufAdvance(&SgBuf, 16);
+ if (cbTmp < 16)
+ {
+ fBufTooSmall = true;
+ break;
+ }
+
+ cbTmp = 2048;
+ while (cbTmp)
+ {
+ size_t cbBuf = cbTmp;
+ RTSgBufGetNextSegment(&SgBuf, &cbBuf);
+ if (!cbBuf)
+ {
+ fBufTooSmall = true;
+ break;
+ }
+
+ cbTmp -= cbBuf;
+ cSegsNew++;
+ }
+
+ cbTmp = RTSgBufAdvance(&SgBuf, 280);
+ if (cbTmp < 280)
+ {
+ fBufTooSmall = true;
+ break;
+ }
+ }
+
+ if (!fBufTooSmall)
+ {
+ PRTSGSEG paSegsNew = (PRTSGSEG)RTMemAllocZ(cSegsNew * sizeof(RTSGSEG));
+ if (paSegsNew)
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+
+ uint32_t idxSeg = 0;
+ for (uint32_t uLba = (uint32_t)uLbaStart; uLba < uLbaStart + cSectorTransfer; uLba++)
+ {
+ /* Sync bytes, see 4.2.3.8 CD Main Channel Block Formats */
+ uint8_t abBuf[16];
+ abBuf[0] = 0x00;
+ memset(&abBuf[1], 0xff, 10);
+ abBuf[11] = 0x00;
+ /* MSF */
+ scsiLBA2MSF(&abBuf[12], uLba);
+ abBuf[15] = 0x01; /* mode 1 data */
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, &abBuf[0], sizeof(abBuf));
+
+ size_t cbTmp = 2048;
+ while (cbTmp)
+ {
+ size_t cbBuf = cbTmp;
+ paSegsNew[idxSeg].pvSeg = RTSgBufGetNextSegment(&pVScsiReq->SgBuf, &cbBuf);
+ paSegsNew[idxSeg].cbSeg = cbBuf;
+ idxSeg++;
+
+ cbTmp -= cbBuf;
+ }
+
+ /**
+ * @todo: maybe compute ECC and parity, layout is:
+ * 2072 4 EDC
+ * 2076 172 P parity symbols
+ * 2248 104 Q parity symbols
+ */
+ RTSgBufSet(&pVScsiReq->SgBuf, 0, 280);
+ }
+
+ paSegs = paSegsNew;
+ cSegs = cSegsNew;
+ pVScsiReq->pvLun = paSegsNew;
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ }
+ else if (cbSectorRegion == 2352)
+ {
+ /* Sector size matches what is read. */
+ cbSector = cbSectorRegion;
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ }
+ break;
+ }
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ }
+ }
+ break;
+ }
+ case SCSI_READ_BUFFER:
+ {
+ uint8_t uDataMode = pVScsiReq->pbCDB[1] & 0x1f;
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[6]));
+
+ switch (uDataMode)
+ {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x0a:
+ break;
+ case 0x0b:
+ {
+ uint8_t aReply[4];
+ RT_ZERO(aReply);
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case 0x1a:
+ case 0x1c:
+ break;
+ default:
+ AssertMsgFailed(("Invalid data mode\n"));
+ }
+ break;
+ }
+ case SCSI_VERIFY_10:
+ case SCSI_START_STOP_UNIT:
+ {
+ int rc2 = VINF_SUCCESS;
+ switch (pVScsiReq->pbCDB[4] & 3)
+ {
+ case 0: /* 00 - Stop motor */
+ case 1: /* 01 - Start motor */
+ break;
+ case 2: /* 10 - Eject media */
+ rc2 = vscsiLunMediumEject(pVScsiLun);
+ break;
+ case 3: /* 11 - Load media */
+ /** @todo */
+ break;
+ }
+ if (RT_SUCCESS(rc2))
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED, 0x02);
+ break;
+ }
+ case SCSI_LOG_SENSE:
+ {
+ uint8_t uPageCode = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t uSubPageCode = pVScsiReq->pbCDB[3];
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[7]));
+
+ switch (uPageCode)
+ {
+ case 0x00:
+ {
+ if (uSubPageCode == 0)
+ {
+ uint8_t aReply[4];
+
+ aReply[0] = 0;
+ aReply[1] = 0;
+ aReply[2] = 0;
+ aReply[3] = 0;
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_SERVICE_ACTION_IN_16:
+ {
+ switch (pVScsiReq->pbCDB[1] & 0x1f)
+ {
+ case SCSI_SVC_ACTION_IN_READ_CAPACITY_16:
+ {
+ uint8_t aReply[32];
+
+ memset(aReply, 0, sizeof(aReply));
+ scsiH2BE_U64(aReply, pVScsiLunMmc->cSectors - 1);
+ scsiH2BE_U32(&aReply[8], _2K);
+ /* Leave the rest 0 */
+
+ vscsiReqSetXferSize(pVScsiReq, sizeof(aReply));
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00); /* Don't know if this is correct */
+ }
+ break;
+ }
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ {
+ pVScsiLunMmc->fLocked = RT_BOOL(pVScsiReq->pbCDB[4] & 0x01);
+ vscsiLunMediumSetLock(pVScsiLun, pVScsiLunMmc->fLocked);
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ uint8_t format;
+ uint16_t cbMax;
+ bool fMSF;
+
+ format = pVScsiReq->pbCDB[2] & 0x0f;
+ cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ fMSF = (pVScsiReq->pbCDB[1] >> 1) & 1;
+
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ switch (format)
+ {
+ case 0x00:
+ rcReq = mmcReadTOCNormal(pVScsiLun, pVScsiReq, cbMax, fMSF);
+ break;
+ case 0x01:
+ rcReq = mmcReadTOCMulti(pVScsiLun, pVScsiReq, cbMax, fMSF);
+ break;
+ case 0x02:
+ rcReq = mmcReadTOCRaw(pVScsiLun, pVScsiReq, cbMax, fMSF);
+ break;
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ {
+ /* Only supporting polled mode at the moment. */
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ if (pVScsiReq->pbCDB[1] & 0x1)
+ rcReq = vscsiLunMmcGetEventStatusNotification(pVScsiLunMmc, pVScsiReq, cbMax);
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ }
+ case SCSI_MECHANISM_STATUS:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[8]);
+ uint8_t aReply[8];
+
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ scsiH2BE_U16(&aReply[0], 0);
+ /* no current LBA */
+ aReply[2] = 0;
+ aReply[3] = 0;
+ aReply[4] = 0;
+ aReply[5] = 1;
+ scsiH2BE_U16(&aReply[6], 0);
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(sizeof(aReply), cbMax));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_DISC_INFORMATION:
+ {
+ uint8_t aReply[34];
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ memset(aReply, '\0', sizeof(aReply));
+ scsiH2BE_U16(&aReply[0], 32);
+ aReply[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+ aReply[3] = 1; /* number of first track */
+ aReply[4] = 1; /* number of sessions (LSB) */
+ aReply[5] = 1; /* first track number in last session (LSB) */
+ aReply[6] = (uint8_t)vscsiLunMediumGetRegionCount(pVScsiLun); /* last track number in last session (LSB) */
+ aReply[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+ aReply[8] = 0; /* disc type = CD-ROM */
+ aReply[9] = 0; /* number of sessions (MSB) */
+ aReply[10] = 0; /* number of sessions (MSB) */
+ aReply[11] = 0; /* number of sessions (MSB) */
+ scsiH2BE_U32(&aReply[16], 0x00ffffff); /* last session lead-in start time is not available */
+ scsiH2BE_U32(&aReply[20], 0x00ffffff); /* last possible start time for lead-out is not available */
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, RT_MIN(sizeof(aReply), cbMax));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_TRACK_INFORMATION:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcReadTrackInformation(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_GET_CONFIGURATION:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcGetConfiguration(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_READ_DVD_STRUCTURE:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[8]);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcReadDvdStructure(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ default:
+ //AssertMsgFailed(("Command %#x [%s] not implemented\n", pVScsiReq->pbCDB[0], SCSICmdText(pVScsiReq->pbCDB[0])));
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+ }
+ }
+
+ if (enmTxDir != VSCSIIOREQTXDIR_INVALID)
+ {
+ LogFlow(("%s: uLbaStart=%llu cSectorTransfer=%u\n",
+ __FUNCTION__, uLbaStart, cSectorTransfer));
+
+ vscsiReqSetXferSize(pVScsiReq, cSectorTransfer * cbSector);
+ if (RT_UNLIKELY(uLbaStart + cSectorTransfer > pVScsiLunMmc->cSectors))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else if (!cSectorTransfer)
+ {
+ /* A 0 transfer length is not an error. */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else
+ {
+ /* Check that the sector size is valid. */
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+ rc = vscsiLunMediumQueryRegionPropertiesForLba(pVScsiLun, uLbaStart,
+ NULL, NULL, NULL, &enmDataForm);
+ if (RT_FAILURE(rc))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else if ( enmDataForm != VDREGIONDATAFORM_MODE1_2048
+ && enmDataForm != VDREGIONDATAFORM_MODE1_2352
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2336
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2352
+ && enmDataForm != VDREGIONDATAFORM_RAW
+ && cbSector == _2K)
+ {
+ rcReq = vscsiLunReqSenseErrorInfoSet(pVScsiLun, pVScsiReq,
+ SCSI_SENSE_ILLEGAL_REQUEST | SCSI_SENSE_FLAG_ILI,
+ SCSI_ASC_ILLEGAL_MODE_FOR_THIS_TRACK, 0, uLbaStart);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else
+ {
+ /* Enqueue new I/O request */
+ rc = vscsiIoReqTransferEnqueueEx(pVScsiLun, pVScsiReq, enmTxDir,
+ uLbaStart * cbSector,
+ paSegs, cSegs, cSectorTransfer * cbSector);
+ }
+ }
+ }
+ else /* Request completed */
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+
+ return rc;
+}
+
+/** @interface_method_impl{VSCSILUNDESC,pfnVScsiLunReqFree} */
+static DECLCALLBACK(void) vscsiLunMmcReqFree(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq,
+ void *pvLun)
+{
+ RT_NOREF2(pVScsiLun, pVScsiReq);
+ RTMemFree(pvLun);
+}
+
+/** @interface_method_impl{VSCSILUNDESC,pfnVScsiLunMediumInserted} */
+static DECLCALLBACK(int) vscsiLunMmcMediumInserted(PVSCSILUNINT pVScsiLun)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSILUNMMC pVScsiLunMmc = (PVSCSILUNMMC)pVScsiLun;
+
+ pVScsiLunMmc->cSectors = 0;
+ uint32_t cTracks = vscsiLunMediumGetRegionCount(pVScsiLun);
+ for (uint32_t i = 0; i < cTracks && RT_SUCCESS(rc); i++)
+ {
+ uint64_t cBlocks = 0;
+ rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, i, NULL, &cBlocks,
+ NULL, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ pVScsiLunMmc->cSectors += cBlocks;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus);
+ switch (OldStatus)
+ {
+ case MMCEVENTSTATUSTYPE_MEDIA_CHANGED:
+ case MMCEVENTSTATUSTYPE_MEDIA_REMOVED:
+ /* no change, we will send "medium removed" + "medium inserted" */
+ NewStatus = MMCEVENTSTATUSTYPE_MEDIA_CHANGED;
+ break;
+ default:
+ NewStatus = MMCEVENTSTATUSTYPE_MEDIA_NEW;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus,
+ NewStatus, OldStatus));
+
+ ASMAtomicXchgU32(&pVScsiLunMmc->u32MediaTrackType, MMC_MEDIA_TYPE_UNKNOWN);
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VSCSILUNDESC,pfnVScsiLunMediumRemoved} */
+static DECLCALLBACK(int) vscsiLunMmcMediumRemoved(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNMMC pVScsiLunMmc = (PVSCSILUNMMC)pVScsiLun;
+
+ ASMAtomicWriteU32((volatile uint32_t *)&pVScsiLunMmc->MediaEventStatus, MMCEVENTSTATUSTYPE_MEDIA_REMOVED);
+ ASMAtomicXchgU32(&pVScsiLunMmc->u32MediaTrackType, MMC_MEDIA_TYPE_NO_DISC);
+ pVScsiLunMmc->cSectors = 0;
+ return VINF_SUCCESS;
+}
+
+
+VSCSILUNDESC g_VScsiLunTypeMmc =
+{
+ /** enmLunType */
+ VSCSILUNTYPE_MMC,
+ /** pcszDescName */
+ "MMC",
+ /** cbLun */
+ sizeof(VSCSILUNMMC),
+ /** cSupOpcInfo */
+ 0,
+ /** paSupOpcInfo */
+ NULL,
+ /** pfnVScsiLunInit */
+ vscsiLunMmcInit,
+ /** pfnVScsiLunDestroy */
+ vscsiLunMmcDestroy,
+ /** pfnVScsiLunReqProcess */
+ vscsiLunMmcReqProcess,
+ /** pfnVScsiLunReqFree */
+ vscsiLunMmcReqFree,
+ /** pfnVScsiLunMediumInserted */
+ vscsiLunMmcMediumInserted,
+ /** pfnVScsiLunMediumRemoved */
+ vscsiLunMmcMediumRemoved
+};
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp
new file mode 100644
index 00000000..e67042fe
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp
@@ -0,0 +1,630 @@
+/* $Id: VSCSILunSbc.cpp $ */
+/** @file
+ * Virtual SCSI driver: SBC LUN implementation (hard disks)
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/cdefs.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "VSCSIInternal.h"
+
+/** Maximum of amount of LBAs to unmap with one command. */
+#define VSCSI_UNMAP_LBAS_MAX(a_cbSector) ((10*_1M) / a_cbSector)
+
+/**
+ * SBC LUN instance
+ */
+typedef struct VSCSILUNSBC
+{
+ /** Core LUN structure */
+ VSCSILUNINT Core;
+ /** Sector size of the medium. */
+ uint64_t cbSector;
+ /** Size of the virtual disk. */
+ uint64_t cSectors;
+ /** VPD page pool. */
+ VSCSIVPDPOOL VpdPagePool;
+} VSCSILUNSBC;
+/** Pointer to a SBC LUN instance */
+typedef VSCSILUNSBC *PVSCSILUNSBC;
+
+static DECLCALLBACK(int) vscsiLunSbcInit(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNSBC pVScsiLunSbc = (PVSCSILUNSBC)pVScsiLun;
+ int rc = VINF_SUCCESS;
+ int cVpdPages = 0;
+
+ uint32_t cRegions = vscsiLunMediumGetRegionCount(pVScsiLun);
+ if (cRegions != 1)
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, 0, NULL, &pVScsiLunSbc->cSectors,
+ &pVScsiLunSbc->cbSector, NULL);
+ if (RT_SUCCESS(rc))
+ rc = vscsiVpdPagePoolInit(&pVScsiLunSbc->VpdPagePool);
+
+ /* Create device identification page - mandatory. */
+ if (RT_SUCCESS(rc))
+ {
+ PVSCSIVPDPAGEDEVID pDevIdPage;
+
+ rc = vscsiVpdPagePoolAllocNewPage(&pVScsiLunSbc->VpdPagePool, VSCSI_VPD_DEVID_NUMBER,
+ VSCSI_VPD_DEVID_SIZE, (uint8_t **)&pDevIdPage);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Not conforming to the SPC spec but Solaris needs at least a stub to work. */
+ pDevIdPage->u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ pDevIdPage->u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ pDevIdPage->u16PageLength = RT_H2BE_U16(0x0);
+ cVpdPages++;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_UNMAP))
+ {
+ PVSCSIVPDPAGEBLOCKLIMITS pBlkPage;
+ PVSCSIVPDPAGEBLOCKPROV pBlkProvPage;
+
+ /* Create the page and fill it. */
+ rc = vscsiVpdPagePoolAllocNewPage(&pVScsiLunSbc->VpdPagePool, VSCSI_VPD_BLOCK_LIMITS_NUMBER,
+ VSCSI_VPD_BLOCK_LIMITS_SIZE, (uint8_t **)&pBlkPage);
+ if (RT_SUCCESS(rc))
+ {
+ pBlkPage->u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ pBlkPage->u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ pBlkPage->u16PageLength = RT_H2BE_U16(0x3c);
+ pBlkPage->u8MaxCmpWriteLength = 0;
+ pBlkPage->u16OptTrfLengthGran = 0;
+ pBlkPage->u32MaxTrfLength = 0;
+ pBlkPage->u32OptTrfLength = 0;
+ pBlkPage->u32MaxPreXdTrfLength = 0;
+ pBlkPage->u32MaxUnmapLbaCount = RT_H2BE_U32(VSCSI_UNMAP_LBAS_MAX(pVScsiLunSbc->cbSector));
+ pBlkPage->u32MaxUnmapBlkDescCount = UINT32_C(0xffffffff);
+ pBlkPage->u32OptUnmapGranularity = 0;
+ pBlkPage->u32UnmapGranularityAlignment = 0;
+ cVpdPages++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vscsiVpdPagePoolAllocNewPage(&pVScsiLunSbc->VpdPagePool, VSCSI_VPD_BLOCK_PROV_NUMBER,
+ VSCSI_VPD_BLOCK_PROV_SIZE, (uint8_t **)&pBlkProvPage);
+ if (RT_SUCCESS(rc))
+ {
+ pBlkProvPage->u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ pBlkProvPage->u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ pBlkProvPage->u16PageLength = RT_H2BE_U16(0x4);
+ pBlkProvPage->u8ThresholdExponent = 1;
+ pBlkProvPage->fLBPU = true;
+ cVpdPages++;
+ }
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_NON_ROTATIONAL))
+ {
+ PVSCSIVPDPAGEBLOCKCHARACTERISTICS pBlkPage;
+
+ /* Create the page and fill it. */
+ rc = vscsiVpdPagePoolAllocNewPage(&pVScsiLunSbc->VpdPagePool, VSCSI_VPD_BLOCK_CHARACTERISTICS_NUMBER,
+ VSCSI_VPD_BLOCK_CHARACTERISTICS_SIZE, (uint8_t **)&pBlkPage);
+ if (RT_SUCCESS(rc))
+ {
+ pBlkPage->u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ pBlkPage->u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ pBlkPage->u16PageLength = RT_H2BE_U16(0x3c);
+ pBlkPage->u16MediumRotationRate = RT_H2BE_U16(VSCSI_VPD_BLOCK_CHARACT_MEDIUM_ROTATION_RATE_NON_ROTATING);
+ cVpdPages++;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && cVpdPages)
+ {
+ PVSCSIVPDPAGESUPPORTEDPAGES pVpdPages;
+
+ rc = vscsiVpdPagePoolAllocNewPage(&pVScsiLunSbc->VpdPagePool, VSCSI_VPD_SUPPORTED_PAGES_NUMBER,
+ VSCSI_VPD_SUPPORTED_PAGES_SIZE + cVpdPages, (uint8_t **)&pVpdPages);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned idxVpdPage = 0;
+ pVpdPages->u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ pVpdPages->u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ pVpdPages->u16PageLength = RT_H2BE_U16(cVpdPages);
+
+ pVpdPages->abVpdPages[idxVpdPage++] = VSCSI_VPD_DEVID_NUMBER;
+
+ if (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_UNMAP)
+ {
+ pVpdPages->abVpdPages[idxVpdPage++] = VSCSI_VPD_BLOCK_LIMITS_NUMBER;
+ pVpdPages->abVpdPages[idxVpdPage++] = VSCSI_VPD_BLOCK_PROV_NUMBER;
+ }
+
+ if (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_NON_ROTATIONAL)
+ pVpdPages->abVpdPages[idxVpdPage++] = VSCSI_VPD_BLOCK_CHARACTERISTICS_NUMBER;
+ }
+ }
+
+ /* For SBC LUNs, there will be no ready state transitions. */
+ pVScsiLunSbc->Core.fReady = true;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vscsiLunSbcDestroy(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNSBC pVScsiLunSbc = (PVSCSILUNSBC)pVScsiLun;
+
+ vscsiVpdPagePoolDestroy(&pVScsiLunSbc->VpdPagePool);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vscsiLunSbcReqProcess(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq)
+{
+ PVSCSILUNSBC pVScsiLunSbc = (PVSCSILUNSBC)pVScsiLun;
+ int rc = VINF_SUCCESS;
+ int rcReq = SCSI_STATUS_OK;
+ uint64_t uLbaStart = 0;
+ uint32_t cSectorTransfer = 0;
+ VSCSIIOREQTXDIR enmTxDir = VSCSIIOREQTXDIR_INVALID;
+
+ switch(pVScsiReq->pbCDB[0])
+ {
+ case SCSI_INQUIRY:
+ {
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[3]));
+
+ /* Check for EVPD bit. */
+ if (pVScsiReq->pbCDB[1] & 0x1)
+ {
+ rc = vscsiVpdPagePoolQueryPage(&pVScsiLunSbc->VpdPagePool, pVScsiReq, pVScsiReq->pbCDB[2]);
+ if (RT_UNLIKELY(rc == VERR_NOT_FOUND))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ }
+ else if (pVScsiReq->pbCDB[2] != 0) /* A non zero page code is an error. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ else
+ {
+ SCSIINQUIRYDATA ScsiInquiryReply;
+
+ memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+
+ ScsiInquiryReply.cbAdditional = 31;
+ ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS;
+ ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ ScsiInquiryReply.u3AnsiVersion = 0x05; /* SPC-4 compliant */
+ ScsiInquiryReply.fCmdQue = 1; /* Command queuing supported. */
+ ScsiInquiryReply.fWBus16 = 1;
+
+ const char *pszVendorId = "VBOX";
+ const char *pszProductId = "HARDDISK";
+ const char *pszProductLevel = "1.0";
+ int rcTmp = vscsiLunQueryInqStrings(pVScsiLun, &pszVendorId, &pszProductId, &pszProductLevel);
+ Assert(RT_SUCCESS(rcTmp) || rcTmp == VERR_NOT_FOUND); RT_NOREF(rcTmp);
+
+ scsiPadStrS(ScsiInquiryReply.achVendorId, pszVendorId, 8);
+ scsiPadStrS(ScsiInquiryReply.achProductId, pszProductId, 16);
+ scsiPadStrS(ScsiInquiryReply.achProductLevel, pszProductLevel, 4);
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, (uint8_t *)&ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ }
+ break;
+ }
+ case SCSI_READ_CAPACITY:
+ {
+ uint8_t aReply[8];
+ memset(aReply, 0, sizeof(aReply));
+
+ vscsiReqSetXferSize(pVScsiReq, sizeof(aReply));
+
+ /*
+ * If sector size exceeds the maximum value that is
+ * able to be stored in 4 bytes return 0xffffffff in this field
+ */
+ if (pVScsiLunSbc->cSectors > UINT32_C(0xffffffff))
+ scsiH2BE_U32(aReply, UINT32_C(0xffffffff));
+ else
+ scsiH2BE_U32(aReply, pVScsiLunSbc->cSectors - 1);
+ scsiH2BE_U32(&aReply[4], (uint32_t)pVScsiLunSbc->cbSector);
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_MODE_SENSE_6:
+ {
+ uint8_t uModePage = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t aReply[24];
+ uint8_t *pu8ReplyPos;
+ bool fValid = false;
+
+ vscsiReqSetXferSize(pVScsiReq, pVScsiReq->pbCDB[4]);
+ memset(aReply, 0, sizeof(aReply));
+ aReply[0] = 4; /* Reply length 4. */
+ aReply[1] = 0; /* Default media type. */
+ aReply[2] = RT_BIT(4); /* Caching supported. */
+ aReply[3] = 0; /* Block descriptor length. */
+
+ if (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_READONLY)
+ aReply[2] |= RT_BIT(7); /* Set write protect bit */
+
+ pu8ReplyPos = aReply + 4;
+
+ if ((uModePage == 0x08) || (uModePage == 0x3f))
+ {
+ memset(pu8ReplyPos, 0, 20);
+ *pu8ReplyPos++ = 0x08; /* Page code. */
+ *pu8ReplyPos++ = 0x12; /* Size of the page. */
+ *pu8ReplyPos++ = 0x4; /* Write cache enabled. */
+ fValid = true;
+ } else if (uModePage == 0) {
+ fValid = true;
+ }
+
+ /* Querying unknown pages must fail. */
+ if (fValid) {
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ } else {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_MODE_SELECT_6:
+ {
+ uint8_t abParms[12];
+ size_t cbCopied;
+ size_t cbList = pVScsiReq->pbCDB[4];
+
+ vscsiReqSetXferSize(pVScsiReq, pVScsiReq->pbCDB[4]);
+
+ /* Copy the parameters. */
+ cbCopied = RTSgBufCopyToBuf(&pVScsiReq->SgBuf, &abParms[0], sizeof(abParms));
+
+ /* Handle short LOGICAL BLOCK LENGTH parameter. */
+ if ( !(pVScsiReq->pbCDB[1] & 0x01)
+ && cbCopied == sizeof(abParms)
+ && cbList >= 12
+ && abParms[3] == 8)
+ {
+ uint32_t cbBlock;
+
+ cbBlock = scsiBE2H_U24(&abParms[4 + 5]);
+ Log2(("SBC: set LOGICAL BLOCK LENGTH to %u\n", cbBlock));
+ if (cbBlock == 512) /* Fixed block size. */
+ {
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ }
+ /* Fail any other requests. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ }
+ case SCSI_READ_6:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = ((uint64_t) pVScsiReq->pbCDB[3]
+ | (pVScsiReq->pbCDB[2] << 8)
+ | ((pVScsiReq->pbCDB[1] & 0x1f) << 16));
+ cSectorTransfer = pVScsiReq->pbCDB[4];
+ break;
+ }
+ case SCSI_READ_10:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ break;
+ }
+ case SCSI_READ_12:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[6]);
+ break;
+ }
+ case SCSI_READ_16:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ uLbaStart = scsiBE2H_U64(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[10]);
+ break;
+ }
+ case SCSI_WRITE_6:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_WRITE;
+ uLbaStart = ((uint64_t) pVScsiReq->pbCDB[3]
+ | (pVScsiReq->pbCDB[2] << 8)
+ | ((pVScsiReq->pbCDB[1] & 0x1f) << 16));
+ cSectorTransfer = pVScsiReq->pbCDB[4];
+ break;
+ }
+ case SCSI_WRITE_10:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_WRITE;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+ break;
+ }
+ case SCSI_WRITE_12:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_WRITE;
+ uLbaStart = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[6]);
+ break;
+ }
+ case SCSI_WRITE_16:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_WRITE;
+ uLbaStart = scsiBE2H_U64(&pVScsiReq->pbCDB[2]);
+ cSectorTransfer = scsiBE2H_U32(&pVScsiReq->pbCDB[10]);
+ break;
+ }
+ case SCSI_SYNCHRONIZE_CACHE:
+ {
+ break; /* Handled below */
+ }
+ case SCSI_READ_BUFFER:
+ {
+ uint8_t uDataMode = pVScsiReq->pbCDB[1] & 0x1f;
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[6]));
+
+ switch (uDataMode)
+ {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x0a:
+ break;
+ case 0x0b:
+ {
+ uint8_t aReply[4];
+
+ /* We do not implement an echo buffer. */
+ memset(aReply, 0, sizeof(aReply));
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case 0x1a:
+ case 0x1c:
+ break;
+ default:
+ AssertMsgFailed(("Invalid data mode\n"));
+ }
+ break;
+ }
+ case SCSI_VERIFY_10:
+ case SCSI_START_STOP_UNIT:
+ {
+ vscsiReqSetXferSize(pVScsiReq, 0);
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_LOG_SENSE:
+ {
+ uint8_t uPageCode = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t uSubPageCode = pVScsiReq->pbCDB[3];
+
+ vscsiReqSetXferSize(pVScsiReq, scsiBE2H_U16(&pVScsiReq->pbCDB[7]));
+
+ switch (uPageCode)
+ {
+ case 0x00:
+ {
+ if (uSubPageCode == 0)
+ {
+ uint8_t aReply[4];
+
+ aReply[0] = 0;
+ aReply[1] = 0;
+ aReply[2] = 0;
+ aReply[3] = 0;
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ }
+ RT_FALL_THRU();
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_SERVICE_ACTION_IN_16:
+ {
+ switch (pVScsiReq->pbCDB[1] & 0x1f)
+ {
+ case SCSI_SVC_ACTION_IN_READ_CAPACITY_16:
+ {
+ uint8_t aReply[32];
+
+ memset(aReply, 0, sizeof(aReply));
+ scsiH2BE_U64(aReply, pVScsiLunSbc->cSectors - 1);
+ scsiH2BE_U32(&aReply[8], 512);
+ if (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_UNMAP)
+ aReply[14] = 0x80; /* LPME enabled */
+ /* Leave the rest 0 */
+
+ vscsiReqSetXferSize(pVScsiReq, sizeof(aReply));
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00); /* Don't know if this is correct */
+ }
+ break;
+ }
+ case SCSI_UNMAP:
+ {
+ if (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_UNMAP)
+ {
+ uint8_t abHdr[8];
+ size_t cbCopied;
+ size_t cbList = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+
+ /* Copy the header. */
+ vscsiReqSetXferSize(pVScsiReq, cbList);
+ cbCopied = RTSgBufCopyToBuf(&pVScsiReq->SgBuf, &abHdr[0], sizeof(abHdr));
+
+ /* Using the anchor bit is not supported. */
+ if ( !(pVScsiReq->pbCDB[1] & 0x01)
+ && cbCopied == sizeof(abHdr)
+ && cbList >= 8)
+ {
+ uint32_t cBlkDesc = scsiBE2H_U16(&abHdr[2]) / 16;
+
+ if (cBlkDesc)
+ {
+ PRTRANGE paRanges = (PRTRANGE)RTMemAllocZ(cBlkDesc * sizeof(RTRANGE));
+ if (paRanges)
+ {
+ for (unsigned i = 0; i < cBlkDesc; i++)
+ {
+ uint8_t abBlkDesc[16];
+
+ cbCopied = RTSgBufCopyToBuf(&pVScsiReq->SgBuf, &abBlkDesc[0], sizeof(abBlkDesc));
+ if (RT_UNLIKELY(cbCopied != sizeof(abBlkDesc)))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ break;
+ }
+
+ paRanges[i].offStart = scsiBE2H_U64(&abBlkDesc[0]) * 512;
+ paRanges[i].cbRange = scsiBE2H_U32(&abBlkDesc[8]) * 512;
+ }
+
+ if (rcReq == SCSI_STATUS_OK)
+ rc = vscsiIoReqUnmapEnqueue(pVScsiLun, pVScsiReq, paRanges, cBlkDesc);
+ if ( rcReq != SCSI_STATUS_OK
+ || RT_FAILURE(rc))
+ RTMemFree(paRanges);
+ }
+ else /* Out of memory. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_HARDWARE_ERROR, SCSI_ASC_SYSTEM_RESOURCE_FAILURE,
+ SCSI_ASCQ_SYSTEM_BUFFER_FULL);
+ }
+ else /* No block descriptors is not an error condition. */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ }
+ else /* Invalid CDB. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+
+ break;
+ }
+ default:
+ //AssertMsgFailed(("Command %#x [%s] not implemented\n", pRequest->pbCDB[0], SCSICmdText(pRequest->pbCDB[0])));
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+ }
+
+ if (enmTxDir != VSCSIIOREQTXDIR_INVALID)
+ {
+ LogFlow(("%s: uLbaStart=%llu cSectorTransfer=%u\n",
+ __FUNCTION__, uLbaStart, cSectorTransfer));
+
+ vscsiReqSetXferSize(pVScsiReq, cSectorTransfer * 512);
+
+ if (RT_UNLIKELY(uLbaStart + cSectorTransfer > pVScsiLunSbc->cSectors))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else if (!cSectorTransfer)
+ {
+ /* A 0 transfer length is not an error. */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else
+ {
+ /* Enqueue new I/O request */
+ if ( ( enmTxDir == VSCSIIOREQTXDIR_WRITE
+ || enmTxDir == VSCSIIOREQTXDIR_FLUSH)
+ && (pVScsiLun->fFeatures & VSCSI_LUN_FEATURE_READONLY))
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_DATA_PROTECT, SCSI_ASC_WRITE_PROTECTED, 0x00);
+ else
+ rc = vscsiIoReqTransferEnqueue(pVScsiLun, pVScsiReq, enmTxDir,
+ uLbaStart * 512, cSectorTransfer * 512);
+ }
+ }
+ else if (pVScsiReq->pbCDB[0] == SCSI_SYNCHRONIZE_CACHE)
+ {
+ /* Enqueue flush */
+ vscsiReqSetXferSize(pVScsiReq, 0);
+ rc = vscsiIoReqFlushEnqueue(pVScsiLun, pVScsiReq);
+ }
+ else if (pVScsiReq->pbCDB[0] != SCSI_UNMAP) /* Request completed */
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+
+ return rc;
+}
+
+VSCSILUNDESC g_VScsiLunTypeSbc =
+{
+ /** enmLunType */
+ VSCSILUNTYPE_SBC,
+ /** pcszDescName */
+ "SBC",
+ /** cbLun */
+ sizeof(VSCSILUNSBC),
+ /** cSupOpcInfo */
+ 0,
+ /** paSupOpcInfo */
+ NULL,
+ /** pfnVScsiLunInit */
+ vscsiLunSbcInit,
+ /** pfnVScsiLunDestroy */
+ vscsiLunSbcDestroy,
+ /** pfnVScsiLunReqProcess */
+ vscsiLunSbcReqProcess,
+ /** pfnVScsiLunReqFree */
+ NULL,
+ /** pfnVScsiLunMediumInserted */
+ NULL,
+ /** pfnVScsiLunMediumRemoved */
+ NULL
+};
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp
new file mode 100644
index 00000000..96b05696
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp
@@ -0,0 +1,459 @@
+/* $Id: VSCSILunSsc.cpp $ */
+/** @file
+ * Virtual SCSI driver: SSC LUN implementation (Streaming tape)
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <VBox/types.h>
+#include <VBox/vscsi.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "VSCSIInternal.h"
+
+/**
+ * SSC LUN instance
+ */
+typedef struct VSCSILUNSSC
+{
+ /** Core LUN structure */
+ VSCSILUNINT Core;
+ /** Size of the virtual tape. */
+ uint64_t cbTape;
+ /** Current position. */
+ uint64_t uCurPos;
+ /** Number of blocks. */
+ uint64_t cBlocks;
+ /** Block size. */
+ uint64_t cbBlock;
+ /** Medium locked indicator. */
+ bool fLocked;
+} VSCSILUNSSC, *PVSCSILUNSSC;
+
+
+static int vscsiLUNSSCInit(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNSSC pVScsiLunSsc = (PVSCSILUNSSC)pVScsiLun;
+ int rc = VINF_SUCCESS;
+
+ pVScsiLunSsc->cbBlock = 512; /* Default to 512-byte blocks. */
+ pVScsiLunSsc->uCurPos = 0; /* Start at beginning of tape. */
+ pVScsiLunSsc->cbTape = 0;
+
+ uint32_t cRegions = vscsiLunMediumGetRegionCount(pVScsiLun);
+ if (cRegions != 1)
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ rc = vscsiLunMediumQueryRegionProperties(pVScsiLun, 0, NULL, &pVScsiLunSsc->cBlocks,
+ &pVScsiLunSsc->cbBlock, NULL);
+
+ if (RT_SUCCESS(rc))
+ pVScsiLunSsc->cbTape = pVScsiLunSsc->cBlocks * pVScsiLunSsc->cbBlock;
+
+ return rc;
+}
+
+static int vscsiLUNSSCDestroy(PVSCSILUNINT pVScsiLun)
+{
+ PVSCSILUNSSC pVScsiLUNSSC = (PVSCSILUNSSC)pVScsiLun;
+
+ pVScsiLUNSSC->uCurPos = 0; // shut compiler up
+
+ return VINF_SUCCESS;
+}
+
+static int vscsiLUNSSCReqProcess(PVSCSILUNINT pVScsiLun, PVSCSIREQINT pVScsiReq)
+{
+ PVSCSILUNSSC pVScsiLUNSSC = (PVSCSILUNSSC)pVScsiLun;
+ VSCSIIOREQTXDIR enmTxDir = VSCSIIOREQTXDIR_INVALID;
+ uint64_t uTransferStart = 0;
+ uint32_t cBlocksTransfer = 0;
+ uint32_t cbTransfer = 0;
+ int rc = VINF_SUCCESS;
+ int rcReq = SCSI_STATUS_OK;
+ unsigned uCmd = pVScsiReq->pbCDB[0];
+
+ /*
+ * GET CONFIGURATION, GET EVENT/STATUS NOTIFICATION, INQUIRY, and REQUEST SENSE commands
+ * operate even when a unit attention condition exists for initiator; every other command
+ * needs to report CHECK CONDITION in that case.
+ */
+ if (!pVScsiLUNSSC->Core.fReady && uCmd != SCSI_INQUIRY)
+ {
+ /*
+ * A note on media changes: As long as a medium is not present, the unit remains in
+ * the 'not ready' state. Technically the unit becomes 'ready' soon after a medium
+ * is inserted; however, we internally keep the 'not ready' state until we've had
+ * a chance to report the UNIT ATTENTION status indicating a media change.
+ */
+ if (pVScsiLUNSSC->Core.fMediaPresent)
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_UNIT_ATTENTION,
+ SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED, 0x00);
+ pVScsiLUNSSC->Core.fReady = true;
+ }
+ else
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY,
+ SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ }
+ else
+ {
+ switch (uCmd)
+ {
+ case SCSI_TEST_UNIT_READY:
+ Assert(!pVScsiLUNSSC->Core.fReady); /* Only should get here if LUN isn't ready. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ break;
+
+ case SCSI_INQUIRY:
+ {
+ SCSIINQUIRYDATA ScsiInquiryReply;
+
+ memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
+
+ ScsiInquiryReply.cbAdditional = 31;
+ ScsiInquiryReply.fRMB = 1; /* Removable. */
+ ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_SEQUENTIAL_ACCESS;
+ ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_CONNECTED;
+ ScsiInquiryReply.u3AnsiVersion = 0x05; /* SSC-?? compliant */
+ ScsiInquiryReply.fCmdQue = 1; /* Command queuing supported. */
+ ScsiInquiryReply.fWBus16 = 1;
+ scsiPadStrS(ScsiInquiryReply.achVendorId, "VBOX", 8);
+ scsiPadStrS(ScsiInquiryReply.achProductId, "TAPE DRIVE", 16);
+ scsiPadStrS(ScsiInquiryReply.achProductLevel, "1.0", 4);
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, (uint8_t *)&ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_MODE_SENSE_6:
+ {
+// uint8_t uModePage = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t aReply[24];
+ uint8_t *pu8ReplyPos;
+ uint8_t uReplyLen;
+ bool fWantBlkDesc = !!(pVScsiReq->pbCDB[1] & RT_BIT(3)); /* DBD bit. */
+
+ memset(aReply, 0, sizeof(aReply));
+ if (fWantBlkDesc)
+ uReplyLen = 4 + 8;
+ else
+ uReplyLen = 4;
+
+ aReply[0] = uReplyLen - 1; /* Reply length. */
+ aReply[1] = 0xB6; /* Travan TR-4 medium (whatever). */
+ aReply[2] = 0; //RT_BIT(7); /* Write Protected. */ //@todo!
+ aReply[3] = uReplyLen - 4; /* Block descriptor length. */
+
+ pu8ReplyPos = aReply + 4;
+
+#if 0
+ if ((uModePage == 0x08) || (uModePage == 0x3f))
+ {
+ memset(pu8ReplyPos, 0, 20);
+ *pu8ReplyPos++ = 0x08; /* Page code. */
+ *pu8ReplyPos++ = 0x12; /* Size of the page. */
+ *pu8ReplyPos++ = 0x4; /* Write cache enabled. */
+ }
+#endif
+
+ /* Fill out the Block Descriptor. */
+ if (fWantBlkDesc)
+ {
+ *pu8ReplyPos++ = 0x45; /* Travan TR-4 density. */
+ *pu8ReplyPos++ = 0; /* All blocks are the same. */
+ *pu8ReplyPos++ = 0; /// @todo this calls for some macros!
+ *pu8ReplyPos++ = 0;
+ *pu8ReplyPos++ = 0; /* Reserved. */
+ *pu8ReplyPos++ = 0x00; /* Block length (512). */
+ *pu8ReplyPos++ = 0x02;
+ *pu8ReplyPos++ = 0x00;
+ }
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_MODE_SELECT_6:
+ {
+ /** @todo implement!! */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_READ_6:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_READ;
+ cbTransfer = ((uint32_t) pVScsiReq->pbCDB[4]
+ | (pVScsiReq->pbCDB[3] << 8)
+ | (pVScsiReq->pbCDB[2] << 16));
+ cBlocksTransfer = pVScsiReq->pbCDB[4];
+ uTransferStart = pVScsiLUNSSC->uCurPos;
+ pVScsiLUNSSC->uCurPos += cbTransfer;
+ break;
+ }
+ case SCSI_WRITE_6:
+ {
+ enmTxDir = VSCSIIOREQTXDIR_WRITE;
+ cbTransfer = ((uint32_t) pVScsiReq->pbCDB[4]
+ | (pVScsiReq->pbCDB[3] << 8)
+ | (pVScsiReq->pbCDB[2] << 16));
+ cBlocksTransfer = pVScsiReq->pbCDB[4];
+ uTransferStart = pVScsiLUNSSC->uCurPos;
+ pVScsiLUNSSC->uCurPos += cbTransfer;
+ break;
+ }
+ case SCSI_READ_BUFFER:
+ {
+ uint8_t uDataMode = pVScsiReq->pbCDB[1] & 0x1f;
+
+ switch (uDataMode)
+ {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x0a:
+ break;
+ case 0x0b:
+ {
+ uint8_t aReply[4];
+
+ /* We do not implement an echo buffer. */
+ memset(aReply, 0, sizeof(aReply));
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case 0x1a:
+ case 0x1c:
+ break;
+ default:
+ AssertMsgFailed(("Invalid data mode\n"));
+ }
+ break;
+ }
+ case SCSI_VERIFY_10:
+ case SCSI_LOAD_UNLOAD:
+ {
+ /// @todo should load/unload do anyhting? is verify even supported?
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_LOG_SENSE:
+ {
+ uint8_t uPageCode = pVScsiReq->pbCDB[2] & 0x3f;
+ uint8_t uSubPageCode = pVScsiReq->pbCDB[3];
+
+ switch (uPageCode)
+ {
+ case 0x00:
+ {
+ if (uSubPageCode == 0)
+ {
+ uint8_t aReply[4];
+
+ aReply[0] = 0;
+ aReply[1] = 0;
+ aReply[2] = 0;
+ aReply[3] = 0;
+
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ }
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00);
+ }
+ break;
+ }
+ case SCSI_SERVICE_ACTION_IN_16:
+ {
+ switch (pVScsiReq->pbCDB[1] & 0x1f)
+ {
+ default:
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET, 0x00); /* Don't know if this is correct */
+ }
+ break;
+ }
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ {
+ pVScsiLUNSSC->fLocked = pVScsiReq->pbCDB[4] & 1;
+ vscsiLunMediumSetLock(pVScsiLun, pVScsiLUNSSC->fLocked);
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ case SCSI_REWIND:
+ /// @todo flush data + write EOD? immed bit? partitions?
+ pVScsiLUNSSC->uCurPos = 0;
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ case SCSI_RESERVE_6:
+ /// @todo perform actual reservation
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ case SCSI_RELEASE_6:
+ /// @todo perform actual release
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ case SCSI_READ_BLOCK_LIMITS:
+ {
+ uint8_t aReply[6];
+
+ /* Report unrestricted block sizes (1-FFFFFFh). */
+ memset(aReply, 0, sizeof(aReply));
+ /// @todo Helpers for big-endian 16-bit/24-bit/32-bit constants?
+ aReply[1] = aReply[2] = aReply[3] = 0xff;
+ aReply[5] = 0x01;
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, aReply, sizeof(aReply));
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ break;
+ }
+ default:
+ //AssertMsgFailed(("Command %#x [%s] not implemented\n", pVScsiReq->pbCDB[0], SCSICmdText(pVScsiReq->pbCDB[0])));
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00);
+ }
+ }
+
+ if (enmTxDir != VSCSIIOREQTXDIR_INVALID)
+ {
+ LogFlow(("%s: uTransferStart=%llu cbTransfer=%u\n",
+ __FUNCTION__, uTransferStart, cbTransfer));
+
+ if (RT_UNLIKELY(uTransferStart + cbTransfer > pVScsiLUNSSC->cbTape))
+ {
+ uint64_t cbResidue = uTransferStart + cbTransfer - pVScsiLUNSSC->cbTape;
+
+ if (enmTxDir == VSCSIIOREQTXDIR_READ && cbResidue < cbTransfer)
+ {
+ /* If it's a read and some data is still available, read what we can. */
+ rc = vscsiIoReqTransferEnqueue(pVScsiLun, pVScsiReq, enmTxDir,
+ uTransferStart, cbTransfer - cbResidue);
+ rcReq = vscsiLunReqSenseErrorInfoSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NONE | SCSI_SENSE_FLAG_FILEMARK, SCSI_ASC_NONE, SCSI_ASCQ_FILEMARK_DETECTED, cbResidue);
+ }
+ else
+ {
+// rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_, SCSI_ASC_NONE, SCSI_ASCQ_END_OF_DATA_DETECTED);
+ /* Report a file mark. */
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NONE | SCSI_SENSE_FLAG_FILEMARK, SCSI_ASC_NONE, SCSI_ASCQ_FILEMARK_DETECTED);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ }
+ else if (!cbTransfer)
+ {
+ /* A 0 transfer length is not an error. */
+ rcReq = vscsiLunReqSenseOkSet(pVScsiLun, pVScsiReq);
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+ }
+ else
+ {
+ /* Enqueue new I/O request */
+ rc = vscsiIoReqTransferEnqueue(pVScsiLun, pVScsiReq, enmTxDir,
+ uTransferStart, cbTransfer);
+ }
+ }
+ else /* Request completed */
+ vscsiDeviceReqComplete(pVScsiLun->pVScsiDevice, pVScsiReq, rcReq, false, VINF_SUCCESS);
+
+ return rc;
+}
+
+/** @interface_method_impl{VSCSILUNDESC,pfnVScsiLunMediumInserted} */
+static DECLCALLBACK(int) vscsiLunSSCMediumInserted(PVSCSILUNINT pVScsiLun)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cRegions = vscsiLunMediumGetRegionCount(pVScsiLun);
+ if (cRegions != 1)
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ {
+#if 0
+ PVSCSILUNSSC pVScsiLUNSSC = (PVSCSILUNSSC)pVScsiLun;
+
+ pVScsiLUNSSC->cSectors = cbDisk / pVScsiLUNSSC->cbSector;
+
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32((volatile uint32_t *)&pVScsiLUNSSC->MediaEventStatus);
+ switch (OldStatus)
+ {
+ case MMCEVENTSTATUSTYPE_MEDIA_CHANGED:
+ case MMCEVENTSTATUSTYPE_MEDIA_REMOVED:
+ /* no change, we will send "medium removed" + "medium inserted" */
+ NewStatus = MMCEVENTSTATUSTYPE_MEDIA_CHANGED;
+ break;
+ default:
+ NewStatus = MMCEVENTSTATUSTYPE_MEDIA_NEW;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32((volatile uint32_t *)&pVScsiLUNSSC->MediaEventStatus,
+ NewStatus, OldStatus));
+
+ ASMAtomicXchgU32(&pVScsiLUNSSC->u32MediaTrackType, MMC_MEDIA_TYPE_UNKNOWN);
+#endif
+ }
+
+ return rc;
+}
+
+/** @interface_method_impl{VSCSILUNDESC,pfnVScsiLunMediumRemoved} */
+static DECLCALLBACK(int) vscsiLunSSCMediumRemoved(PVSCSILUNINT pVScsiLun)
+{
+ NOREF(pVScsiLun);
+#if 0
+ PVSCSILUNSSC pVScsiLUNSSC = (PVSCSILUNSSC)pVScsiLun;
+
+ ASMAtomicWriteU32((volatile uint32_t *)&pVScsiLUNSSC->MediaEventStatus, MMCEVENTSTATUSTYPE_MEDIA_REMOVED);
+ ASMAtomicXchgU32(&pVScsiLUNSSC->u32MediaTrackType, MMC_MEDIA_TYPE_NO_DISC);
+#endif
+ return VINF_SUCCESS;
+}
+
+VSCSILUNDESC g_VScsiLunTypeSsc =
+{
+ /** enmLunType */
+ VSCSILUNTYPE_SSC,
+ /** pcszDescName */
+ "SSC",
+ /** cbLun */
+ sizeof(VSCSILUNSSC),
+ /** cSupOpcInfo */
+ 0,
+ /** paSupOpcInfo */
+ NULL,
+ /** pfnVScsiLunInit */
+ vscsiLUNSSCInit,
+ /** pfnVScsiLunDestroy */
+ vscsiLUNSSCDestroy,
+ /** pfnVScsiLunReqProcess */
+ vscsiLUNSSCReqProcess,
+ /** pfnVScsiLunReqFree */
+ NULL,
+ /** pfnVScsiLunMediumInserted */
+ vscsiLunSSCMediumInserted,
+ /** pfnVScsiLunMediumRemoved */
+ vscsiLunSSCMediumRemoved
+};
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp
new file mode 100644
index 00000000..26ddedf3
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp
@@ -0,0 +1,88 @@
+/* $Id: VSCSISense.cpp $ */
+/** @file
+ * Virtual SCSI driver: Sense handling
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+#include "VSCSIInternal.h"
+
+void vscsiSenseInit(PVSCSISENSE pVScsiSense)
+{
+ memset(pVScsiSense->abSenseBuf, 0, sizeof(pVScsiSense->abSenseBuf));
+
+ /* Fill in valid sense information (can't be just zeros). */
+ pVScsiSense->abSenseBuf[0] = (1 << 7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+ pVScsiSense->abSenseBuf[2] = SCSI_SENSE_NONE;
+ pVScsiSense->abSenseBuf[7] = 10;
+ pVScsiSense->abSenseBuf[12] = SCSI_ASC_NONE;
+}
+
+int vscsiReqSenseOkSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq)
+{
+ memset(pVScsiSense->abSenseBuf, 0, sizeof(pVScsiSense->abSenseBuf));
+
+ pVScsiSense->abSenseBuf[0] = (1 << 7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+ pVScsiSense->abSenseBuf[2] = SCSI_SENSE_NONE;
+ pVScsiSense->abSenseBuf[7] = 10;
+ pVScsiSense->abSenseBuf[12] = SCSI_ASC_NONE;
+ pVScsiSense->abSenseBuf[13] = SCSI_ASC_NONE; /* Should be ASCQ but it has the same value for success. */
+
+ if (pVScsiReq->pbSense && pVScsiReq->cbSense)
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense));
+
+ return SCSI_STATUS_OK;
+}
+
+int vscsiReqSenseErrorSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey, uint8_t uSCSIASC, uint8_t uSCSIASCQ)
+{
+ memset(pVScsiSense->abSenseBuf, 0, sizeof(pVScsiSense->abSenseBuf));
+ pVScsiSense->abSenseBuf[0] = (1 << 7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+ pVScsiSense->abSenseBuf[2] = uSCSISenseKey;
+ pVScsiSense->abSenseBuf[7] = 10;
+ pVScsiSense->abSenseBuf[12] = uSCSIASC;
+ pVScsiSense->abSenseBuf[13] = uSCSIASCQ;
+
+ if (pVScsiReq->pbSense && pVScsiReq->cbSense)
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense));
+
+ return SCSI_STATUS_CHECK_CONDITION;
+}
+
+int vscsiReqSenseErrorInfoSet(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq, uint8_t uSCSISenseKey, uint8_t uSCSIASC, uint8_t uSCSIASCQ, uint32_t uInfo)
+{
+ memset(pVScsiSense->abSenseBuf, 0, sizeof(pVScsiSense->abSenseBuf));
+ pVScsiSense->abSenseBuf[0] = RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED; /* Fixed format */
+ pVScsiSense->abSenseBuf[2] = uSCSISenseKey;
+ scsiH2BE_U32(&pVScsiSense->abSenseBuf[3], uInfo);
+ pVScsiSense->abSenseBuf[7] = 10;
+ pVScsiSense->abSenseBuf[12] = uSCSIASC;
+ pVScsiSense->abSenseBuf[13] = uSCSIASCQ;
+
+ if (pVScsiReq->pbSense && pVScsiReq->cbSense)
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense));
+
+ return SCSI_STATUS_CHECK_CONDITION;
+}
+
+int vscsiReqSenseCmd(PVSCSISENSE pVScsiSense, PVSCSIREQINT pVScsiReq)
+{
+ /* Copy the current sense data to the buffer. */
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, pVScsiSense->abSenseBuf, sizeof(pVScsiSense->abSenseBuf));
+ return vscsiReqSenseOkSet(pVScsiSense, pVScsiReq);
+}
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp
new file mode 100644
index 00000000..70fb7243
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp
@@ -0,0 +1,117 @@
+/* $Id: VSCSIVpdPagePool.cpp $ */
+/** @file
+ * Virtual SCSI driver: VPD page pool
+ */
+
+/*
+ * Copyright (C) 2011-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VSCSI
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+
+#include "VSCSIInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * A VSCSI VPD page.
+ */
+typedef struct VSCSIVPDPAGE
+{
+ /** List node. */
+ RTLISTNODE NodePages;
+ /** Page size. */
+ size_t cbPage;
+ /** Page data - variable size. */
+ uint8_t abPage[1];
+} VSCSIVPDPAGE;
+/** Pointer to a VPD page. */
+typedef VSCSIVPDPAGE *PVSCSIVPDPAGE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+int vscsiVpdPagePoolInit(PVSCSIVPDPOOL pVScsiVpdPool)
+{
+ RTListInit(&pVScsiVpdPool->ListPages);
+ return VINF_SUCCESS;
+}
+
+
+void vscsiVpdPagePoolDestroy(PVSCSIVPDPOOL pVScsiVpdPool)
+{
+ PVSCSIVPDPAGE pIt, pItNext;
+
+ RTListForEachSafe(&pVScsiVpdPool->ListPages, pIt, pItNext, VSCSIVPDPAGE, NodePages)
+ {
+ RTListNodeRemove(&pIt->NodePages);
+ RTMemFree(pIt);
+ }
+}
+
+
+int vscsiVpdPagePoolAllocNewPage(PVSCSIVPDPOOL pVScsiVpdPool, uint8_t uPage, size_t cbPage, uint8_t **ppbPage)
+{
+ int rc = VINF_SUCCESS;
+ PVSCSIVPDPAGE pPage;
+
+ /* Check that the page doesn't exist already. */
+ RTListForEach(&pVScsiVpdPool->ListPages, pPage, VSCSIVPDPAGE, NodePages)
+ {
+ if (pPage->abPage[1] == uPage)
+ return VERR_ALREADY_EXISTS;
+ }
+
+ pPage = (PVSCSIVPDPAGE)RTMemAllocZ(RT_UOFFSETOF_DYN(VSCSIVPDPAGE, abPage[cbPage]));
+ if (pPage)
+ {
+ pPage->cbPage = cbPage;
+ pPage->abPage[1] = uPage;
+ RTListAppend(&pVScsiVpdPool->ListPages, &pPage->NodePages);
+ *ppbPage = &pPage->abPage[0];
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+int vscsiVpdPagePoolQueryPage(PVSCSIVPDPOOL pVScsiVpdPool, PVSCSIREQINT pVScsiReq, uint8_t uPage)
+{
+ PVSCSIVPDPAGE pPage;
+
+ /* Check that the page doesn't exist already. */
+ RTListForEach(&pVScsiVpdPool->ListPages, pPage, VSCSIVPDPAGE, NodePages)
+ {
+ if (pPage->abPage[1] == uPage)
+ {
+ RTSgBufCopyFromBuf(&pVScsiReq->SgBuf, &pPage->abPage[0], pPage->cbPage);
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
diff --git a/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h
new file mode 100644
index 00000000..32fbd036
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h
@@ -0,0 +1,202 @@
+/* $Id: VSCSIVpdPages.h $ */
+/** @file
+ * Virtual SCSI driver: Definitions for VPD pages.
+ */
+
+/*
+ * Copyright (C) 2011-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIVpdPages_h
+#define VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIVpdPages_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/stdint.h>
+
+/** VPD device identification page number. */
+#define VSCSI_VPD_DEVID_NUMBER 0x83
+/** VPD device identification size. */
+#define VSCSI_VPD_DEVID_SIZE 4
+/**
+ * Device identification VPD page data.
+ */
+#pragma pack(1)
+typedef struct VSCSIVPDPAGEDEVID
+{
+ /** Device type. */
+ unsigned u5PeripheralDeviceType : 5; /**< 0x00 / 00 */
+ /** Qualifier. */
+ unsigned u3PeripheralQualifier : 3;
+ /** Page number. */
+ unsigned u8PageCode : 8;
+ /** Page size (Big endian) */
+ unsigned u16PageLength : 16;
+} VSCSIVPDPAGEDEVID;
+#pragma pack()
+AssertCompileSize(VSCSIVPDPAGEDEVID, VSCSI_VPD_DEVID_SIZE);
+typedef VSCSIVPDPAGEDEVID *PVSCSIVPDPAGEDEVID;
+typedef const VSCSIVPDPAGEDEVID *PCVSCSIVPDPAGEDEVID;
+
+/** VPD supported VPD pages page number. */
+#define VSCSI_VPD_SUPPORTED_PAGES_NUMBER 0x00
+/** VPD supported VPD pages size. */
+#define VSCSI_VPD_SUPPORTED_PAGES_SIZE 4
+/**
+ * Block limits VPD page data.
+ */
+#pragma pack(1)
+typedef struct VSCSIVPDPAGESUPPORTEDPAGES
+{
+ /** Device type. */
+ unsigned u5PeripheralDeviceType : 5; /**< 0x00 / 00 */
+ /** Qualifier. */
+ unsigned u3PeripheralQualifier : 3;
+ /** Page number. */
+ unsigned u8PageCode : 8;
+ /** Page size (Big endian) */
+ unsigned u16PageLength : 16;
+ /** Supported pages array - variable. */
+ uint8_t abVpdPages[1];
+} VSCSIVPDPAGESUPPORTEDPAGES;
+#pragma pack()
+AssertCompileSize(VSCSIVPDPAGESUPPORTEDPAGES, VSCSI_VPD_SUPPORTED_PAGES_SIZE+1);
+typedef VSCSIVPDPAGESUPPORTEDPAGES *PVSCSIVPDPAGESUPPORTEDPAGES;
+typedef const VSCSIVPDPAGESUPPORTEDPAGES *PCVSCSIVPDPAGESUPPORTEDPAGES;
+
+/** VPD block characteristics page number. */
+#define VSCSI_VPD_BLOCK_CHARACTERISTICS_NUMBER 0xb1
+/** VPD block characteristics size. */
+#define VSCSI_VPD_BLOCK_CHARACTERISTICS_SIZE 64
+/**
+ * Block limits VPD page data.
+ */
+#pragma pack(1)
+typedef struct VSCSIVPDPAGEBLOCKCHARACTERISTICS
+{
+ /** Device type. */
+ unsigned u5PeripheralDeviceType : 5; /**< 0x00 / 00 */
+ /** Qualifier. */
+ unsigned u3PeripheralQualifier : 3;
+ /** Page number. */
+ unsigned u8PageCode : 8;
+ /** Page size (Big endian) */
+ unsigned u16PageLength : 16;
+ /** Medium rotation rate. */
+ unsigned u16MediumRotationRate : 16;
+ /** Reserved. */
+ unsigned u8Reserved : 8;
+ /** Nominal form factor. */
+ unsigned u4NominalFormFactor : 4;
+ /** Reserved */
+ unsigned u4Reserved : 4;
+ /** Reserved. */
+ uint8_t abReserved[56];
+} VSCSIVPDPAGEBLOCKCHARACTERISTICS;
+#pragma pack()
+AssertCompileSize(VSCSIVPDPAGEBLOCKCHARACTERISTICS, VSCSI_VPD_BLOCK_CHARACTERISTICS_SIZE);
+typedef VSCSIVPDPAGEBLOCKCHARACTERISTICS *PVSCSIVPDPAGEBLOCKCHARACTERISTICS;
+typedef const VSCSIVPDPAGEBLOCKCHARACTERISTICS *PCVSCSIVPDPAGEBLOCKCHARACTERISTICS;
+
+#define VSCSI_VPD_BLOCK_CHARACT_MEDIUM_ROTATION_RATE_NOT_REPORTED UINT16_C(0x0000)
+#define VSCSI_VPD_BLOCK_CHARACT_MEDIUM_ROTATION_RATE_NON_ROTATING UINT16_C(0x0001)
+
+/** VPD block limits page number. */
+#define VSCSI_VPD_BLOCK_LIMITS_NUMBER 0xb0
+/** VPD block limits size. */
+#define VSCSI_VPD_BLOCK_LIMITS_SIZE 64
+/**
+ * Block limits VPD page data.
+ */
+#pragma pack(1)
+typedef struct VSCSIVPDPAGEBLOCKLIMITS
+{
+ /** Device type. */
+ unsigned u5PeripheralDeviceType : 5; /**< 0x00 / 00 */
+ /** Qualifier. */
+ unsigned u3PeripheralQualifier : 3;
+ /** Page number. */
+ unsigned u8PageCode : 8;
+ /** Page size (Big endian) */
+ unsigned u16PageLength : 16;
+ /** Reserved. */
+ uint8_t u8Reserved;
+ /** Maximum compare and write length. */
+ uint8_t u8MaxCmpWriteLength;
+ /** Optimal transfer length granularity. */
+ uint16_t u16OptTrfLengthGran;
+ /** Maximum transfer length. */
+ uint32_t u32MaxTrfLength;
+ /** Optimal transfer length. */
+ uint32_t u32OptTrfLength;
+ /** Maximum PREFETCH, XDREAD and XDWRITE transfer length. */
+ uint32_t u32MaxPreXdTrfLength;
+ /** Maximum UNMAP LBA count. */
+ uint32_t u32MaxUnmapLbaCount;
+ /** Maximum UNMAP block descriptor count. */
+ uint32_t u32MaxUnmapBlkDescCount;
+ /** Optimal UNMAP granularity. */
+ uint32_t u32OptUnmapGranularity;
+ /** UNMAP granularity alignment. */
+ uint32_t u32UnmapGranularityAlignment;
+ /** Reserved. */
+ uint8_t abReserved[28];
+} VSCSIVPDPAGEBLOCKLIMITS;
+#pragma pack()
+AssertCompileSize(VSCSIVPDPAGEBLOCKLIMITS, VSCSI_VPD_BLOCK_LIMITS_SIZE);
+typedef VSCSIVPDPAGEBLOCKLIMITS *PVSCSIVPDPAGEBLOCKLIMITS;
+typedef const VSCSIVPDPAGEBLOCKLIMITS *PCVSCSIVPDPAGEBLOCKLIMITS;
+
+/** VPD block provisioning page number. */
+#define VSCSI_VPD_BLOCK_PROV_NUMBER 0xb2
+/** VPD block provisioning size. */
+#define VSCSI_VPD_BLOCK_PROV_SIZE 8
+/**
+ * Block provisioning VPD page data.
+ */
+#pragma pack(1)
+typedef struct VSCSIVPDPAGEBLOCKPROV
+{
+ /** Device type. */
+ unsigned u5PeripheralDeviceType : 5; /**< 0x00 / 00 */
+ /** Qualifier. */
+ unsigned u3PeripheralQualifier : 3;
+ /** Page number. */
+ unsigned u8PageCode : 8;
+ /** Page size (Big endian) */
+ unsigned u16PageLength : 16;
+ /** Threshold exponent. */
+ unsigned u8ThresholdExponent : 8;
+ /** Descriptor present. */
+ unsigned fDP : 1;
+ /** Anchored LBAs supported. */
+ unsigned fAncSup : 1;
+ /** Reserved. */
+ unsigned u4Reserved : 4;
+ /** WRITE SAME command supported. */
+ unsigned fLBPWS : 1;
+ /** UNMAP command supported. */
+ unsigned fLBPU : 1;
+ /** Provisioning type. */
+ unsigned u3ProvType : 3;
+ /** Reserved. */
+ unsigned u5Reserved : 5;
+ /** Reserved. */
+ unsigned u8Reserved : 8;
+} VSCSIVPDPAGEBLOCKPROV;
+#pragma pack()
+AssertCompileSize(VSCSIVPDPAGEBLOCKPROV, VSCSI_VPD_BLOCK_PROV_SIZE);
+typedef VSCSIVPDPAGEBLOCKPROV *PVSCSIVPDPAGEBLOCKPROV;
+typedef const VSCSIVPDPAGEBLOCKPROV *PCVSCSIVPDPAGEBLOCKPROVS;
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_VSCSI_VSCSIVpdPages_h */
+
diff --git a/src/VBox/Devices/Storage/swab.h b/src/VBox/Devices/Storage/swab.h
new file mode 100644
index 00000000..3c42fb12
--- /dev/null
+++ b/src/VBox/Devices/Storage/swab.h
@@ -0,0 +1,64 @@
+/* $Id: swab.h $ */
+/** @file
+ *
+ * VBox storage devices:
+ * C++-safe replacements for some Linux byte order macros
+ *
+ * On Linux, DrvHostDVD.cpp includes <linux/cdrom.h>, which in turn
+ * includes <linux/byteorder/swab.h>. Unfortunately, that file is not very
+ * C++ friendly, and our C++ compiler refuses to look at it. The solution
+ * is to define _LINUX_BYTEORDER_SWAB_H, which prevents that file's contents
+ * from getting included at all, and to provide, in this file, our own
+ * C++-proof versions of the macros which are needed by <linux/cdrom.h>
+ * before we include that file. We actually provide them as inline
+ * functions, due to the way they get resolved in the original.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Storage_swab_h
+#define VBOX_INCLUDED_SRC_Storage_swab_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define _LINUX_BYTEORDER_SWAB_H
+#define _LINUX_BYTEORDER_SWABB_H
+
+#include <asm/types.h>
+
+/* Sorry for the unnecessary brackets here, but I really think
+ readability requires them */
+static __inline__ __u16 __swab16p(const __u16 *px)
+{
+ __u16 x = *px;
+ return ((x & 0xff) << 8) | ((x >> 8) & 0xff);
+}
+
+static __inline__ __u32 __swab32p(const __u32 *px)
+{
+ __u32 x = *px;
+ return ((x & 0xff) << 24) | ((x & 0xff00) << 8)
+ | ((x >> 8) & 0xff00) | ((x >> 24) & 0xff);
+}
+
+static __inline__ __u64 __swab64p(const __u64 *px)
+{
+ __u64 x = *px;
+ return ((x & 0xff) << 56) | ((x & 0xff00) << 40)
+ | ((x & 0xff0000) << 24) | ((x & 0xff000000) << 8)
+ | ((x >> 8) & 0xff000000) | ((x >> 24) & 0xff0000)
+ | ((x >> 40) & 0xff00) | ((x >> 56) & 0xff);
+}
+
+#endif /* !VBOX_INCLUDED_SRC_Storage_swab_h */