summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Storage')
-rw-r--r--src/VBox/Devices/Storage/ATAPIPassthrough.cpp1012
-rw-r--r--src/VBox/Devices/Storage/ATAPIPassthrough.h57
-rw-r--r--src/VBox/Devices/Storage/Debug.cpp1195
-rw-r--r--src/VBox/Devices/Storage/DevAHCI.cpp6172
-rw-r--r--src/VBox/Devices/Storage/DevATA.cpp8484
-rw-r--r--src/VBox/Devices/Storage/DevBusLogic.cpp4506
-rw-r--r--src/VBox/Devices/Storage/DevFdc.cpp3208
-rw-r--r--src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp5526
-rw-r--r--src/VBox/Devices/Storage/DevLsiLogicSCSI.h3531
-rw-r--r--src/VBox/Devices/Storage/DevVirtioSCSI.cpp2772
-rw-r--r--src/VBox/Devices/Storage/DrvDiskIntegrity.cpp2160
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-darwin.cpp768
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp448
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-linux.cpp399
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-solaris.cpp431
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase-win.cpp568
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.cpp1584
-rw-r--r--src/VBox/Devices/Storage/DrvHostBase.h202
-rw-r--r--src/VBox/Devices/Storage/DrvHostDVD.cpp581
-rw-r--r--src/VBox/Devices/Storage/DrvHostFloppy.cpp120
-rw-r--r--src/VBox/Devices/Storage/DrvRamDisk.cpp1863
-rw-r--r--src/VBox/Devices/Storage/DrvSCSI.cpp1583
-rw-r--r--src/VBox/Devices/Storage/DrvVD.cpp5613
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-darwin.cpp542
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-generic.cpp70
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-win.cpp572
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt.h103
-rw-r--r--src/VBox/Devices/Storage/IOBufMgmt.cpp543
-rw-r--r--src/VBox/Devices/Storage/IOBufMgmt.h119
-rw-r--r--src/VBox/Devices/Storage/Makefile.kup0
-rw-r--r--src/VBox/Devices/Storage/UsbMsd.cpp2416
-rw-r--r--src/VBox/Devices/Storage/VBoxSCSI.h133
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp434
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h714
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp267
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp184
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp1797
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp655
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp469
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp107
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp128
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h212
-rw-r--r--src/VBox/Devices/Storage/swab.h74
43 files changed, 62322 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..55eda8f5
--- /dev/null
+++ b/src/VBox/Devices/Storage/ATAPIPassthrough.cpp
@@ -0,0 +1,1012 @@
+/* $Id: ATAPIPassthrough.cpp $ */
+/** @file
+ * VBox storage devices: ATAPI emulation (common code for DevATA and DevAHCI).
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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.
+ *
+ * @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.
+ * @param cbBuf The buffer size (max).
+ */
+static int atapiTrackListUpdateFromSendCueSheet(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf, size_t cbBuf)
+{
+ int rc;
+ 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;
+ AssertLogRelReturn(cTracks <= cbBuf, VERR_BUFFER_OVERFLOW);
+
+ 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, size_t cbBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf, cbBuf);
+ 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;
+ 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, size_t cbBuf)
+{
+ int rc;
+ uint16_t cbBuffer = (uint16_t)RT_MIN(scsiBE2H_U16(&pbCDB[7]), cbBuf);
+ 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, size_t cbBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf, cbBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static int atapiTrackListUpdateFromReadDvdStructure(PTRACKLIST pTrackList, const uint8_t *pbCDB,
+ const void *pvBuf, size_t cbBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf, cbBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static int atapiTrackListUpdateFromReadDiscInformation(PTRACKLIST pTrackList, const uint8_t *pbCDB,
+ const void *pvBuf, size_t cbBuf)
+{
+ RT_NOREF(pTrackList, pbCDB, pvBuf, cbBuf);
+ 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.
+ *
+ * @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 */
+
+/**
+ * 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)
+{
+ PTRACKLIST pTrackList = (PTRACKLIST)RTMemAllocZ(sizeof(TRACKLIST));
+ if (pTrackList)
+ {
+ *ppTrackList = pTrackList;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Destroys the allocated task list handle.
+ *
+ * @param pTrackList The track list handle to destroy.
+ */
+DECLHIDDEN(void) ATAPIPassthroughTrackListDestroy(PTRACKLIST pTrackList)
+{
+ if (pTrackList->paTracks)
+ RTMemFree(pTrackList->paTracks);
+ RTMemFree(pTrackList);
+}
+
+/**
+ * Clears all tracks from the given task list.
+ *
+ * @param pTrackList The track list to clear.
+ */
+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;
+}
+
+/**
+ * Updates the track list from the given CDB and data buffer.
+ *
+ * @returns VBox status code.
+ * @param pTrackList The track list to update.
+ * @param pbCDB The CDB buffer.
+ * @param pvBuf The data buffer.
+ * @param cbBuf The buffer isze.
+ */
+DECLHIDDEN(int) ATAPIPassthroughTrackListUpdate(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf, size_t cbBuf)
+{
+ int rc;
+
+ switch (pbCDB[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ rc = atapiTrackListUpdateFromSendCueSheet(pTrackList, pbCDB, pvBuf, cbBuf);
+ break;
+ case SCSI_SEND_DVD_STRUCTURE:
+ rc = atapiTrackListUpdateFromSendDvdStructure(pTrackList, pbCDB, pvBuf, cbBuf);
+ break;
+ case SCSI_READ_TOC_PMA_ATIP:
+ rc = atapiTrackListUpdateFromReadTocPmaAtip(pTrackList, pbCDB, pvBuf, cbBuf);
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ rc = atapiTrackListUpdateFromReadTrackInformation(pTrackList, pbCDB, pvBuf, cbBuf);
+ break;
+ case SCSI_READ_DVD_STRUCTURE:
+ rc = atapiTrackListUpdateFromReadDvdStructure(pTrackList, pbCDB, pvBuf, cbBuf);
+ break;
+ case SCSI_READ_DISC_INFORMATION:
+ rc = atapiTrackListUpdateFromReadDiscInformation(pTrackList, pbCDB, pvBuf, cbBuf);
+ 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;
+}
+
+/**
+ * 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)
+{
+ 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;
+}
+
+
+/**
+ * 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)
+{
+ 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..f5ea0ec4
--- /dev/null
+++ b/src/VBox/Devices/Storage/ATAPIPassthrough.h
@@ -0,0 +1,57 @@
+/* $Id: ATAPIPassthrough.h $ */
+/** @file
+ * VBox storage devices: ATAPI passthrough helpers (common code for DevATA and DevAHCI).
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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;
+
+DECLHIDDEN(int) ATAPIPassthroughTrackListCreateEmpty(PTRACKLIST *ppTrackList);
+DECLHIDDEN(void) ATAPIPassthroughTrackListDestroy(PTRACKLIST pTrackList);
+DECLHIDDEN(void) ATAPIPassthroughTrackListClear(PTRACKLIST pTrackList);
+DECLHIDDEN(int) ATAPIPassthroughTrackListUpdate(PTRACKLIST pTrackList, const uint8_t *pbCDB, const void *pvBuf, size_t cbBuf);
+DECLHIDDEN(uint32_t) ATAPIPassthroughTrackListGetSectorSizeFromLba(PTRACKLIST pTrackList, uint32_t iAtapiLba);
+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..b2af3a49
--- /dev/null
+++ b/src/VBox/Devices/Storage/Debug.cpp
@@ -0,0 +1,1195 @@
+/* $Id: Debug.cpp $ */
+/** @file
+ * VBox storage devices: debug helpers
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#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)"
+};
+
+static struct
+{
+ uint8_t uStatus;
+ const char * const pszStatusText;
+} g_aSCSIStatusText[]
+=
+{
+ { 0x00, "GOOD" },
+ { 0x02, "CHECK CONDITION" },
+ { 0x04, "CONDITION MET" },
+ { 0x08, "BUSY" },
+ { 0x10, "INTERMEDIATE"},
+ { 0x14, "CONDITION MET" },
+ { 0x18, "RESERVATION CONFLICT" },
+ { 0x22, "COMMAND TERMINATED" },
+ { 0x28, "TASK SET FULL" },
+ { 0x30, "ACA ACTIVE" },
+ { 0x40, "TASK ABORTED" },
+};
+
+/**
+ * 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)";
+}
+
+const char * SCSIStatusText(uint8_t uStatus)
+{
+ 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_aSCSIStatusText[iIdx].uStatus == uStatus)
+ return g_aSCSIStatusText[iIdx].pszStatusText;
+ }
+ return "(Unknown extended status code)";
+}
+
+/**
+ * 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..199dc04f
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevAHCI.cpp
@@ -0,0 +1,6172 @@
+/* $Id: DevAHCI.cpp $ */
+/** @file
+ * DevAHCI - AHCI controller device (disk and cdrom).
+ *
+ * Implements the AHCI standard 1.1
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @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 <VBox/AssertGuest.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.
+ */
+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;
+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];
+ /** 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;
+
+
+/**
+ * The shared state of an AHCI port.
+ */
+typedef struct AHCIPORT
+{
+ /** 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;
+ uint32_t u32Alignment1;
+ /** 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 afAlignment1[2];
+
+ /** 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;
+ /** The LUN (same as port number). */
+ uint32_t iLUN;
+ /** Set if there is a device present at the port. */
+ bool fPresent;
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** Exponent of logical sectors in a physical sector, number of logical sectors is 2^exp. */
+ uint8_t cLogSectorsPerPhysicalExp;
+ uint8_t bAlignment2;
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+
+ /** 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;
+
+ /** Physical geometry of this image. */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+
+ /** The status LED state for this drive. */
+ PDMLED Led;
+
+ /** 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;
+AssertCompileSizeAlignment(AHCIPORT, 8);
+/** Pointer to the shared state of an AHCI port. */
+typedef AHCIPORT *PAHCIPORT;
+
+
+/**
+ * The ring-3 state of an AHCI port.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIMEDIAPORT
+ * @implements PDMIMEDIAEXPORT
+ */
+typedef struct AHCIPORTR3
+{
+ /** Pointer to the device instance - only to get our bearings in an interface
+ * method, nothing else. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** The LUN (same as port number). */
+ uint32_t iLUN;
+
+ /** 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;
+ /** Port description. */
+ char szDesc[8];
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The extended media port interface. */
+ PDMIMEDIAEXPORT IMediaExPort;
+
+ /** Async IO Thread. */
+ R3PTRTYPE(PPDMTHREAD) pAsyncIOThread;
+ /** First task throwing an error. */
+ R3PTRTYPE(volatile PAHCIREQ) pTaskErr;
+
+} AHCIPORTR3;
+AssertCompileSizeAlignment(AHCIPORTR3, 8);
+/** Pointer to the ring-3 state of an AHCI port. */
+typedef AHCIPORTR3 *PAHCIPORTR3;
+
+
+/**
+ * Main AHCI device state.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct AHCI
+{
+ /** Global Host Control register of the HBA
+ * @todo r=bird: Make this a 'name' doxygen comment with { and add a
+ * corrsponding at-} where appropriate. I cannot tell where to put the
+ * latter. */
+
+ /** 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;
+
+ /** Countdown timer for command completion coalescing. */
+ TMTIMERHANDLE hHbaCccTimer;
+
+ /** Which port number is used to mark an CCC interrupt */
+ uint8_t uCccPortNr;
+ uint8_t abAlignment1[7];
+
+ /** 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 aPorts[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;
+
+ /** 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;
+ /** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */
+ volatile bool f8ByteMMIO4BytesWrittenSuccessfully;
+
+ /** Device is in a reset state.
+ * @todo r=bird: This isn't actually being modified by anyone... */
+ bool fReset;
+ /** Supports 64bit addressing
+ * @todo r=bird: This isn't really being modified by anyone (always false). */
+ bool f64BitAddr;
+ /** Flag whether the controller has BIOS access enabled.
+ * @todo r=bird: Not used, just queried from CFGM. */
+ bool fBootable;
+
+ bool afAlignment2[2];
+
+ /** Number of usable ports on this controller. */
+ uint32_t cPortsImpl;
+ /** Number of usable command slots for each port. */
+ uint32_t cCmdSlotsAvail;
+
+ /** PCI region \#0: Legacy IDE fake, 8 ports. */
+ IOMIOPORTHANDLE hIoPortsLegacyFake0;
+ /** PCI region \#1: Legacy IDE fake, 1 port. */
+ IOMIOPORTHANDLE hIoPortsLegacyFake1;
+ /** PCI region \#2: Legacy IDE fake, 8 ports. */
+ IOMIOPORTHANDLE hIoPortsLegacyFake2;
+ /** PCI region \#3: Legacy IDE fake, 1 port. */
+ IOMIOPORTHANDLE hIoPortsLegacyFake3;
+ /** PCI region \#4: BMDMA I/O port range, 16 ports, used for the Index/Data
+ * pair register access. */
+ IOMIOPORTHANDLE hIoPortIdxData;
+ /** PCI region \#5: MMIO registers. */
+ IOMMMIOHANDLE hMmio;
+} AHCI;
+AssertCompileMemberAlignment(AHCI, aPorts, 8);
+/** Pointer to the state of an AHCI device. */
+typedef AHCI *PAHCI;
+
+
+/**
+ * Main AHCI device ring-3 state.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct AHCIR3
+{
+ /** Pointer to the device instance - only for getting our bearings in
+ * interface methods. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** 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;
+
+ /** Register structure per port */
+ AHCIPORTR3 aPorts[AHCI_MAX_NR_PORTS_IMPL];
+
+ /** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when
+ * a port is entering the idle state. */
+ bool volatile fSignalIdle;
+ bool afAlignment7[2+4];
+} AHCIR3;
+/** Pointer to the ring-3 state of an AHCI device. */
+typedef AHCIR3 *PAHCIR3;
+
+
+/**
+ * Main AHCI device ring-0 state.
+ */
+typedef struct AHCIR0
+{
+ uint64_t uUnused;
+} AHCIR0;
+/** Pointer to the ring-0 state of an AHCI device. */
+typedef AHCIR0 *PAHCIR0;
+
+
+/**
+ * Main AHCI device raw-mode state.
+ */
+typedef struct AHCIRC
+{
+ uint64_t uUnused;
+} AHCIRC;
+/** Pointer to the raw-mode state of an AHCI device. */
+typedef AHCIRC *PAHCIRC;
+
+
+/** Main AHCI device current context state. */
+typedef CTX_SUFF(AHCI) AHCICC;
+/** Pointer to the current context state of an AHCI device. */
+typedef CTX_SUFF(PAHCI) PAHCICC;
+
+
+/**
+ * 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.
+ *
+ * @param pDevIns The device 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 DECLCALLBACKTYPE(void, FNAHCIR3MEMCOPYCALLBACK,(PPDMDEVINS pDevIns, RTGCPHYS GCPhys,
+ PRTSGBUF pSgBuf, size_t cbCopy, size_t *pcbSkip));
+/** Pointer to a memory copy buffer callback. */
+typedef FNAHCIR3MEMCOPYCALLBACK *PFNAHCIR3MEMCOPYCALLBACK;
+#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;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value);
+} AHCIOPREG;
+
+/**
+ * AHCI port register operator.
+ */
+typedef struct pAhciPort_opreg
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value);
+} AHCIPORTOPREG;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+static void ahciR3HBAReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIR3 pThisCC);
+static int ahciPostFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, unsigned uFisType, uint8_t *pCmdFis);
+static void ahciPostFirstD2HFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort);
+static size_t ahciR3CopyBufferToPrdtl(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, const void *pvSrc, size_t cbSrc, size_t cbSkip);
+static bool ahciR3CancelActiveTasks(PAHCIPORTR3 pAhciPortR3);
+#endif
+RT_C_DECLS_END
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#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(PPDMDEVINS pDevIns)
+{
+ Log(("%s: Clearing interrupt\n", __FUNCTION__));
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+}
+
+/**
+ * Updates the IRQ level and sets port bit in the global interrupt status register of the HBA.
+ */
+static int ahciHbaSetInterrupt(PPDMDEVINS pDevIns, PAHCI pThis, uint8_t iPort, int rcBusy)
+{
+ Log(("P%u: %s: Setting interrupt\n", iPort, __FUNCTION__));
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if (pThis->regHbaCtrl & AHCI_HBA_CTRL_IE)
+ {
+ if ((pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN) && (pThis->regHbaCccPorts & (1 << iPort)))
+ {
+ pThis->uCccCurrentNr++;
+ if (pThis->uCccCurrentNr >= pThis->uCccNr)
+ {
+ /* Reset command completion coalescing state. */
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hHbaCccTimer, pThis->uCccTimeout);
+ pThis->uCccCurrentNr = 0;
+
+ pThis->u32PortsInterrupted |= (1 << pThis->uCccPortNr);
+ if (!(pThis->u32PortsInterrupted & ~(1 << pThis->uCccPortNr)))
+ {
+ Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+ PDMDevHlpPCISetIrq(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 *)&pThis->u32PortsInterrupted, (1 << iPort));
+ if (!(pThis->u32PortsInterrupted & ~(1 << iPort)))
+ {
+ Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__));
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ }
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->lock);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV, Assert irq when an CCC timeout occurs.}
+ */
+static DECLCALLBACK(void) ahciCccTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, hTimer);
+ PAHCI pThis = (PAHCI)pvUser;
+
+ int rc = ahciHbaSetInterrupt(pDevIns, pThis, pThis->uCccPortNr, VERR_IGNORED);
+ AssertRC(rc);
+}
+
+/**
+ * Finishes the port reset of the given port.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The port to finish the reset on, shared bits.
+ * @param pAhciPortR3 The port to finish the reset on, ring-3 bits.
+ */
+static void ahciPortResetFinish(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3)
+{
+ ahciLog(("%s: Initiated.\n", __FUNCTION__));
+
+ /* Cancel all tasks first. */
+ bool fAllTasksCanceled = ahciR3CancelActiveTasks(pAhciPortR3);
+ 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(pDevIns, pAhciPort);
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS);
+
+ if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+ {
+ int rc = ahciHbaSetInterrupt(pDevIns, pThis, 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pAhciPort The port to kick, shared bits.
+ */
+static void ahciIoThreadKick(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort)
+{
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+}
+
+static VBOXSTRICTRC PortCmdIssue_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ RT_NOREF(pThis, iReg);
+
+ /* 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(pDevIns, 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 VBOXSTRICTRC PortCmdIssue_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortSActive_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+ RT_NOREF(pDevIns, pThis, iReg);
+
+ pAhciPort->regSACT |= u32Value;
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC PortSActive_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortSError_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortSError_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ ahciLog(("%s: read regSERR=%#010x\n", __FUNCTION__, pAhciPort->regSERR));
+ *pu32Value = pAhciPort->regSERR;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC PortSControl_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pThis, 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_NOREF(pDevIns, 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", 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->fPresent)
+ {
+ /* Do the port reset here, so the guest sees the new status immediately. */
+ if (pThis->fLegacyPortResetMethod)
+ {
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PAHCIPORTR3 pAhciPortR3 = &RT_SAFE_SUBSCRIPT(pThisCC->aPorts, pAhciPort->iLUN);
+ ahciPortResetFinish(pDevIns, pThis, pAhciPort, pAhciPortR3);
+ pAhciPort->regSCTL = u32Value; /* Update after finishing the reset, so the I/O thread doesn't get a chance to do the reset. */
+ }
+ else
+ {
+ if (!pThis->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(pDevIns, pAhciPort);
+ }
+ }
+ else /* Just update the value if there is no device attached. */
+ pAhciPort->regSCTL = u32Value;
+
+ return VINF_SUCCESS;
+#endif
+}
+
+static VBOXSTRICTRC PortSControl_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortSStatus_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortSignature_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ ahciLog(("%s: read regSIG=%#010x\n", __FUNCTION__, pAhciPort->regSIG));
+ *pu32Value = pAhciPort->regSIG;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC PortTaskFileData_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmd_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmd_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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->fPresent
+ && !(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);
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+ }
+ }
+ else
+ {
+ if (!pAhciPort->fPresent)
+ 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->fPresent)
+ {
+ 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(pDevIns, pAhciPort);
+ ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS);
+
+ if (pAhciPort->regIE & AHCI_PORT_IE_DHRE)
+ {
+ int rc = ahciHbaSetInterrupt(pDevIns, pThis, 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->fPresent)
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ ahciPostFirstD2HFisIntoMemory(pDevIns, 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 VBOXSTRICTRC PortIntrEnable_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortIntrEnable_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(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(pDevIns, pThis, pAhciPort->iLUN, VINF_IOM_R3_MMIO_WRITE);
+
+ if (rc == VINF_SUCCESS)
+ pAhciPort->regIE = u32Value;
+
+ return rc;
+}
+
+/**
+ * Read from the port interrupt status register.
+ */
+static VBOXSTRICTRC PortIntrSts_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortIntrSts_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortFisAddrUp_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortFisAddrUp_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortFisAddr_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortFisAddr_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmdLstAddrUp_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmdLstAddrUp_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmdLstAddr_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC PortCmdLstAddr_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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 VBOXSTRICTRC HbaVersion_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: read regHbaVs=%#010x\n", __FUNCTION__, pThis->regHbaVs));
+ *pu32Value = pThis->regHbaVs;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global Ports implemented register.
+ */
+static VBOXSTRICTRC HbaPortsImplemented_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: read regHbaPi=%#010x\n", __FUNCTION__, pThis->regHbaPi));
+ *pu32Value = pThis->regHbaPi;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global interrupt status register.
+ */
+static VBOXSTRICTRC HbaInterruptStatus_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(iReg);
+ Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ pThis->regHbaIs &= ~(u32Value);
+
+ /*
+ * Update interrupt status register and check for ports who
+ * set the interrupt inbetween.
+ */
+ bool fClear = true;
+ pThis->regHbaIs |= ASMAtomicXchgU32(&pThis->u32PortsInterrupted, 0);
+ if (!pThis->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 = &pThis->aPorts[i];
+
+ if (pAhciPort->regIE & pAhciPort->regIS)
+ {
+ Log(("%s: Interrupt status of port %u set -> Set interrupt again\n", __FUNCTION__, i));
+ ASMAtomicOrU32(&pThis->u32PortsInterrupted, 1 << i);
+ fClear = false;
+ break;
+ }
+ }
+ u32Value >>= 1;
+ i++;
+ }
+ }
+ else
+ fClear = false;
+
+ if (fClear)
+ ahciHbaClearInterrupt(pDevIns);
+ else
+ {
+ Log(("%s: Not clearing interrupt: u32PortsInterrupted=%#010x\n", __FUNCTION__, pThis->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(pDevIns, 0, 0);
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->lock);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read from the global interrupt status register.
+ */
+static VBOXSTRICTRC HbaInterruptStatus_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(iReg);
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, VINF_IOM_R3_MMIO_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint32_t u32PortsInterrupted = ASMAtomicXchgU32(&pThis->u32PortsInterrupted, 0);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->lock);
+ Log(("%s: read regHbaIs=%#010x u32PortsInterrupted=%#010x\n", __FUNCTION__, pThis->regHbaIs, u32PortsInterrupted));
+
+ pThis->regHbaIs |= u32PortsInterrupted;
+
+#ifdef LOG_ENABLED
+ Log(("%s:", __FUNCTION__));
+ uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts));
+ for (unsigned i = 0; i < cPortsImpl; i++)
+ {
+ if ((pThis->regHbaIs >> i) & 0x01)
+ Log((" P%d", i));
+ }
+ Log(("\n"));
+#endif
+
+ *pu32Value = pThis->regHbaIs;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global control register.
+ */
+static VBOXSTRICTRC HbaControl_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ 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)));
+ RT_NOREF(iReg);
+
+#ifndef IN_RING3
+ RT_NOREF(pDevIns, pThis, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ /*
+ * Increase the active thread counter because we might set the host controller
+ * reset bit.
+ */
+ ASMAtomicIncU32(&pThis->cThreadsActive);
+ ASMAtomicWriteU32(&pThis->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(&pThis->cThreadsActive);
+ if ( (u32Value & AHCI_HBA_CTRL_HR)
+ && !cThreadsActive)
+ ahciR3HBAReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC));
+
+ return VINF_SUCCESS;
+#endif
+}
+
+/**
+ * Read the global control register.
+ */
+static VBOXSTRICTRC HbaControl_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: read regHbaCtrl=%#010x\n"
+ "%s: AE=%d IE=%d HR=%d\n",
+ __FUNCTION__, pThis->regHbaCtrl,
+ __FUNCTION__, (pThis->regHbaCtrl & AHCI_HBA_CTRL_AE) >> 31, (pThis->regHbaCtrl & AHCI_HBA_CTRL_IE) >> 1,
+ (pThis->regHbaCtrl & AHCI_HBA_CTRL_HR)));
+ *pu32Value = pThis->regHbaCtrl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global capabilities register.
+ */
+static VBOXSTRICTRC HbaCapabilities_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, 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__, pThis->regHbaCap,
+ __FUNCTION__, (pThis->regHbaCap & AHCI_HBA_CAP_S64A) >> 31, (pThis->regHbaCap & AHCI_HBA_CAP_SNCQ) >> 30,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SIS) >> 28, (pThis->regHbaCap & AHCI_HBA_CAP_SSS) >> 27,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SALP) >> 26, (pThis->regHbaCap & AHCI_HBA_CAP_SAL) >> 25,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SCLO) >> 24, (pThis->regHbaCap & AHCI_HBA_CAP_ISS) >> 20,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SNZO) >> 19, (pThis->regHbaCap & AHCI_HBA_CAP_SAM) >> 18,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SPM) >> 17, (pThis->regHbaCap & AHCI_HBA_CAP_PMD) >> 15,
+ (pThis->regHbaCap & AHCI_HBA_CAP_SSC) >> 14, (pThis->regHbaCap & AHCI_HBA_CAP_PSC) >> 13,
+ (pThis->regHbaCap & AHCI_HBA_CAP_NCS) >> 8, (pThis->regHbaCap & AHCI_HBA_CAP_NP)));
+ *pu32Value = pThis->regHbaCap;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing control register.
+ */
+static VBOXSTRICTRC HbaCccCtl_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(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)));
+
+ pThis->regHbaCccCtl = u32Value;
+ pThis->uCccTimeout = AHCI_HBA_CCC_CTL_TV_GET(u32Value);
+ pThis->uCccPortNr = AHCI_HBA_CCC_CTL_INT_GET(u32Value);
+ pThis->uCccNr = AHCI_HBA_CCC_CTL_CC_GET(u32Value);
+
+ if (u32Value & AHCI_HBA_CCC_CTL_EN)
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hHbaCccTimer, pThis->uCccTimeout); /* Arm the timer */
+ else
+ PDMDevHlpTimerStop(pDevIns, pThis->hHbaCccTimer);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing control register.
+ */
+static VBOXSTRICTRC HbaCccCtl_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: read regHbaCccCtl=%#010x\n"
+ "%s: TV=%d CC=%d INT=%d EN=%d\n",
+ __FUNCTION__, pThis->regHbaCccCtl,
+ __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(pThis->regHbaCccCtl), AHCI_HBA_CCC_CTL_CC_GET(pThis->regHbaCccCtl),
+ AHCI_HBA_CCC_CTL_INT_GET(pThis->regHbaCccCtl), (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)));
+ *pu32Value = pThis->regHbaCccCtl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the global command completion coalescing ports register.
+ */
+static VBOXSTRICTRC HbaCccPorts_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value));
+
+ pThis->regHbaCccPorts = u32Value;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the global command completion coalescing ports register.
+ */
+static VBOXSTRICTRC HbaCccPorts_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log(("%s: read regHbaCccPorts=%#010x\n", __FUNCTION__, pThis->regHbaCccPorts));
+
+#ifdef LOG_ENABLED
+ Log(("%s:", __FUNCTION__));
+ uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts));
+ for (unsigned i = 0; i < cPortsImpl; i++)
+ {
+ if ((pThis->regHbaCccPorts >> i) & 0x01)
+ Log((" P%d", i));
+ }
+ Log(("\n"));
+#endif
+
+ *pu32Value = pThis->regHbaCccPorts;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid write to global register
+ */
+static VBOXSTRICTRC HbaInvalid_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg, u32Value);
+ Log(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port write.
+ */
+static VBOXSTRICTRC PortInvalid_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, pAhciPort, iReg, u32Value);
+ ahciLog(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalid Port read.
+ */
+static VBOXSTRICTRC PortInvalid_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, 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, shared bits.
+ * @param pAhciPortR3 The port to reset, ring-3 bits.
+ */
+static void ahciR3PortSwReset(PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3)
+{
+ bool fAllTasksCanceled;
+
+ /* Cancel all tasks first. */
+ fAllTasksCanceled = ahciR3CancelActiveTasks(pAhciPortR3);
+ 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->fPresent)
+ {
+ 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, shared bits.
+ */
+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 pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pThisCC The ring-3 AHCI state.
+ */
+static void ahciR3HBAReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIR3 pThisCC)
+{
+ unsigned i;
+ int rc = VINF_SUCCESS;
+
+ LogRel(("AHCI#%u: Reset the HBA\n", pDevIns->iInstance));
+
+ /* Stop the CCC timer. */
+ if (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN)
+ {
+ rc = PDMDevHlpTimerStop(pDevIns, pThis->hHbaCccTimer);
+ if (RT_FAILURE(rc))
+ AssertMsgFailed(("%s: Failed to stop timer!\n", __FUNCTION__));
+ }
+
+ /* Reset every port */
+ uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts));
+ for (i = 0; i < cPortsImpl; i++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[i];
+ PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i];
+
+ pAhciPort->iLUN = i;
+ pAhciPortR3->iLUN = i;
+ ahciR3PortSwReset(pAhciPort, pAhciPortR3);
+ }
+
+ /* 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(pDevIns);
+
+ pThis->f64BitAddr = false;
+ pThis->u32PortsInterrupted = 0;
+ pThis->f8ByteMMIO4BytesWrittenSuccessfully = false;
+ /* Clear the HBA Reset bit */
+ pThis->regHbaCtrl &= ~AHCI_HBA_CTRL_HR;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Reads from a AHCI controller register.
+ *
+ * @returns Strict VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param uReg The register to write.
+ * @param pv Where to store the result.
+ * @param cb Number of bytes read.
+ */
+static VBOXSTRICTRC ahciRegisterRead(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t uReg, void *pv, unsigned cb)
+{
+ VBOXSTRICTRC rc;
+ 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(pDevIns, pThis, 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;
+ rc = VINF_SUCCESS;
+ }
+ }
+ 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 < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts))
+ && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+ {
+ const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+ rc = pPortReg->pfnRead(pDevIns, pThis, &pThis->aPorts[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:
+ ASSERT_GUEST_MSG_FAILED(("%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 Strict VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param offReg The offset of the register to write to.
+ * @param u32Value The value to write.
+ */
+static VBOXSTRICTRC ahciRegisterWrite(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t offReg, uint32_t u32Value)
+{
+ VBOXSTRICTRC 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(pDevIns, pThis, 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 < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts))
+ && iReg < RT_ELEMENTS(g_aPortOpRegs)))
+ {
+ const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg];
+ rc = pPortReg->pfnWrite(pDevIns, pThis, &pThis->aPorts[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;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ Log2(("#%d ahciMMIORead: pvUser=%p:{%.*Rhxs} cb=%d off=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, off));
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = ahciRegisterRead(pDevIns, pThis, off, pv, cb);
+
+ Log2(("#%d ahciMMIORead: return pvUser=%p:{%.*Rhxs} cb=%d off=%RGp rc=%Rrc\n",
+ pDevIns->iInstance, pv, cb, pv, cb, off, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+
+ Assert(cb == 4 || cb == 8); /* Assert IOM flags & sanity */
+ Assert(!(off & (cb - 1))); /* Ditto. */
+
+ /* 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.
+ */
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ if (!pThis->f8ByteMMIO4BytesWrittenSuccessfully)
+ {
+ rc = ahciMMIOWrite(pDevIns, pvUser, off, pv, 4);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ pThis->f8ByteMMIO4BytesWrittenSuccessfully = true;
+ }
+
+ rc = ahciMMIOWrite(pDevIns, pvUser, off + 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)
+ pThis->f8ByteMMIO4BytesWrittenSuccessfully = false;
+
+ return rc;
+ }
+
+ /* Do the access. */
+ Log2(("#%d ahciMMIOWrite: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, off));
+ return ahciRegisterWrite(pDevIns, pThis, off, *(uint32_t const *)pv);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Fake IDE port handler provided to make solaris happy.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, offPort, u32, cb);
+ ASSERT_GUEST_MSG_FAILED(("Should not happen\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Fake IDE port handler provided to make solaris happy.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ /** @todo we should set *pu32 to something. */
+ RT_NOREF(pDevIns, pvUser, offPort, pu32, cb);
+ ASSERT_GUEST_MSG_FAILED(("Should not happen\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * I/O port handler for writes to the index/data register pair.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ahciIdxDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ RT_NOREF(pvUser, cb);
+
+ if (offPort >= 8)
+ {
+ ASSERT_GUEST(cb == 4);
+
+ uint32_t const iReg = (offPort - 8) / 4;
+ if (iReg == 0)
+ {
+ /* Write the index register. */
+ pThis->regIdx = u32;
+ }
+ else
+ {
+ /** @todo range check? */
+ ASSERT_GUEST(iReg == 1);
+ rc = ahciRegisterWrite(pDevIns, pThis, pThis->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 offPort=%#x rc=%Rrc\n",
+ pDevIns->iInstance, &u32, cb, &u32, cb, offPort, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * I/O port handler for reads from the index/data register pair.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ahciIdxDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ RT_NOREF(pvUser);
+
+ if (offPort >= 8)
+ {
+ ASSERT_GUEST(cb == 4);
+
+ uint32_t const iReg = (offPort - 8) / 4;
+ if (iReg == 0)
+ {
+ /* Read the index register. */
+ *pu32 = pThis->regIdx;
+ }
+ else
+ {
+ /** @todo range check? */
+ ASSERT_GUEST(iReg == 1);
+ rc = ahciRegisterRead(pDevIns, pThis, pThis->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_MAX;
+
+ Log2(("#%d ahciIdxDataRead: pu32=%p:{%.*Rhxs} cb=%d offPort=%#x rc=%Rrc\n",
+ pDevIns->iInstance, pu32, cb, pu32, cb, offPort, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/* -=-=-=-=-=- 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)
+{
+ PAHCICC pThisCC = RT_FROM_MEMBER(pInterface, AHCICC, ILeds);
+ if (iLUN < AHCI_MAX_NR_PORTS_IMPL)
+ {
+ PAHCI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PAHCI);
+ *ppLed = &pThis->aPorts[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)
+{
+ PAHCICC pThisCC = RT_FROM_MEMBER(pInterface, AHCICC, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ahciR3PortQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pAhciPortR3->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pAhciPortR3->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pAhciPortR3->IMediaExPort);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) ahciR3PortQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pAhciPortR3->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryScsiInqStrings}
+ */
+static DECLCALLBACK(int) ahciR3PortQueryScsiInqStrings(PPDMIMEDIAPORT pInterface, const char **ppszVendorId,
+ const char **ppszProductId, const char **ppszRevision)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IPort);
+ PAHCI pThis = PDMDEVINS_2_DATA(pAhciPortR3->pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+
+ 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
+ *
+ * @param pAhciPort The port the command FIS was read from (shared bits).
+ * @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
+ *
+ * @param pAhciPort Pointer to the port the command header was read from
+ * (shared bits).
+ * @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.
+ *
+ * @param pDevIns The device instance.
+ * @param pAhciPort Pointer to the port which "receives" the FIS (shared bits).
+ */
+static void ahciPostFirstD2HFisIntoMemory(PPDMDEVINS pDevIns, 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(pDevIns, 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 pDevIns The device instance.
+ * @param pAhciPort The port which "receives" the FIS(shared bits).
+ * @param uFisType The type of the FIS.
+ * @param pCmdFis Pointer to the FIS which is to be posted into memory.
+ */
+static int ahciPostFisIntoMemory(PPDMDEVINS pDevIns, 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));
+ PDMDevHlpPCIPhysWriteMeta(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(PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, 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
+ || pAhciPortR3->pDrvMedia->pfnIsNonRotational(pAhciPortR3->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 (pAhciPortR3->pDrvMedia->pfnIsNonRotational(pAhciPortR3->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(pThis->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(PPDMDEVINS pDevIns, 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(pDevIns, 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The port the device is attached to, shared bits(shared
+ * bits).
+ * @param pAhciReq The state to get the tag number from.
+ */
+static void ahciFinishStorageDeviceReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq)
+{
+ int rc;
+
+ /* Send a status good D2H FIS. */
+ pAhciPort->fResetDevice = false;
+ if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE)
+ ahciPostFirstD2HFisIntoMemory(pDevIns, 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(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+}
+
+/**
+ * Initiates a device reset caused by ATA_DEVICE_RESET (ATAPI only).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The device to reset(shared bits).
+ * @param pAhciReq The task state.
+ */
+static void ahciDeviceReset(PPDMDEVINS pDevIns, PAHCI pThis, 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(pDevIns, pThis, pAhciPort, pAhciReq);
+}
+
+/**
+ * Create a PIO setup FIS and post it into the memory area of the guest.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The port of the SATA controller (shared bits).
+ * @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(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort,
+ size_t cbTransfer, uint8_t *pCmdFis, bool fRead, bool fInterrupt)
+{
+ uint8_t abPioSetupFis[20];
+ bool fAssertIntr = false;
+
+ 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(pDevIns, 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(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+}
+
+/**
+ * Build a D2H FIS and post into the memory area of the guest.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The port of the SATA controller (shared bits).
+ * @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(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t uTag, uint8_t *pCmdFis, bool fInterrupt)
+{
+ uint8_t d2hFis[20];
+ bool fAssertIntr = false;
+
+ 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(pDevIns, 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(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED);
+ AssertRC(rc);
+ }
+ }
+}
+
+/**
+ * Build a SDB Fis and post it into the memory area of the guest.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The port for which the SDB Fis is send, shared bits.
+ * @param pAhciPortR3 The port for which the SDB Fis is send, ring-3 bits.
+ * @param uFinishedTasks Bitmask of finished tasks.
+ * @param fInterrupt If an interrupt should be asserted.
+ */
+static void ahciSendSDBFis(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3,
+ uint32_t uFinishedTasks, bool fInterrupt)
+{
+ uint32_t sdbFis[2];
+ bool fAssertIntr = false;
+ PAHCIREQ pTaskErr = ASMAtomicReadPtrT(&pAhciPortR3->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(pDevIns, 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(pDevIns, pThis, 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 FNAHCIR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) ahciR3CopyBufferFromGuestWorker(PPDMDEVINS pDevIns, 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);
+ Log5Func(("%RGp LB %#zx\n", GCPhys, cbSeg));
+ PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvSeg, cbSeg);
+ Log7Func(("%.*Rhxd\n", cbSeg, pvSeg));
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc FNAHCIR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) ahciR3CopyBufferToGuestWorker(PPDMDEVINS pDevIns, 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);
+ Log5Func(("%RGp LB %#zx\n", GCPhys, cbSeg));
+ Log6Func(("%.*Rhxd\n", cbSeg, pvSeg));
+ PDMDevHlpPCIPhysWriteUser(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 pDevIns The 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(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq,
+ PFNAHCIR3MEMCOPYCALLBACK 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);
+
+ PDMDevHlpPCIPhysReadMeta(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(pDevIns, 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 pDevIns The 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(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ return ahciR3PrdtlWalk(pDevIns, pAhciReq, ahciR3CopyBufferToGuestWorker, pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copies the S/G buffer into a data buffer.
+ *
+ * @returns Amount of bytes copied from the PRDTL.
+ * @param pDevIns The 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(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ return ahciR3PrdtlWalk(pDevIns, pAhciReq, ahciR3CopyBufferFromGuestWorker, pSgBuf, cbSkip, cbCopy);
+}
+
+/**
+ * Copy a simple memory buffer to the guest memory buffer.
+ *
+ * @returns Amount of bytes copied from the PRDTL.
+ * @param pDevIns The 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(PPDMDEVINS pDevIns, 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(pDevIns, pAhciReq, &SgBuf, cbSkip, cbSrc);
+}
+
+/**
+ * Calculates the size of the guest buffer described by the PRDT.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pAhciReq AHCI request structure.
+ * @param pcbPrdt Where to store the size of the guest buffer.
+ */
+static int ahciR3PrdtQuerySize(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, size_t *pcbPrdt)
+{
+ RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl;
+ unsigned cPrdtlEntries = pAhciReq->cPrdtlEntries;
+ size_t cbPrdt = 0;
+
+ do
+ {
+ SGLEntry aPrdtlEntries[32];
+ uint32_t const cPrdtlEntriesRead = RT_MIN(cPrdtlEntries, RT_ELEMENTS(aPrdtlEntries));
+
+ PDMDevHlpPCIPhysReadMeta(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 pAhciPortR3 The AHCI port, ring-3 bits.
+ */
+static bool ahciR3CancelActiveTasks(PAHCIPORTR3 pAhciPortR3)
+{
+ if (pAhciPortR3->pDrvMediaEx)
+ {
+ int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqCancelAll(pAhciPortR3->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 pDevIns The device instance.
+ * @param pAhciPort AHCI port state, shared bits.
+ * @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(PPDMDEVINS pDevIns, 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;
+ 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;
+ PDMDevHlpPCIPhysReadMeta(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. */
+ PDMDevHlpPCIPhysReadMeta(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 pAhciPortR3 The AHCI port, ring-3 bits.
+ * @param uTag The tag to assign.
+ */
+static PAHCIREQ ahciR3ReqAlloc(PAHCIPORTR3 pAhciPortR3, uint32_t uTag)
+{
+ PAHCIREQ pAhciReq = NULL;
+ PDMMEDIAEXIOREQ hIoReq = NULL;
+
+ int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqAlloc(pAhciPortR3->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.
+ *
+ * @param pAhciPortR3 The AHCI port, ring-3 bits.
+ * @param pAhciReq The request to free.
+ */
+static void ahciR3ReqFree(PAHCIPORTR3 pAhciPortR3, PAHCIREQ pAhciReq)
+{
+ if ( pAhciReq
+ && !(pAhciReq->fFlags & AHCI_REQ_IS_ON_STACK))
+ {
+ int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqFree(pAhciPortR3->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 pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pThisCC The ring-3 AHCI state.
+ * @param pAhciPort Pointer to the port where to request completed, shared bits.
+ * @param pAhciPortR3 Pointer to the port where to request completed, ring-3 bits.
+ * @param pAhciReq Pointer to the task which finished.
+ * @param rcReq IPRT status code of the completed request.
+ */
+static bool ahciR3TransferComplete(PPDMDEVINS pDevIns, PAHCI pThis, PAHCICC pThisCC,
+ PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, 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(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",
+ pDevIns->iInstance, pAhciPort->iLUN, rcReq));
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("AHCI#%uP%u: Trim returned rc=%Rrc\n",
+ pDevIns->iInstance, pAhciPort->iLUN, rcReq));
+ else
+ LogRel(("AHCI#%uP%u: %s at offset %llu (%zu bytes left) returned rc=%Rrc\n",
+ 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(&pAhciPortR3->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 = pAhciPortR3->pDrvMediaEx->pfnIoReqQueryXferSize(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, &cbXfer);
+ AssertRC(rc);
+ u32PRDBC = (uint32_t)RT_MIN(cbXfer, pAhciReq->cbTransfer);
+ }
+ else
+ u32PRDBC = (uint32_t)pAhciReq->cbTransfer;
+
+ PDMDevHlpPCIPhysWriteMeta(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(pDevIns, pThis, 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(pAhciPortR3, pAhciReq);
+
+ /* Post a PIO setup FIS first if this is a PIO command which transfers data. */
+ if (fFlags & AHCI_REQ_PIO_DATA)
+ ahciSendPioSetupFis(pDevIns, pThis, pAhciPort, cbTransfer, &cmdFis[0], fRead, false /* fInterrupt */);
+
+ if (fFlags & AHCI_REQ_CLEAR_SACT)
+ {
+ if (RT_SUCCESS(rcReq) && !ASMAtomicReadPtrT(&pAhciPortR3->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(pDevIns, pThis, pAhciPort, pAhciPortR3, 0, true);
+ }
+ else
+ ahciSendD2HFis(pDevIns, pThis, 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",
+ pDevIns->iInstance, pAhciPort->iLUN, rcReq));
+ else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ LogRel(("AHCI#%uP%u: Canceled trim returned rc=%Rrc\n",
+ pDevIns->iInstance,pAhciPort->iLUN, rcReq));
+ else
+ LogRel(("AHCI#%uP%u: Canceled %s at offset %llu (%zu bytes left) returned rc=%Rrc\n",
+ pDevIns->iInstance, pAhciPort->iLUN,
+ pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "read"
+ : "write",
+ pAhciReq->uOffset,
+ pAhciReq->cbTransfer, rcReq));
+ }
+
+ ahciR3ReqFree(pAhciPortR3, 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 && pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+
+ 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)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ int rc = VINF_SUCCESS;
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq);
+
+ ahciR3CopySgBufToPrdtl(pAhciPortR3->pDevIns, 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)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ int rc = VINF_SUCCESS;
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq);
+
+ ahciR3CopySgBufFromPrdtl(pAhciPortR3->pDevIns, 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)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ int rc = VERR_NOT_SUPPORTED;
+ RT_NOREF(hIoReq);
+
+ /* Only allow single 4KB page aligned buffers at the moment. */
+ if ( pIoReq->cPrdtlEntries == 1
+ && pIoReq->cbTransfer == _4K)
+ {
+ RTGCPHYS GCPhysPrdt = pIoReq->GCPhysPrdtl;
+ SGLEntry PrdtEntry;
+
+ PDMDevHlpPCIPhysReadMeta(pDevIns, 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 = PDMDevHlpPCIPhysGCPhys2CCPtr(pDevIns, NULL /* pPciDev */, 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)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq);
+
+ return ahciTrimRangesCreate(pDevIns, pAhciPort, pIoReq, idxRangeStart, paRanges, cRanges, pcRanges);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) ahciR3IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+ PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq);
+
+ ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pIoReq, rcReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) ahciR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+ RT_NOREF(hIoReq, pvIoReqAlloc);
+
+ 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 && pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ 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)
+{
+ PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort);
+ PPDMDEVINS pDevIns = pAhciPortR3->pDevIns;
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+
+ if (pThisCC->pMediaNotify)
+ {
+ int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->pMediaNotify, pAhciPort->iLUN);
+ AssertRC(rc);
+ }
+}
+
+/**
+ * Process an non read/write ATA command.
+ *
+ * @returns The direction of the data transfer
+ * @param pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The AHCI port of the request, shared bits.
+ * @param pAhciPortR3 The AHCI port of the request, ring-3 bits.
+ * @param pAhciReq The AHCI request state.
+ * @param pCmdFis Pointer to the command FIS.
+ */
+static PDMMEDIAEXIOREQTYPE ahciProcessCmd(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3,
+ 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 (pAhciPortR3->pDrvMedia && !pAhciPort->fATAPI)
+ {
+ uint16_t u16Temp[256];
+
+ /* Fill the buffer. */
+ ahciIdentifySS(pThis, pAhciPort, pAhciPortR3, u16Temp);
+
+ /* Copy the buffer. */
+ size_t cbCopied = ahciR3CopyBufferToPrdtl(pDevIns, 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(pDevIns, pThis, 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(pDevIns, 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(&pAhciPortR3->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 = ahciR3CancelActiveTasks(pAhciPortR3);
+ Assert(fAbortedAll); NOREF(fAbortedAll);
+ ahciSendSDBFis(pDevIns, pThis, pAhciPort, pAhciPortR3, UINT32_C(0xffffffff), true);
+
+ break;
+ }
+ }
+
+ /* Copy the buffer. */
+ size_t cbCopied = ahciR3CopyBufferToPrdtl(pDevIns, 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 pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The AHCI port of the request, shared bits.
+ * @param pAhciReq The state of the actual task.
+ */
+static bool ahciPortTaskGetCommandFis(PPDMDEVINS pDevIns, PAHCI pThis, 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: PDMDevHlpPCIPhysReadMeta GCPhysAddrCmdLst=%RGp cbCmdHdr=%u\n", __FUNCTION__,
+ pAhciReq->GCPhysCmdHdrAddr, sizeof(CmdHdr)));
+ PDMDevHlpPCIPhysReadMeta(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: PDMDevHlpPCIPhysReadMeta GCPhysAddrCmdTbl=%RGp cbCmdFis=%u\n", __FUNCTION__, GCPhysAddrCmdTbl, AHCI_CMDFIS_TYPE_H2D_SIZE));
+ PDMDevHlpPCIPhysReadMeta(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;
+ PDMDevHlpPCIPhysReadMeta(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(pDevIns, pThis, 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));
+ PDMDevHlpPCIPhysReadMeta(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 pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pThisCC The ring-3 AHCI state.
+ * @param pAhciPort The port the request is for, shared bits.
+ * @param pAhciPortR3 The port the request is for, ring-3 bits.
+ * @param pAhciReq The request to submit.
+ * @param enmType The request type.
+ */
+static bool ahciR3ReqSubmit(PPDMDEVINS pDevIns, PAHCI pThis, PAHCICC pThisCC, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3,
+ 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 = pAhciPortR3->pDrvMediaEx->pfnIoReqFlush(pAhciPortR3->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 = pAhciPortR3->pDrvMediaEx->pfnIoReqDiscard(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq,
+ cRangesMax);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_READ)
+ {
+ pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1;
+ rc = pAhciPortR3->pDrvMediaEx->pfnIoReqRead(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq,
+ pAhciReq->uOffset, pAhciReq->cbTransfer);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1;
+ rc = pAhciPortR3->pDrvMediaEx->pfnIoReqWrite(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq,
+ pAhciReq->uOffset, pAhciReq->cbTransfer);
+ }
+ else if (enmType == PDMMEDIAEXIOREQTYPE_SCSI)
+ {
+ size_t cbBuf = 0;
+
+ if (pAhciReq->cPrdtlEntries)
+ rc = ahciR3PrdtQuerySize(pDevIns, 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 = pAhciPortR3->pDrvMediaEx->pfnIoReqSendScsiCmd(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq,
+ 0, &pAhciReq->aATAPICmd[0], ATAPI_PACKET_SIZE,
+ PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN, NULL, cbBuf,
+ &pAhciPort->abATAPISense[0], sizeof(pAhciPort->abATAPISense), NULL,
+ &pAhciReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ }
+ }
+
+ if (rc == VINF_SUCCESS)
+ fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pAhciReq, VINF_SUCCESS);
+ else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, 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 pDevIns The device instance.
+ * @param pThis The shared AHCI state.
+ * @param pAhciPort The AHCI port the request is for, shared bits.
+ * @param pAhciReq Request structure to copy the command to.
+ */
+static bool ahciR3CmdPrepare(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq)
+{
+ /* Set current command slot */
+ ASMAtomicWriteU32(&pAhciPort->u32CurrentCommandSlot, pAhciReq->uTag);
+
+ bool fContinue = ahciPortTaskGetCommandFis(pDevIns, pThis, 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",
+ 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(pDevIns, pThis, pAhciPort, pAhciReq->uTag, pAhciReq->cmdFis, true);
+ }
+ else if (pAhciPort->fResetDevice) /* The bit is not set and we are in a reset state. */
+ ahciFinishStorageDeviceReset(pDevIns, pThis, 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;
+}
+
+/**
+ * @callback_method_impl{FNPDMTHREADDEV, The async IO thread for one port.}
+ */
+static DECLCALLBACK(int) ahciAsyncIOLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PAHCIPORTR3 pAhciPortR3 = (PAHCIPORTR3)pThread->pvUser;
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+ 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 = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, 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(&pThis->cThreadsActive);
+
+ /* Check whether the thread should be suspended. */
+ if (pThisCC->fSignalIdle)
+ {
+ if (!ASMAtomicDecU32(&pThis->cThreadsActive))
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ continue;
+ }
+
+ /*
+ * Check whether the global host controller bit is set and go to sleep immediately again
+ * if it is set.
+ */
+ u32RegHbaCtrl = ASMAtomicReadU32(&pThis->regHbaCtrl);
+ if ( u32RegHbaCtrl & AHCI_HBA_CTRL_HR
+ && !ASMAtomicDecU32(&pThis->cThreadsActive))
+ {
+ ahciR3HBAReset(pDevIns, pThis, pThisCC);
+ if (pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ 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(pAhciPortR3, idx);
+ if (RT_LIKELY(pAhciReq))
+ {
+ pAhciReq->uTag = idx;
+ pAhciReq->fFlags = 0;
+
+ bool fContinue = ahciR3CmdPrepare(pDevIns, pThis, pAhciPort, pAhciReq);
+ if (fContinue)
+ {
+ PDMMEDIAEXIOREQTYPE enmType = ahciProcessCmd(pDevIns, pThis, pAhciPort, pAhciPortR3,
+ pAhciReq, pAhciReq->cmdFis);
+ pAhciReq->enmType = enmType;
+
+ if (enmType != PDMMEDIAEXIOREQTYPE_INVALID)
+ fReqCanceled = ahciR3ReqSubmit(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pAhciReq, enmType);
+ else
+ fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3,
+ pAhciReq, VINF_SUCCESS);
+ } /* Command */
+ else
+ ahciR3ReqFree(pAhciPortR3, 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(pDevIns, pThis, pAhciPort, &Req);
+ if (fContinue)
+ fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, &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(pDevIns, pThis, pAhciPort, pAhciPortR3);
+
+ /*
+ * Check whether a host controller reset is pending and execute the reset
+ * if this is the last active thread.
+ */
+ u32RegHbaCtrl = ASMAtomicReadU32(&pThis->regHbaCtrl);
+ uint32_t cThreadsActive = ASMAtomicDecU32(&pThis->cThreadsActive);
+ if ( (u32RegHbaCtrl & AHCI_HBA_CTRL_HR)
+ && !cThreadsActive)
+ ahciR3HBAReset(pDevIns, pThis, pThisCC);
+
+ if (!cThreadsActive && pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ } /* While running */
+
+ ahciLog(("%s: Port %d async IO thread exiting\n", __FUNCTION__, pAhciPort->iLUN));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNPDMTHREADWAKEUPDEV}
+ */
+static DECLCALLBACK(int) ahciAsyncIOLoopWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PAHCIPORTR3 pAhciPortR3 = (PAHCIPORTR3)pThread->pvUser;
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN);
+ return PDMDevHlpSUPSemEventSignal(pDevIns, 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 = PDMDEVINS_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,
+ PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio),
+ pThis->cPortsImpl,
+ pDevIns->fRCEnabled,
+ pDevIns->fR0Enabled);
+
+ /*
+ * 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.
+ */
+ uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts));
+ for (unsigned i = 0; i < cPortsImpl; i++)
+ {
+ PAHCIPORT pThisPort = &pThis->aPorts[i];
+
+ pHlp->pfnPrintf(pHlp, "Port %d: device-attached=%RTbool\n", pThisPort->iLUN, pThisPort->fPresent);
+ 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 = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+
+ if (pThis->cThreadsActive)
+ return false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aPorts); i++)
+ {
+ PAHCIPORT pThisPort = &pThis->aPorts[i];
+ if (pThisPort->fPresent)
+ {
+ 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)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(uPass);
+
+ /* config. */
+ pHlp->pfnSSMPutU32(pSSM, pThis->cPortsImpl);
+ for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fPresent);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fHotpluggable);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[i].szSerialNumber);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[i].szFirmwareRevision);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[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 = pHlp->pfnCFGMQueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i);
+ AssertRCReturn(rc, rc);
+ pHlp->pfnSSMPutU32(pSSM, iPort);
+ }
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ahciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint32_t i;
+ int rc;
+
+ Assert(!pThis->f8ByteMMIO4BytesWrittenSuccessfully);
+
+ /* The config */
+ rc = ahciR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+ AssertRCReturn(rc, rc);
+
+ /* The main device structure. */
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCap);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCtrl);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaIs);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaPi);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaVs);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCccCtl);
+ pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCccPorts);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uCccPortNr);
+ pHlp->pfnSSMPutU64(pSSM, pThis->uCccTimeout);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uCccNr);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uCccCurrentNr);
+ pHlp->pfnSSMPutU32(pSSM, pThis->u32PortsInterrupted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fReset);
+ pHlp->pfnSSMPutBool(pSSM, pThis->f64BitAddr);
+ pHlp->pfnSSMPutBool(pSSM, pDevIns->fR0Enabled);
+ pHlp->pfnSSMPutBool(pSSM, pDevIns->fRCEnabled);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fLegacyPortResetMethod);
+
+ /* Now every port. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ Assert(pThis->aPorts[i].cTasksActive == 0);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCLB);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCLBU);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regFB);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regFBU);
+ pHlp->pfnSSMPutGCPhys(pSSM, pThis->aPorts[i].GCPhysAddrClb);
+ pHlp->pfnSSMPutGCPhys(pSSM, pThis->aPorts[i].GCPhysAddrFb);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regIS);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regIE);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCMD);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regTFD);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSIG);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSSTS);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSCTL);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSERR);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSACT);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCI);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cCylinders);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cHeads);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cSectors);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aPorts[i].cTotalSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].cMultSectors);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aPorts[i].uATATransferMode);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fResetDevice);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fPoweredOn);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fSpunUp);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32TasksFinished);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32QueuedTasksFinished);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32CurrentCommandSlot);
+
+ /* ATAPI saved state. */
+ pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fATAPI);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aPorts[i].abATAPISense[0], sizeof(pThis->aPorts[i].abATAPISense));
+ }
+
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * Loads a saved legacy ATA emulated device state.
+ *
+ * @returns VBox status code.
+ * @param pHlp The device helper call table.
+ * @param pSSM The handle to the saved state.
+ */
+static int ahciR3LoadLegacyEmulationState(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM)
+{
+ int rc;
+ uint32_t u32Version;
+ uint32_t u32;
+ uint32_t u32IOBuffer;
+
+ /* Test for correct version. */
+ rc = pHlp->pfnSSMGetU32(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;
+ }
+
+ pHlp->pfnSSMSkip(pSSM, 19 + 5 * sizeof(bool) + 8 /* sizeof(BMDMAState) */);
+
+ for (uint32_t j = 0; j < 2; j++)
+ {
+ pHlp->pfnSSMSkip(pSSM, 88 + 5 * sizeof(bool) );
+
+ if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ pHlp->pfnSSMSkip(pSSM, 64);
+ else
+ pHlp->pfnSSMSkip(pSSM, 2);
+ /** @todo triple-check this hack after passthrough is working */
+ pHlp->pfnSSMSkip(pSSM, 1);
+
+ if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ pHlp->pfnSSMSkip(pSSM, 4);
+
+ pHlp->pfnSSMSkip(pSSM, sizeof(PDMLED));
+ pHlp->pfnSSMGetU32(pSSM, &u32IOBuffer);
+ if (u32IOBuffer)
+ pHlp->pfnSSMSkip(pSSM, u32IOBuffer);
+ }
+
+ rc = pHlp->pfnSSMGetU32(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 = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ 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
+ && pHlp->pfnSSMHandleRevision(pSSM) >= 79045
+ && pHlp->pfnSSMHandleRevision(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 = pHlp->pfnSSMGetU32(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 pHlp->pfnSSMSetCfgError(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 = pHlp->pfnSSMGetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != pThis->aPorts[i].fPresent)
+ return pHlp->pfnSSMSetCfgError(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 = pHlp->pfnSSMGetBool(pSSM, &fHotpluggable);
+ AssertRCReturn(rc, rc);
+ if (fHotpluggable != pThis->aPorts[i].fHotpluggable)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("AHCI: Port %u config mismatch: Hotplug flag - saved=%RTbool config=%RTbool\n"),
+ i, fHotpluggable, pThis->aPorts[i].fHotpluggable);
+ }
+ else
+ Assert(pThis->aPorts[i].fHotpluggable);
+
+ char szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szSerialNumber, sizeof(szSerialNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szSerialNumber, pThis->aPorts[i].szSerialNumber))
+ LogRel(("AHCI: Port %u config mismatch: Serial number - saved='%s' config='%s'\n",
+ i, szSerialNumber, pThis->aPorts[i].szSerialNumber));
+
+ char szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szFirmwareRevision, sizeof(szFirmwareRevision));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szFirmwareRevision, pThis->aPorts[i].szFirmwareRevision))
+ LogRel(("AHCI: Port %u config mismatch: Firmware revision - saved='%s' config='%s'\n",
+ i, szFirmwareRevision, pThis->aPorts[i].szFirmwareRevision));
+
+ char szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szModelNumber, sizeof(szModelNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szModelNumber, pThis->aPorts[i].szModelNumber))
+ LogRel(("AHCI: Port %u config mismatch: Model number - saved='%s' config='%s'\n",
+ i, szModelNumber, pThis->aPorts[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 = pHlp->pfnCFGMQueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i);
+ AssertRCReturn(rc, rc);
+
+ uint32_t iPortSaved;
+ rc = pHlp->pfnSSMGetU32(pSSM, &iPortSaved);
+ AssertRCReturn(rc, rc);
+
+ if (iPortSaved != iPort)
+ return pHlp->pfnSSMSetCfgError(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. */
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCap);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCtrl);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaIs);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaPi);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaVs);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCccCtl);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCccPorts);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uCccPortNr);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->uCccTimeout);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->uCccNr);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->uCccCurrentNr);
+
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->u32PortsInterrupted);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fReset);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->f64BitAddr);
+ bool fIgn;
+ pHlp->pfnSSMGetBool(pSSM, &fIgn); /* Was fR0Enabled, which should never have been saved! */
+ pHlp->pfnSSMGetBool(pSSM, &fIgn); /* Was fGCEnabled, which should never have been saved! */
+ if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES)
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fLegacyPortResetMethod);
+
+ /* Now every port. */
+ for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[i];
+
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCLB);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCLBU);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regFB);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regFBU);
+ pHlp->pfnSSMGetGCPhysV(pSSM, &pThis->aPorts[i].GCPhysAddrClb);
+ pHlp->pfnSSMGetGCPhysV(pSSM, &pThis->aPorts[i].GCPhysAddrFb);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regIS);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regIE);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCMD);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regTFD);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSIG);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSSTS);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSCTL);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSERR);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regSACT);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regCI);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cCylinders);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cHeads);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cSectors);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aPorts[i].cTotalSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].cMultSectors);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aPorts[i].uATATransferMode);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fResetDevice);
+
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_VBOX_30)
+ pHlp->pfnSSMSkip(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. */
+ pHlp->pfnSSMSkip(pSSM, 2*sizeof(uint8_t));
+ }
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fPoweredOn);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fSpunUp);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32TasksFinished);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32QueuedTasksFinished);
+
+ if (uVersion >= AHCI_SAVED_STATE_VERSION_IDE_EMULATION)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32CurrentCommandSlot);
+
+ if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_ATAPI)
+ {
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fATAPI);
+ pHlp->pfnSSMGetMem(pSSM, pThis->aPorts[i].abATAPISense, sizeof(pThis->aPorts[i].abATAPISense));
+ if (uVersion <= AHCI_SAVED_STATE_VERSION_PRE_ATAPI_REMOVE)
+ {
+ pHlp->pfnSSMSkip(pSSM, 1); /* cNotifiedMediaChange. */
+ pHlp->pfnSSMSkip(pSSM, 4); /* MediaEventStatus */
+ }
+ }
+ else if (pThis->aPorts[i].fATAPI)
+ return pHlp->pfnSSMSetCfgError(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(pHlp, pSSM);
+ if(RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ rc = pHlp->pfnSSMGetU32(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 -=-=-=-=- */
+
+/**
+ * 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, shared bits.
+ * @param pAhciPortR3 The port for which the device is to be configured, ring-3 bits.
+ */
+static int ahciR3ConfigureLUN(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3)
+{
+ /* Query the media interface. */
+ pAhciPortR3->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pAhciPortR3->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(RT_VALID_PTR(pAhciPortR3->pDrvMedia),
+ ("AHCI configuration error: LUN#%d misses the basic media interface!\n", pAhciPort->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pAhciPortR3->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pAhciPortR3->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(RT_VALID_PTR(pAhciPortR3->pDrvMediaEx),
+ ("AHCI configuration error: LUN#%d misses the extended media interface!\n", pAhciPort->iLUN),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Validate type.
+ */
+ PDMMEDIATYPE enmType = pAhciPortR3->pDrvMedia->pfnGetType(pAhciPortR3->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 = pAhciPortR3->pDrvMediaEx->pfnIoReqAllocSizeSet(pAhciPortR3->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 = pAhciPortR3->pDrvMediaEx->pfnQueryFeatures(pAhciPortR3->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->fPresent = 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 = pAhciPortR3->pDrvMedia->pfnGetSectorSize(pAhciPortR3->pDrvMedia);
+ pAhciPort->cTotalSectors = pAhciPortR3->pDrvMedia->pfnGetSize(pAhciPortR3->pDrvMedia) / pAhciPort->cbSector;
+ rc = pAhciPortR3->pDrvMedia->pfnBiosGetPCHSGeometry(pAhciPortR3->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. */
+ pAhciPortR3->pDrvMedia->pfnBiosSetPCHSGeometry(pAhciPortR3->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;
+
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for ahciR3Suspend and ahciR3PowerOff.
+ */
+static void ahciR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, true);
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncSuspendOrPowerOffDone);
+ else
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThisCC->aPorts); i++)
+ {
+ PAHCIPORTR3 pThisPort = &pThisCC->aPorts[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 pThis = PDMDEVINS_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(pThis->aPorts); i++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[i];
+
+ if (pAhciPort->u32TasksRedo)
+ {
+ pAhciPort->u32TasksNew |= pAhciPort->u32TasksRedo;
+ pAhciPort->u32TasksRedo = 0;
+
+ Assert(pAhciPort->fRedo);
+ pAhciPort->fRedo = false;
+
+ /* Notify the async IO thread. */
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess);
+ AssertRC(rc);
+ }
+ }
+
+ 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, shared bits.
+ * @param pAhciPortR3 The attached device, ring-3 bits.
+ * @param pszName Name of the port to get the CFGM node.
+ */
+static int ahciR3VpdInit(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, const char *pszName)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /* Generate a default serial number. */
+ char szSerial[AHCI_SERIAL_NUMBER_LENGTH+1];
+ RTUUID Uuid;
+
+ int rc = VINF_SUCCESS;
+ if (pAhciPortR3->pDrvMedia)
+ rc = pAhciPortR3->pDrvMedia->pfnGetUuid(pAhciPortR3->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 = pHlp->pfnCFGMGetChild(pDevIns->pCfg, pszName);
+ rc = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryU8Def(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ int rc = VINF_SUCCESS;
+
+ Log(("%s:\n", __FUNCTION__));
+
+ AssertMsgReturnVoid(iLUN < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts)), ("iLUN=%u", iLUN));
+ PAHCIPORT pAhciPort = &pThis->aPorts[iLUN];
+ PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[iLUN];
+ AssertMsgReturnVoid( pAhciPort->fHotpluggable
+ || (fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG),
+ ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN));
+
+
+ if (pAhciPortR3->pAsyncIOThread)
+ {
+ int rcThread;
+ /* Destroy the thread. */
+ rc = PDMDevHlpThreadDestroy(pDevIns, pAhciPortR3->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));
+
+ pAhciPortR3->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 | AHCI_PORT_IE_PCE | AHCI_PORT_IE_PRCE))
+ ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED);
+ }
+
+ /*
+ * Zero some important members.
+ */
+ pAhciPortR3->pDrvBase = NULL;
+ pAhciPortR3->pDrvMedia = NULL;
+ pAhciPortR3->pDrvMediaEx = NULL;
+ pAhciPort->fPresent = false;
+}
+
+/**
+ * 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 = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ int rc;
+
+ Log(("%s:\n", __FUNCTION__));
+
+ /* the usual paranoia */
+ AssertMsgReturn(iLUN < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts)), ("iLUN=%u", iLUN), VERR_PDM_LUN_NOT_FOUND);
+ PAHCIPORT pAhciPort = &pThis->aPorts[iLUN];
+ PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[iLUN];
+ AssertRelease(!pAhciPortR3->pDrvBase);
+ AssertRelease(!pAhciPortR3->pDrvMedia);
+ AssertRelease(!pAhciPortR3->pDrvMediaEx);
+ Assert(pAhciPort->iLUN == iLUN);
+ Assert(pAhciPortR3->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, &pAhciPortR3->IBase, &pAhciPortR3->pDrvBase, pAhciPortR3->szDesc);
+ if (RT_SUCCESS(rc))
+ rc = ahciR3ConfigureLUN(pDevIns, pAhciPort, pAhciPortR3);
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pAhciPort->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pAhciPortR3->pDrvBase = NULL;
+ pAhciPortR3->pDrvMedia = NULL;
+ pAhciPortR3->pDrvMediaEx = NULL;
+ pAhciPort->fPresent = false;
+ }
+ else
+ {
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &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, &pAhciPortR3->pAsyncIOThread, pAhciPortR3, ahciAsyncIOLoop,
+ ahciAsyncIOLoopWakeUp, 0, RTTHREADTYPE_IO, pAhciPortR3->szDesc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Init vendor product data.
+ */
+ if (RT_SUCCESS(rc))
+ rc = ahciR3VpdInit(pDevIns, pAhciPort, pAhciPortR3, pAhciPortR3->szDesc);
+
+ /* 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(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED);
+ }
+
+ }
+
+ return rc;
+}
+
+/**
+ * Common reset worker.
+ *
+ * @param pDevIns The device instance data.
+ */
+static int ahciR3ResetCommon(PPDMDEVINS pDevIns)
+{
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ ahciR3HBAReset(pDevIns, pThis, pThisCC);
+
+ /* Hardware reset for the ports. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aPorts); i++)
+ ahciPortHwReset(&pThis->aPorts[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)
+{
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+
+ ahciR3ResetCommon(pDevIns);
+ return true;
+}
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ahciR3Reset(PPDMDEVINS pDevIns)
+{
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, true);
+ if (!ahciR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThisCC->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)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ int rc = VINF_SUCCESS;
+
+ /*
+ * 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 (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->lock))
+ {
+ PDMDevHlpTimerDestroy(pDevIns, pThis->hHbaCccTimer);
+ pThis->hHbaCccTimer = NIL_TMTIMERHANDLE;
+
+ Log(("%s: Destruct every port\n", __FUNCTION__));
+ uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts));
+ for (unsigned iActPort = 0; iActPort < cPortsImpl; iActPort++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[iActPort];
+
+ if (pAhciPort->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pAhciPort->hEvtProcess);
+ pAhciPort->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+ }
+
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->lock);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ahciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+ PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ PPDMIBASE pBase;
+ int rc;
+ unsigned i;
+ uint32_t cbTotalBufferSize = 0; /** @todo r=bird: cbTotalBufferSize isn't ever set. */
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+ /*
+ * Initialize the instance data (everything touched by the destructor need
+ * to be initialized here!).
+ */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, 0x8086); /* Intel */
+ PDMPciDevSetDeviceId(pPciDev, 0x2829); /* ICH-8M */
+ PDMPciDevSetCommand(pPciDev, 0x0000);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+#else
+ PDMPciDevSetCapabilityList(pPciDev, 0x70);
+#endif
+ PDMPciDevSetRevisionId(pPciDev, 0x02);
+ PDMPciDevSetClassProg(pPciDev, 0x01);
+ PDMPciDevSetClassSub(pPciDev, 0x06);
+ PDMPciDevSetClassBase(pPciDev, 0x01);
+ PDMPciDevSetBaseAddress(pPciDev, 5, false, false, false, 0x00000000);
+
+ PDMPciDevSetInterruptLine(pPciDev, 0x00);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+
+ PDMPciDevSetByte(pPciDev, 0x70, VBOX_PCI_CAP_ID_PM); /* Capability ID: PCI Power Management Interface */
+ PDMPciDevSetByte(pPciDev, 0x71, 0xa8); /* next */
+ PDMPciDevSetByte(pPciDev, 0x72, 0x03); /* version ? */
+
+ PDMPciDevSetByte(pPciDev, 0x90, 0x40); /* AHCI mode. */
+ PDMPciDevSetByte(pPciDev, 0x92, 0x3f);
+ PDMPciDevSetByte(pPciDev, 0x94, 0x80);
+ PDMPciDevSetByte(pPciDev, 0x95, 0x01);
+ PDMPciDevSetByte(pPciDev, 0x97, 0x78);
+
+ PDMPciDevSetByte(pPciDev, 0xa8, 0x12); /* SATACR capability */
+ PDMPciDevSetByte(pPciDev, 0xa9, 0x00); /* next */
+ PDMPciDevSetWord(pPciDev, 0xaa, 0x0010); /* Revision */
+ PDMPciDevSetDWord(pPciDev, 0xac, 0x00000028); /* SATA Capability Register 1 */
+
+ pThis->cThreadsActive = 0;
+
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->IBase.pfnQueryInterface = ahciR3Status_QueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = ahciR3Status_QueryStatusLed;
+
+ /* Initialize port members. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[i];
+ PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i];
+ pAhciPortR3->pDevIns = pDevIns;
+ pAhciPort->iLUN = i;
+ pAhciPortR3->iLUN = i;
+ pAhciPort->Led.u32Magic = PDMLED_MAGIC;
+ pAhciPortR3->pDrvBase = NULL;
+ pAhciPortR3->pAsyncIOThread = NULL;
+ pAhciPort->hEvtProcess = NIL_SUPSEMEVENT;
+ pAhciPort->fHotpluggable = true;
+ }
+
+ /*
+ * Init locks, using explicit locking where necessary.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, 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;
+ }
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns,
+ "PrimaryMaster|PrimarySlave|SecondaryMaster"
+ "|SecondarySlave|PortCount|Bootable|CmdSlotsAvail|TigerHack",
+ "Port*");
+
+ rc = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryBoolDef(pCfg, "TigerHack", &fTigerHack, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read TigerHack as boolean"));
+
+
+ /*
+ * Register the PCI device, it's I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ 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(pPciDev, 0x70);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ /*
+ * Solaris 10 U5 fails to map the AHCI register space when the sets (0..3)
+ * 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 = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, 8 /*cPorts*/,
+ ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/,
+ "AHCI Fake #0", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake0);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region")));
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 1 /*iPciRegion*/, 1 /*cPorts*/,
+ ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/,
+ "AHCI Fake #1", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake1);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region")));
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 2 /*iPciRegion*/, 8 /*cPorts*/,
+ ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/,
+ "AHCI Fake #2", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake2);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region")));
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 3 /*iPciRegion*/, 1 /*cPorts*/,
+ ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/,
+ "AHCI Fake #3", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake3);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region")));
+
+ /*
+ * The non-fake PCI I/O regions:
+ * Note! The 4352 byte MMIO region will be rounded up to GUEST_PAGE_SIZE.
+ */
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 4 /*iPciRegion*/, 0x10 /*cPorts*/,
+ ahciIdxDataWrite, ahciIdxDataRead, NULL /*pvUser*/,
+ "AHCI IDX/DATA", NULL /*paExtDescs*/, &pThis->hIoPortIdxData);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region for BMDMA")));
+
+
+ /** @todo change this to IOMMMIO_FLAGS_WRITE_ONLY_DWORD once EM/IOM starts
+ * handling 2nd DWORD failures on split accesses correctly. */
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 5 /*iPciRegion*/, 4352 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM,
+ ahciMMIOWrite, ahciMMIORead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_QWORD_READ_MISSING,
+ "AHCI", &pThis->hMmio);
+ AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI memory region for registers")));
+
+ /*
+ * Create the timer for command completion coalescing feature.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ahciCccTimer, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "AHCI CCC", &pThis->hHbaCccTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize ports.
+ */
+
+ /* Initialize static members on every port. */
+ for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
+ ahciPortHwReset(&pThis->aPorts[i]);
+
+ /* Attach drivers to every available port. */
+ for (i = 0; i < pThis->cPortsImpl; i++)
+ {
+ PAHCIPORT pAhciPort = &pThis->aPorts[i];
+ PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i];
+
+ RTStrPrintf(pAhciPortR3->szDesc, sizeof(pAhciPortR3->szDesc), "Port%u", i);
+
+ /*
+ * Init interfaces.
+ */
+ pAhciPortR3->IBase.pfnQueryInterface = ahciR3PortQueryInterface;
+ pAhciPortR3->IMediaExPort.pfnIoReqCompleteNotify = ahciR3IoReqCompleteNotify;
+ pAhciPortR3->IMediaExPort.pfnIoReqCopyFromBuf = ahciR3IoReqCopyFromBuf;
+ pAhciPortR3->IMediaExPort.pfnIoReqCopyToBuf = ahciR3IoReqCopyToBuf;
+ pAhciPortR3->IMediaExPort.pfnIoReqQueryBuf = ahciR3IoReqQueryBuf;
+ pAhciPortR3->IMediaExPort.pfnIoReqQueryDiscardRanges = ahciR3IoReqQueryDiscardRanges;
+ pAhciPortR3->IMediaExPort.pfnIoReqStateChanged = ahciR3IoReqStateChanged;
+ pAhciPortR3->IMediaExPort.pfnMediumEjected = ahciR3MediumEjected;
+ pAhciPortR3->IPort.pfnQueryDeviceLocation = ahciR3PortQueryDeviceLocation;
+ pAhciPortR3->IPort.pfnQueryScsiInqStrings = ahciR3PortQueryScsiInqStrings;
+ pAhciPort->fWrkThreadSleeping = true;
+
+ /* Query per port configuration options if available. */
+ PCFGMNODE pCfgPort = pHlp->pfnCFGMGetChild(pDevIns->pCfg, pAhciPortR3->szDesc);
+ if (pCfgPort)
+ {
+ rc = pHlp->pfnCFGMQueryBoolDef(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, &pAhciPortR3->IBase, &pAhciPortR3->pDrvBase, pAhciPortR3->szDesc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ahciR3ConfigureLUN(pDevIns, pAhciPort, pAhciPortR3);
+ if (RT_FAILURE(rc))
+ {
+ Log(("%s: Failed to configure the %s.\n", __FUNCTION__, pAhciPortR3->szDesc));
+ return rc;
+ }
+
+ /* Mark that a device is present on that port */
+ if (i < 6)
+ pPciDev->abConfig[0x93] |= (1 << i);
+
+ /*
+ * Init vendor product data.
+ */
+ rc = ahciR3VpdInit(pDevIns, pAhciPort, pAhciPortR3, pAhciPortR3->szDesc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pAhciPort->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to create SUP event semaphore"));
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPortR3->pAsyncIOThread, pAhciPortR3, ahciAsyncIOLoop,
+ ahciAsyncIOLoopWakeUp, 0, RTTHREADTYPE_IO, pAhciPortR3->szDesc);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to create worker thread %s"), pAhciPortR3->szDesc);
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pAhciPortR3->pDrvBase = NULL;
+ pAhciPort->fPresent = false;
+ rc = VINF_SUCCESS;
+ LogRel(("AHCI: %s: No driver attached\n", pAhciPortR3->szDesc));
+ }
+ else
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("AHCI: Failed to attach drive to %s"), pAhciPortR3->szDesc);
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else
+ AssertMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc),
+ PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot attach to status driver")));
+
+ /*
+ * Saved state.
+ */
+ 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);
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ahciRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake0, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake1, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake2, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake3, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortIdxData, ahciIdxDataWrite, ahciIdxDataRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ahciMMIOWrite, ahciMMIORead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceAHCI =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "ahci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | 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,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(AHCI),
+ /* .cbInstanceCC = */ sizeof(AHCICC),
+ /* .cbInstanceRC = */ sizeof(AHCIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Intel AHCI controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ahciR3Construct,
+ /* .pfnDestruct = */ ahciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ahciR3Reset,
+ /* .pfnSuspend = */ ahciR3Suspend,
+ /* .pfnResume = */ ahciR3Resume,
+ /* .pfnAttach = */ ahciR3Attach,
+ /* .pfnDetach = */ ahciR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ ahciR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ahciRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ahciRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#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..bef5a2b0
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevATA.cpp
@@ -0,0 +1,8484 @@
+/* $Id: DevATA.cpp $ */
+/** @file
+ * VBox storage devices: ATA/ATAPI controller device (disk and cdrom).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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/AssertGuest.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+#include <VBox/ata.h>
+
+#include "ATAPIPassthrough.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* 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 21
+/** Saved state version without iCurLBA for ATA commands. */
+#define ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA 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
+/** @} */
+
+/** Values read from an empty (with no devices attached) ATA bus. */
+#define ATA_EMPTY_BUS_DATA 0x7F
+#define ATA_EMPTY_BUS_DATA_32 0x7F7F7F7F
+
+/**
+ * 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
+
+/** The maxium I/O buffer size (for sanity). */
+#define ATA_MAX_SECTOR_SIZE _4K
+/** The maxium I/O buffer size (for sanity). */
+#define ATA_MAX_IO_BUFFER_SIZE (ATA_MAX_MULT_SECTORS * ATA_MAX_SECTOR_SIZE)
+
+/** Mask to be applied to all indexing into ATACONTROLLER::aIfs. */
+#define ATA_SELECTED_IF_MASK 1
+
+/**
+ * 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_EJECT_REQUESTED 1 /**< medium eject requested (eject button pressed) */
+#define ATA_EVENT_STATUS_MEDIA_NEW 2 /**< new medium inserted */
+#define ATA_EVENT_STATUS_MEDIA_REMOVED 3 /**< medium removed */
+#define ATA_EVENT_STATUS_MEDIA_CHANGED 4 /**< medium was removed + new medium was inserted */
+
+/* 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
+/** @} */
+
+/** Number of I/O ports per bus-master DMA controller. */
+#define BM_DMA_CTL_IOPORTS 8
+/** Mask corresponding to BM_DMA_CTL_IOPORTS. */
+#define BM_DMA_CTL_IOPORTS_MASK 7
+/** Shift count corresponding to BM_DMA_CTL_IOPORTS. */
+#define BM_DMA_CTL_IOPORTS_SHIFT 3
+
+/** @} */
+
+#define ATADEVSTATE_2_DEVINS(pIf) ( (pIf)->CTX_SUFF(pDevIns) )
+#define CONTROLLER_2_DEVINS(pController) ( (pController)->CTX_SUFF(pDevIns) )
+
+
+/*********************************************************************************************************************************
+* 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;
+ /** Explicit alignment padding. */
+ uint8_t abAlignment[2];
+ /** 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 shared state of an ATA device.
+ */
+typedef struct ATADEVSTATE
+{
+ /** The I/O buffer.
+ * @note Page aligned in case it helps. */
+ uint8_t abIOBuffer[ATA_MAX_IO_BUFFER_SIZE];
+
+ /** 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;
+ /** Physical CHS disk geometry (static). */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Translated CHS disk geometry (variable). */
+ PDMMEDIAGEOMETRY XCHSGeometry;
+ /** 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;
+
+ /** Current LBA position (both ATA/ATAPI). */
+ uint32_t iCurLBA;
+ /** ATAPI current sector size. */
+ uint32_t cbATAPISector;
+ /** ATAPI current command. */
+ uint8_t abATAPICmd[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;
+
+ /*
+ * 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;
+
+ /** The LUN number. */
+ uint32_t iLUN;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** The device number. */
+ uint8_t iDev;
+ /** Set if the device is present. */
+ bool fPresent;
+ /** Explicit alignment. */
+ uint8_t bAlignment2;
+
+ /** 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];
+
+ /** Padding the structure to a multiple of 4096 for better I/O buffer alignment. */
+ uint8_t abAlignment4[7 + 3528];
+} ATADEVSTATE;
+AssertCompileMemberAlignment(ATADEVSTATE, cTotalSectors, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, StatATADMA, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, u64CmdTS, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, szSerialNumber, 8);
+AssertCompileSizeAlignment(ATADEVSTATE, 4096); /* To align the buffer on a page boundrary. */
+/** Pointer to the shared state of an ATA device. */
+typedef ATADEVSTATE *PATADEVSTATE;
+
+
+/**
+ * The ring-3 state of an ATA device.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIBLOCKPORT
+ * @implements PDMIMOUNTNOTIFY
+ */
+typedef struct ATADEVSTATER3
+{
+ /** 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 number. */
+ uint32_t iLUN;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** The device number. */
+ uint8_t iDev;
+ /** Explicit alignment. */
+ uint8_t abAlignment2[2];
+ /** The device instance so we can get our bearings from an interface method. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** The current tracklist of the loaded medium if passthrough is used. */
+ R3PTRTYPE(PTRACKLIST) pTrackList;
+} ATADEVSTATER3;
+/** Pointer to the ring-3 state of an ATA device. */
+typedef ATADEVSTATER3 *PATADEVSTATER3;
+
+
+/**
+ * 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;
+ /** Transfer direction. */
+ uint8_t uTxDir;
+ /** How many bytes to transfer. */
+ uint32_t cbTotalTransfer;
+} 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 shared state of an ATA controller.
+ *
+ * Has two devices, the master (0) and the slave (1).
+ */
+typedef struct ATACONTROLLER
+{
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADEVSTATE aIfs[2];
+
+ /** 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. */
+ uint32_t 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 event semaphore the thread is waiting on for requests. */
+ SUPSEMEVENT hAsyncIOSem;
+ /** 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;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** Magic delay before triggering interrupts in DMA mode. */
+ uint32_t msDelayIRQ;
+ /** The lock protecting the request queue. */
+ PDMCRITSECT AsyncIORequestLock;
+
+ /** Timestamp we started the reset. */
+ uint64_t u64ResetTime;
+
+ /** The first port in the first I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts1First;
+ /** The other ports in the first I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts1Other;
+ /** The second I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts2;
+ /** The first I/O port range, empty controller operation. */
+ IOMIOPORTHANDLE hIoPortsEmpty1;
+ /** The second I/O port range, empty controller operation. */
+ IOMIOPORTHANDLE hIoPortsEmpty2;
+
+ /* Statistics */
+ STAMCOUNTER StatAsyncOps;
+ uint64_t StatAsyncMinWait;
+ uint64_t StatAsyncMaxWait;
+ STAMCOUNTER StatAsyncTimeUS;
+ STAMPROFILEADV StatAsyncTime;
+ STAMPROFILE StatLockWait;
+ uint8_t abAlignment4[3328];
+} ATACONTROLLER;
+AssertCompileMemberAlignment(ATACONTROLLER, lock, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, aIfs, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, u64ResetTime, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, StatAsyncOps, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, AsyncIORequestLock, 8);
+AssertCompileSizeAlignment(ATACONTROLLER, 4096); /* To align the controllers, devices and I/O buffers on page boundaries. */
+/** Pointer to the shared state of an ATA controller. */
+typedef ATACONTROLLER *PATACONTROLLER;
+
+
+/**
+ * The ring-3 state of an ATA controller.
+ */
+typedef struct ATACONTROLLERR3
+{
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADEVSTATER3 aIfs[2];
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** The async I/O thread handle. NIL_RTTHREAD if no thread. */
+ RTTHREAD hAsyncIOThread;
+ /** The event semaphore the thread is waiting on during suspended I/O. */
+ RTSEMEVENT hSuspendIOSem;
+ /** Set when the destroying the device instance and the thread must exit. */
+ uint32_t volatile fShutdown;
+ /** Whether to call PDMDevHlpAsyncNotificationCompleted when idle. */
+ bool volatile fSignalIdle;
+
+ /** The controller number. */
+ uint8_t iCtl;
+
+ uint8_t abAlignment[3];
+} ATACONTROLLERR3;
+/** Pointer to the ring-3 state of an ATA controller. */
+typedef ATACONTROLLERR3 *PATACONTROLLERR3;
+
+
+/** ATA chipset type. */
+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,
+ /** ICH6 chipset */
+ CHIPSET_ICH6,
+ CHIPSET_32BIT_HACK=0x7fffffff
+} CHIPSET;
+AssertCompileSize(CHIPSET, 4);
+
+/**
+ * The shared state of a ATA PCI device.
+ */
+typedef struct ATASTATE
+{
+ /** The controllers. */
+ ATACONTROLLER aCts[2];
+ /** Flag indicating chipset being emulated. */
+ CHIPSET enmChipset;
+ /** Explicit alignment padding. */
+ uint8_t abAlignment1[7];
+ /** PCI region \#4: Bus-master DMA I/O ports. */
+ IOMIOPORTHANDLE hIoPortsBmDma;
+} ATASTATE;
+/** Pointer to the shared state of an ATA PCI device. */
+typedef ATASTATE *PATASTATE;
+
+
+/**
+ * The ring-3 state of a ATA PCI device.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct ATASTATER3
+{
+ /** The controllers. */
+ ATACONTROLLERR3 aCts[2];
+ /** 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;
+ /** Pointer to device instance (for getting our bearings in interface methods). */
+ PPDMDEVINSR3 pDevIns;
+} ATASTATER3;
+/** Pointer to the ring-3 state of an ATA PCI device. */
+typedef ATASTATER3 *PATASTATER3;
+
+
+/**
+ * The ring-0 state of the ATA PCI device.
+ */
+typedef struct ATASTATER0
+{
+ uint64_t uUnused;
+} ATASTATER0;
+/** Pointer to the ring-0 state of an ATA PCI device. */
+typedef ATASTATER0 *PATASTATER0;
+
+
+/**
+ * The raw-mode state of the ATA PCI device.
+ */
+typedef struct ATASTATERC
+{
+ uint64_t uUnused;
+} ATASTATERC;
+/** Pointer to the raw-mode state of an ATA PCI device. */
+typedef ATASTATERC *PATASTATERC;
+
+
+/** The current context state of an ATA PCI device. */
+typedef CTX_SUFF(ATASTATE) ATASTATECC;
+/** Pointer to the current context state of an ATA PCI device. */
+typedef CTX_SUFF(PATASTATE) PATASTATECC;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+#ifdef IN_RING3
+DECLINLINE(void) ataSetStatusValue(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* 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(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* 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(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* 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 FNBEGINTRANSFER(PATACONTROLLER pCtl, PATADEVSTATE s);
+typedef FNBEGINTRANSFER *PFNBEGINTRANSFER;
+typedef bool FNSOURCESINK(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3);
+typedef FNSOURCESINK *PFNSOURCESINK;
+
+static FNBEGINTRANSFER ataR3ReadWriteSectorsBT;
+static FNBEGINTRANSFER ataR3PacketBT;
+static FNBEGINTRANSFER atapiR3CmdBT;
+static FNBEGINTRANSFER atapiR3PassthroughCmdBT;
+
+static FNSOURCESINK ataR3IdentifySS;
+static FNSOURCESINK ataR3FlushSS;
+static FNSOURCESINK ataR3ReadSectorsSS;
+static FNSOURCESINK ataR3WriteSectorsSS;
+static FNSOURCESINK ataR3ExecuteDeviceDiagnosticSS;
+static FNSOURCESINK ataR3TrimSS;
+static FNSOURCESINK ataR3PacketSS;
+static FNSOURCESINK ataR3InitDevParmSS;
+static FNSOURCESINK ataR3RecalibrateSS;
+static FNSOURCESINK atapiR3GetConfigurationSS;
+static FNSOURCESINK atapiR3GetEventStatusNotificationSS;
+static FNSOURCESINK atapiR3IdentifySS;
+static FNSOURCESINK atapiR3InquirySS;
+static FNSOURCESINK atapiR3MechanismStatusSS;
+static FNSOURCESINK atapiR3ModeSenseErrorRecoverySS;
+static FNSOURCESINK atapiR3ModeSenseCDStatusSS;
+static FNSOURCESINK atapiR3ReadSS;
+static FNSOURCESINK atapiR3ReadCapacitySS;
+static FNSOURCESINK atapiR3ReadDiscInformationSS;
+static FNSOURCESINK atapiR3ReadTOCNormalSS;
+static FNSOURCESINK atapiR3ReadTOCMultiSS;
+static FNSOURCESINK atapiR3ReadTOCRawSS;
+static FNSOURCESINK atapiR3ReadTrackInformationSS;
+static FNSOURCESINK atapiR3RequestSenseSS;
+static FNSOURCESINK atapiR3PassthroughSS;
+static FNSOURCESINK atapiR3ReadDVDStructureSS;
+# 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 PFNBEGINTRANSFER 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 PFNSOURCESINK 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(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ pCtl->AsyncIOReqHead = 0;
+ pCtl->AsyncIOReqTail = 0;
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+# endif /* IN_RING3 */
+
+static void ataHCAsyncIOPutRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, const ATARequest *pReq)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ uint8_t const iAsyncIORequest = pCtl->AsyncIOReqHead % RT_ELEMENTS(pCtl->aAsyncIORequests);
+ Assert((iAsyncIORequest + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
+ memcpy(&pCtl->aAsyncIORequests[iAsyncIORequest], pReq, sizeof(*pReq));
+ pCtl->AsyncIOReqHead = (iAsyncIORequest + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+
+ rc = PDMDevHlpCritSectScheduleExitEvent(pDevIns, &pCtl->lock, pCtl->hAsyncIOSem);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDevHlpSUPSemEventSignal(pDevIns, pCtl->hAsyncIOSem);
+ AssertRC(rc);
+ }
+}
+
+# ifdef IN_RING3
+
+static const ATARequest *ataR3AsyncIOGetCurrentRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ const ATARequest *pReq;
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
+ pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
+ else
+ pReq = NULL;
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &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 pDevIns The device instance.
+ * @param pCtl Controller for which to remove the request.
+ * @param ReqType Type of the request to remove.
+ */
+static void ataR3AsyncIORemoveCurrentRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, ATAAIO ReqType)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
+ {
+ pCtl->AsyncIOReqTail++;
+ pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+ }
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &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 pDevIns The device instance.
+ * @param pCtl Controller for which to dump the queue.
+ */
+static void ataR3AsyncIODumpRequests(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ LogRel(("PIIX3 ATA: Ctl#%d: request queue dump (topmost is current):\n", pCtl->iCtl));
+ uint8_t curr = pCtl->AsyncIOReqTail;
+ do
+ {
+ if (curr == pCtl->AsyncIOReqHead)
+ LogRel(("PIIX3 ATA: Ctl#%d: processed requests (topmost is oldest):\n", pCtl->iCtl));
+ 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 = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+
+/**
+ * Checks whether the request queue for a particular controller is empty
+ * or whether a particular controller is idle.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl Controller for which to check the queue.
+ * @param fStrict If set then the controller is checked to be idle.
+ */
+static bool ataR3AsyncIOIsIdle(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, bool fStrict)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ bool fIdle = pCtl->fRedoIdle;
+ if (!fIdle)
+ fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
+ if (fStrict)
+ fIdle &= (pCtl->uAsyncIOState == ATA_AIO_NEW);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+ return fIdle;
+}
+
+
+/**
+ * Send a transfer request to the async I/O thread.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @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(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s,
+ uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer,
+ ATAFNSS iSourceSink, bool fChainedTransfer)
+{
+ ATARequest Req;
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &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__, pCtl->iCtl));
+ 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(pDevIns, pCtl, true /*fStrict*/))
+ {
+ Log(("%s: Ctl#%d: ignored command %#04x, controller state %d\n", __FUNCTION__, pCtl->iCtl, 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(pCtl, 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__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &Req);
+}
+
+
+/**
+ * Send an abort command request to the async I/O thread.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ * @param fResetDrive Whether to reset the drive or just abort a command.
+ */
+static void ataR3AbortCurrentCommand(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, bool fResetDrive)
+{
+ ATARequest Req;
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &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__, pCtl->iCtl));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_ABORT;
+ Req.u.a.iIf = pCtl->iSelectedIf;
+ Req.u.a.fResetDrive = fResetDrive;
+ ataSetStatus(pCtl, s, ATA_STAT_BUSY);
+ Log2(("%s: Ctl#%d: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, pCtl->iCtl, s->iLUN));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &Req);
+}
+
+# endif /* IN_RING3 */
+
+/**
+ * Set the internal interrupt pending status, update INTREQ as appropriate.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ */
+static void ataHCSetIRQ(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ if (!s->fIrqPending)
+ {
+ 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. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ /* Only actually set the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK])
+ {
+ /** @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 */
+
+/**
+ * Clear the internal interrupt pending status, update INTREQ as appropriate.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ */
+static void ataUnsetIRQ(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ if (s->fIrqPending)
+ {
+ 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 & ATA_SELECTED_IF_MASK])
+ {
+ 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(PATACONTROLLER pCtl, PATADEVSTATE 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(pCtl, s, ATA_STAT_DRQ | ATA_STAT_SEEK);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY);
+}
+
+
+static void ataHCPIOTransferStop(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE 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(pDevIns, pCtl, 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(PATADEVSTATE 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The controller to lock.
+ */
+DECLINLINE(void) ataR3LockEnter(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->lock, rcLock);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+}
+
+/**
+ * Leaves the lock protecting the controller against concurrent data access.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The controller to unlock.
+ */
+DECLINLINE(void) ataR3LockLeave(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+}
+
+static uint32_t ataR3GetNSectors(PATADEVSTATE 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(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t status)
+{
+ s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | status);
+}
+
+
+static void ataR3CmdError(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t uErrorCode)
+{
+ Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
+ Assert(uErrorCode);
+ s->uATARegError = uErrorCode;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK | 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;
+}
+
+/**
+ * Sink/Source: IDENTIFY
+ */
+static bool ataR3IdentifySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint16_t *p;
+ RT_NOREF(pDevIns);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)&s->abIOBuffer[0];
+ 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->XCHSGeometry.cCylinders, 16383));
+ p[55] = RT_H2LE_U16(s->XCHSGeometry.cHeads);
+ p[56] = RT_H2LE_U16(s->XCHSGeometry.cSectors);
+ p[57] = RT_H2LE_U16( RT_MIN(s->XCHSGeometry.cCylinders, 16383)
+ * s->XCHSGeometry.cHeads
+ * s->XCHSGeometry.cSectors);
+ p[58] = RT_H2LE_U16( RT_MIN(s->XCHSGeometry.cCylinders, 16383)
+ * s->XCHSGeometry.cHeads
+ * s->XCHSGeometry.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 ( pDevR3->pDrvMedia->pfnDiscard
+ || s->cbSector != 512
+ || pDevR3->pDrvMedia->pfnIsNonRotational(pDevR3->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 (pDevR3->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 (pDevR3->pDrvMedia->pfnIsNonRotational(pDevR3->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(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+/**
+ * Sink/Source: FLUSH
+ */
+static bool ataR3FlushSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE);
+ Assert(!s->cbElementaryTransfer);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ STAM_PROFILE_START(&s->StatFlushes, f);
+ rc = pDevR3->pDrvMedia->pfnFlush(pDevR3->pDrvMedia);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&s->StatFlushes, f);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, 0);
+ return false;
+}
+
+/**
+ * Sink/Source: ATAPI IDENTIFY
+ */
+static bool atapiR3IdentifySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint16_t *p;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)&s->abIOBuffer[0];
+ 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(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static void ataR3SetSignature(PATADEVSTATE s)
+{
+ s->uATARegSelect &= 0xf0; /* clear head */
+ /* put signature */
+ s->uATARegNSector = 1;
+ s->uATARegSector = 1;
+ if (s->fATAPI)
+ {
+ s->uATARegLCyl = 0x14;
+ s->uATARegHCyl = 0xeb;
+ }
+ else
+ {
+ s->uATARegLCyl = 0;
+ s->uATARegHCyl = 0;
+ }
+}
+
+
+static uint64_t ataR3GetSector(PATADEVSTATE 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 = ((uint32_t)(s->uATARegSelect & 0x0f) << 24)
+ | ((uint32_t)s->uATARegHCyl << 16)
+ | ((uint32_t)s->uATARegLCyl << 8)
+ | s->uATARegSector;
+ }
+ }
+ else
+ {
+ /* CHS */
+ iLBA = (((uint32_t)s->uATARegHCyl << 8) | s->uATARegLCyl) * s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors
+ + (s->uATARegSelect & 0x0f) * s->XCHSGeometry.cSectors
+ + (s->uATARegSector - 1);
+ LogFlowFunc(("CHS %u/%u/%u -> LBA %llu\n", ((uint32_t)s->uATARegHCyl << 8) | s->uATARegLCyl, s->uATARegSelect & 0x0f, s->uATARegSector, iLBA));
+ }
+ return iLBA;
+}
+
+static void ataR3SetSector(PATADEVSTATE 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 */
+ AssertMsgReturnVoid(s->XCHSGeometry.cHeads && s->XCHSGeometry.cSectors, ("Device geometry not set!\n"));
+ cyl = iLBA / (s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors);
+ r = iLBA % (s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors);
+ s->uATARegHCyl = cyl >> 8;
+ s->uATARegLCyl = cyl;
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->XCHSGeometry.cSectors) & 0x0f);
+ s->uATARegSector = (r % s->XCHSGeometry.cSectors) + 1;
+ LogFlowFunc(("LBA %llu -> CHS %u/%u/%u\n", iLBA, cyl, s->uATARegSelect & 0x0f, s->uATARegSector));
+ }
+}
+
+
+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 void ataR3WarningFileStale(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: File handle became stale\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_FILESTALE",
+ N_("The file became stale (often due to a restarted NFS server). VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+
+static bool ataR3IsRedoSetWarning(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, int rc)
+{
+ Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pCtl->lock));
+ if (rc == VERR_DISK_FULL)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningDiskFull(pDevIns);
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningFileTooBig(pDevIns);
+ 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(pDevIns);
+ return true;
+ }
+ if (rc == VERR_STALE_FILE_HANDLE)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningFileStale(pDevIns);
+ return true;
+ }
+ if (rc == VERR_VD_DEK_MISSING)
+ {
+ /* Error message already set. */
+ pCtl->fRedoIdle = true;
+ return true;
+ }
+
+ return false;
+}
+
+
+static int ataR3ReadSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, void *pvBuf, uint32_t cSectors, bool *pfRedo)
+{
+ int rc;
+ uint32_t const cbSector = s->cbSector;
+ uint32_t cbToRead = cSectors * cbSector;
+ Assert(pvBuf == &s->abIOBuffer[0]);
+ AssertReturnStmt(cbToRead <= sizeof(s->abIOBuffer), *pfRedo = false, VERR_BUFFER_OVERFLOW);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, u64Sector * cbSector, pvBuf, cbToRead);
+ 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, cbToRead, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbToRead);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+static int ataR3WriteSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, const void *pvBuf, uint32_t cSectors, bool *pfRedo)
+{
+ int rc;
+ uint32_t const cbSector = s->cbSector;
+ uint32_t cbToWrite = cSectors * cbSector;
+ Assert(pvBuf == &s->abIOBuffer[0]);
+ AssertReturnStmt(cbToWrite <= sizeof(s->abIOBuffer), *pfRedo = false, VERR_BUFFER_OVERFLOW);
+
+ ataR3LockLeave(pDevIns, 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 = pDevR3->pDrvMedia->pfnWrite(pDevR3->pDrvMedia, u64Sector * cbSector, pvBuf, cbToWrite);
+# 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, cbToWrite, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cbToWrite);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+/**
+ * Begin Transfer: READ/WRITE SECTORS
+ */
+static void ataR3ReadWriteSectorsBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint32_t cSectors;
+
+ cSectors = s->cbTotalTransfer / cbSector;
+ if (cSectors > s->cSectorsPerIRQ)
+ s->cbElementaryTransfer = s->cSectorsPerIRQ * cbSector;
+ else
+ s->cbElementaryTransfer = cSectors * cbSector;
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ ataR3CmdOK(pCtl, s, 0);
+}
+
+
+/**
+ * Sink/Source: READ SECTORS
+ */
+static bool ataR3ReadSectorsSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint32_t cSectors;
+ uint64_t iLBA;
+ bool fRedo;
+ int rc;
+
+ cSectors = s->cbElementaryTransfer / cbSector;
+ Assert(cSectors);
+ iLBA = s->iCurLBA;
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3ReadSectors(pDevIns, pCtl, s, pDevR3, iLBA, s->abIOBuffer, cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ /* When READ SECTORS etc. finishes, the address in the task
+ * file register points at the last sector read, not at the next
+ * sector that would be read. This ensures the registers always
+ * contain a valid sector address.
+ */
+ if (s->cbElementaryTransfer == s->cbTotalTransfer)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3SetSector(s, iLBA + cSectors - 1);
+ }
+ else
+ ataR3SetSector(s, iLBA + cSectors);
+ s->uATARegNSector -= cSectors;
+ s->iCurLBA += cSectors;
+ ataR3CmdOK(pCtl, 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(pCtl, s, ID_ERR);
+ }
+ return false;
+}
+
+
+/**
+ * Sink/Source: WRITE SECTOR
+ */
+static bool ataR3WriteSectorsSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint64_t iLBA;
+ uint32_t cSectors;
+ bool fRedo;
+ int rc;
+
+ cSectors = s->cbElementaryTransfer / cbSector;
+ Assert(cSectors);
+ iLBA = s->iCurLBA;
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3WriteSectors(pDevIns, pCtl, s, pDevR3, iLBA, s->abIOBuffer, cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ ataR3SetSector(s, iLBA + cSectors);
+ s->iCurLBA = iLBA + cSectors;
+ if (!s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(pCtl, 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(pCtl, s, ID_ERR);
+ }
+ return false;
+}
+
+
+static void atapiR3CmdOK(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ s->uATARegError = 0;
+ ataSetStatusValue(pCtl, 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(PATACONTROLLER pCtl, PATADEVSTATE 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(pCtl, 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(PATACONTROLLER pCtl, PATADEVSTATE 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(pCtl, s, abATAPISense, sizeof(abATAPISense));
+}
+
+
+/**
+ * Begin Transfer: ATAPI command
+ */
+static void atapiR3CmdBT(PATACONTROLLER pCtl, PATADEVSTATE 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(pCtl, s);
+}
+
+
+/**
+ * Begin Transfer: ATAPI Passthrough command
+ */
+static void atapiR3PassthroughCmdBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ atapiR3CmdBT(pCtl, s);
+}
+
+
+/**
+ * Sink/Source: READ
+ */
+static bool atapiR3ReadSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc;
+ uint64_t cbBlockRegion = 0;
+ VDREGIONDATAFORM enmDataForm;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ uint32_t const iATAPILBA = s->iCurLBA;
+ uint32_t const cbTransfer = RT_MIN(s->cbTotalTransfer, RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE));
+ uint32_t const cbATAPISector = s->cbATAPISector;
+ uint32_t const cSectors = cbTransfer / cbATAPISector;
+ Assert(cSectors * cbATAPISector <= cbTransfer);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iATAPILBA));
+ AssertLogRelReturn(cSectors * cbATAPISector <= sizeof(s->abIOBuffer), false);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, iATAPILBA, NULL, NULL,
+ &cbBlockRegion, &enmDataForm);
+ 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 == cbATAPISector)
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, (uint64_t)iATAPILBA * cbATAPISector,
+ s->abIOBuffer, cbATAPISector * cSectors);
+ else
+ {
+ uint32_t const iEndSector = iATAPILBA + cSectors;
+ ASSERT_GUEST(iEndSector >= iATAPILBA);
+ if (cbBlockRegion == 2048 && cbATAPISector == 2352)
+ {
+ /* Generate the sync bytes. */
+ uint8_t *pbBuf = s->abIOBuffer;
+
+ for (uint32_t i = iATAPILBA; i < iEndSector; 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 = pDevR3->pDrvMedia->pfnRead(pDevR3->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 && cbATAPISector == 2048)
+ {
+ /* Read only the user data portion. */
+ uint8_t *pbBuf = s->abIOBuffer;
+
+ for (uint32_t i = iATAPILBA; i < iEndSector; i++)
+ {
+ uint8_t abTmp[2352];
+ uint8_t cbSkip;
+
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, (uint64_t)i * 2352, &abTmp[0], 2352);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Mode 2 has an additional subheader before user data; we need to
+ * skip 16 bytes for Mode 1 (sync + header) and 20 bytes for Mode 2 +
+ * (sync + header + subheader).
+ */
+ switch (enmDataForm) {
+ case VDREGIONDATAFORM_MODE2_2352:
+ case VDREGIONDATAFORM_XA_2352:
+ cbSkip = 24;
+ break;
+ case VDREGIONDATAFORM_MODE1_2352:
+ cbSkip = 16;
+ break;
+ default:
+ AssertMsgFailed(("Unexpected region form (%#u), using default skip value\n", enmDataForm));
+ cbSkip = 16;
+ }
+ memcpy(pbBuf, &abTmp[cbSkip], 2048);
+ pbBuf += 2048;
+ }
+ }
+ else
+ ASSERT_GUEST_MSG_FAILED(("Unsupported: cbBlockRegion=%u cbATAPISector=%u\n", cbBlockRegion, cbATAPISector));
+ }
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+ }
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, 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(pCtl, s);
+ s->iCurLBA = 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, iATAPILBA));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
+ }
+ return false;
+}
+
+/**
+ * Sets the given media track type.
+ */
+static uint32_t ataR3MediumTypeSet(PATADEVSTATE s, uint32_t MediaTrackType)
+{
+ return ASMAtomicXchgU32(&s->MediaTrackType, MediaTrackType);
+}
+
+
+/**
+ * Sink/Source: Passthrough
+ */
+static bool atapiR3PassthroughSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ uint32_t cbTransfer;
+ PSTAMPROFILEADV pProf = NULL;
+
+ cbTransfer = RT_MIN(s->cbAtapiPassthroughTransfer, RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE));
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->abIOBuffer));
+
+ /* 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(pDevIns, pCtl);
+
+# if defined(LOG_ENABLED)
+ char szBuf[1024];
+
+ memset(szBuf, 0, sizeof(szBuf));
+
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_MODE_SELECT_10:
+ {
+ size_t cbBlkDescLength = scsiBE2H_U16(&s->abIOBuffer[6]);
+
+ SCSILogModePage(szBuf, sizeof(szBuf) - 1,
+ s->abIOBuffer + 8 + cbBlkDescLength,
+ cbTransfer - 8 - cbBlkDescLength);
+ break;
+ }
+ case SCSI_SEND_CUE_SHEET:
+ {
+ SCSILogCueSheet(szBuf, sizeof(szBuf) - 1,
+ s->abIOBuffer, cbTransfer);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Log2(("%s\n", szBuf));
+# endif
+
+ if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
+
+ Assert(s->cbATAPISector);
+ const uint32_t cbATAPISector = RT_MAX(s->cbATAPISector, 1); /* paranoia */
+ const uint32_t cbIOBuffer = RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE); /* ditto */
+
+ if ( cbTransfer > SCSI_MAX_BUFFER_SIZE
+ || s->cbElementaryTransfer > 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 abATAPICmd[ATAPI_PACKET_SIZE];
+ uint32_t iATAPILBA, cSectors, cReqSectors, cbCurrTX;
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t cSectorsMax; /**< Maximum amount of sectors to read without exceeding the I/O buffer. */
+
+ cSectorsMax = cbTransfer / cbATAPISector;
+ AssertStmt(cSectorsMax * s->cbATAPISector <= cbIOBuffer, cSectorsMax = cbIOBuffer / cbATAPISector);
+
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U16(s->abATAPICmd + 7);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U32(s->abATAPICmd + 6);
+ break;
+ case SCSI_READ_CD:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U24(s->abATAPICmd + 6);
+ break;
+ case SCSI_READ_CD_MSF:
+ iATAPILBA = scsiMSF2LBA(s->abATAPICmd + 3);
+ cSectors = scsiMSF2LBA(s->abATAPICmd + 6) - iATAPILBA;
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->abATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ ataR3LockEnter(pDevIns, pCtl);
+ return false;
+ }
+ cSectorsMax = RT_MIN(cSectorsMax, cSectors);
+ memcpy(abATAPICmd, s->abATAPICmd, ATAPI_PACKET_SIZE);
+ cReqSectors = 0;
+ for (uint32_t i = cSectorsMax; i > 0; i -= cReqSectors)
+ {
+ if (i * cbATAPISector > SCSI_MAX_BUFFER_SIZE)
+ cReqSectors = SCSI_MAX_BUFFER_SIZE / cbATAPISector;
+ else
+ cReqSectors = i;
+ cbCurrTX = cbATAPISector * cReqSectors;
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(abATAPICmd + 7, cReqSectors);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(abATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(abATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(abATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(abATAPICmd + 6, iATAPILBA + cReqSectors);
+ break;
+ }
+ AssertLogRelReturn((uintptr_t)(pbBuf - &s->abIOBuffer[0]) + cbCurrTX <= sizeof(s->abIOBuffer), false);
+ rc = pDevR3->pDrvMedia->pfnSendCmd(pDevR3->pDrvMedia, abATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (rc != VINF_SUCCESS)
+ break;
+ iATAPILBA += cReqSectors;
+ pbBuf += cbATAPISector * cReqSectors;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Adjust ATAPI command for the next call. */
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(s->abATAPICmd + 7, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(s->abATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(s->abATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(s->abATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(s->abATAPICmd + 6, iATAPILBA + cSectors - cSectorsMax);
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->abATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ AssertLogRelReturn(cbTransfer <= sizeof(s->abIOBuffer), false);
+ rc = pDevR3->pDrvMedia->pfnSendCmd(pDevR3->pDrvMedia, s->abATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ s->abIOBuffer, &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ }
+ if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
+
+ ataR3LockEnter(pDevIns, 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->abATAPICmd[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ if (!pDevR3->pTrackList)
+ rc = ATAPIPassthroughTrackListCreateEmpty(&pDevR3->pTrackList);
+
+ if (RT_SUCCESS(rc))
+ rc = ATAPIPassthroughTrackListUpdate(pDevR3->pTrackList, s->abATAPICmd, s->abIOBuffer, sizeof(s->abIOBuffer));
+
+ 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->abATAPICmd[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
+ break;
+ }
+ case SCSI_SYNCHRONIZE_CACHE:
+ {
+ if (pDevR3->pTrackList)
+ ATAPIPassthroughTrackListClear(pDevR3->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 < cbIOBuffer)
+ s->cbTotalTransfer = cbTransfer;
+
+ if ( s->abATAPICmd[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->abIOBuffer));
+ scsiPadStr(&s->abIOBuffer[8], "VBOX", 8);
+ scsiPadStr(&s->abIOBuffer[16], "CD-ROM", 16);
+ scsiPadStr(&s->abIOBuffer[32], "1.0", 4);
+ }
+
+ if (cbTransfer)
+ Log3(("ATAPI PT data read (%d):\n%.*Rhxd\n", cbTransfer, cbTransfer, s->abIOBuffer));
+ }
+
+ /* 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(pCtl, s);
+ }
+ }
+ else
+ {
+ if (s->cErrors < MAX_LOG_REL_ERRORS)
+ {
+ uint8_t u8Cmd = s->abATAPICmd[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(pCtl, s, abATAPISense, sizeof(abATAPISense));
+ }
+ return false;
+}
+
+
+/**
+ * Begin Transfer: Read DVD structures
+ */
+static bool atapiR3ReadDVDStructureSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *buf = s->abIOBuffer;
+ int media = s->abATAPICmd[1];
+ int format = s->abATAPICmd[7];
+ RT_NOREF(pDevIns, pDevR3);
+
+ AssertCompile(sizeof(s->abIOBuffer) > UINT16_MAX /* want a RT_MIN() below, but clang takes offence at always false stuff */);
+ uint16_t max_len = scsiBE2H_U16(&s->abATAPICmd[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->abATAPICmd[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(pCtl, 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(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+static bool atapiR3ReadSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s,
+ uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+ Assert(cSectors > 0);
+ s->iCurLBA = iATAPILBA;
+ s->cbATAPISector = cbSector;
+ ataR3StartTransfer(pDevIns, pCtl, s, cSectors * cbSector,
+ PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ CAPACITY
+ */
+static bool atapiR3ReadCapacitySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ DISCK INFORMATION
+ */
+static bool atapiR3ReadDiscInformationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ 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)pDevR3->pDrvMedia->pfnGetRegionCount(pDevR3->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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TRACK INFORMATION
+ */
+static bool atapiR3ReadTrackInformationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t u32LogAddr = scsiBE2H_U32(&s->abATAPICmd[2]);
+ uint8_t u8LogAddrType = s->abATAPICmd[1] & 0x03;
+ RT_NOREF(pDevIns);
+
+ int rc;
+ 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 = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, u32LogAddr, &uRegion,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ break;
+ case 0x01:
+ {
+ if (u32LogAddr >= 1)
+ {
+ uRegion = u32LogAddr - 1;
+ rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ }
+ else
+ rc = VERR_NOT_FOUND; /** @todo Return lead-in information. */
+ break;
+ }
+ case 0x02:
+ default:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ atapiR3CmdErrorSimple(pCtl, 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(pCtl, s);
+ return false;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureListProfiles(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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 (PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(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 DECLCALLBACKTYPE(uint32_t, FNATAPIR3FEATUREFILL,(PATADEVSTATE 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}
+};
+
+/**
+ * Sink/Source: ATAPI GET CONFIGURATION
+ */
+static bool atapiR3GetConfigurationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbIOBuffer = RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t cbBuf = cbIOBuffer;
+ uint32_t cbCopied = 0;
+ uint16_t u16Sfn = scsiBE2H_U16(&s->abATAPICmd[2]);
+ uint8_t u8Rt = s->abATAPICmd[1] & 0x03;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 80);
+ /* Accept valid request types only. */
+ if (u8Rt == 3)
+ {
+ atapiR3CmdErrorSimple(pCtl, 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->abIOBuffer, 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI GET EVENT STATUS NOTIFICATION
+ */
+static bool atapiR3GetEventStatusNotificationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+
+ if (!(s->abATAPICmd[1] & 1))
+ {
+ /* no asynchronous operation supported */
+ atapiR3CmdErrorSimple(pCtl, 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] = OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED ? 0x04 /* media changed */ : 0x03; /* media removed */
+ 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI INQUIRY
+ */
+static bool atapiR3InquirySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MODE SENSE ERROR RECOVERY
+ */
+static bool atapiR3ModeSenseErrorRecoverySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MODE SENSE CD STATUS
+ */
+static bool atapiR3ModeSenseCDStatusSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns);
+
+ /* 28 bytes of total returned data corresponds to ATAPI 2.6. Note that at least some versions
+ * of NEC_IDE.SYS DOS driver (possibly other Oak Technology OTI-011 drivers) do not correctly
+ * handle cases where more than 28 bytes are returned due to bugs. See @bugref{5869}.
+ */
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 28);
+ scsiH2BE_U16(&pbBuf[0], 26);
+ 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] = 18; /* 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 (pDevR3->pDrvMount && pDevR3->pDrvMount->pfnIsLocked(pDevR3->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], RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) / _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) */
+ pbBuf[26] = 0; /* reserved */
+ pbBuf[27] = 0; /* reserved */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI REQUEST SENSE
+ */
+static bool atapiR3RequestSenseSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ memset(pbBuf, '\0', RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer)));
+ AssertCompile(sizeof(s->abIOBuffer) >= sizeof(s->abATAPISense));
+ memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MECHANISM STATUS
+ */
+static bool atapiR3MechanismStatusSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ 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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC NORMAL
+ */
+static bool atapiR3ReadTOCNormalSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint8_t *q;
+ uint8_t iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+ RT_NOREF(pDevIns);
+
+ /* Track fields are 8-bit and 1-based, so cut the track count at 255,
+ avoiding any potential buffer overflow issues below. */
+ uint32_t cTracks = pDevR3->pDrvMedia->pfnGetRegionCount(pDevR3->pDrvMedia);
+ AssertStmt(cTracks <= UINT8_MAX, cTracks = UINT8_MAX);
+ AssertCompile(sizeof(s->abIOBuffer) >= 2 + 256 + 8);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->abATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->abATAPICmd[6];
+ if (iStartTrack == 0)
+ iStartTrack = 1;
+
+ if (iStartTrack > cTracks && iStartTrack != 0xaa)
+ {
+ atapiR3CmdErrorSimple(pCtl, 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 = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->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 = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC MULTI
+ */
+static bool atapiR3ReadTOCMultiSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ bool fMSF;
+ RT_NOREF(pDevIns);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 12);
+ fMSF = (s->abATAPICmd[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 = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->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(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC RAW
+ */
+static bool atapiR3ReadTOCRawSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint8_t *q;
+ uint8_t iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->abATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->abATAPICmd[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(pCtl, s);
+ return false;
+}
+
+
+static void atapiR3ParseCmdVirtualATAPI(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket = s->abATAPICmd;
+ uint32_t cbMax;
+ uint32_t cSectors, iATAPILBA;
+
+ switch (pbPacket[0])
+ {
+ case SCSI_TEST_UNIT_READY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ if (s->cNotifiedMediaChange-- > 2)
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ }
+ else
+ {
+ PPDMIMOUNT const pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount && pDrvMount->pfnIsMounted(pDrvMount))
+ atapiR3CmdOK(pCtl, s);
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ }
+ break;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, 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_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(pDevIns, pCtl, 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(pDevIns, pCtl, s, RT_MIN(cbMax, 28), 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(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ break;
+ }
+ case SCSI_REQUEST_SENSE:
+ cbMax = pbPacket[4];
+ ataR3StartTransfer(pDevIns, pCtl, 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:
+ {
+ PPDMIMOUNT const pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount && pDrvMount->pfnIsMounted(pDrvMount))
+ {
+ if (pbPacket[4] & 1)
+ pDrvMount->pfnLock(pDrvMount);
+ else
+ pDrvMount->pfnUnlock(pDrvMount);
+ atapiR3CmdOK(pCtl, s);
+ }
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ case SCSI_READ_10:
+ case SCSI_READ_12:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, 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(pCtl, s);
+ break;
+ }
+
+ /* Check that the sector size is valid. */
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->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(pCtl, 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(pCtl, s, &abATAPISense[0], sizeof(abATAPISense));
+ break;
+ }
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2048);
+ break;
+ }
+ case SCSI_READ_CD_MSF:
+ case SCSI_READ_CD:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if ((pbPacket[10] & 0x7) != 0)
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ if (pbPacket[0] == SCSI_READ_CD)
+ {
+ cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+ }
+ else /* READ CD MSF */
+ {
+ iATAPILBA = scsiMSF2LBA(pbPacket + 3);
+ if (iATAPILBA > scsiMSF2LBA(pbPacket + 6))
+ {
+ Log2(("Start MSF %02u:%02u:%02u > end MSF %02u:%02u:%02u!\n", *(pbPacket + 3), *(pbPacket + 4), *(pbPacket + 5),
+ *(pbPacket + 6), *(pbPacket + 7), *(pbPacket + 8)));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ cSectors = scsiMSF2LBA(pbPacket + 6) - iATAPILBA;
+ Log2(("Start MSF %02u:%02u:%02u -> LBA %u\n", *(pbPacket + 3), *(pbPacket + 4), *(pbPacket + 5), iATAPILBA));
+ Log2(("End MSF %02u:%02u:%02u -> %u sectors\n", *(pbPacket + 6), *(pbPacket + 7), *(pbPacket + 8), cSectors));
+ }
+ if (cSectors == 0)
+ {
+ atapiR3CmdOK(pCtl, 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(pCtl, 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 = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, iATAPILBA,
+ NULL, NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ {
+ if (uChnSel == 0)
+ {
+ /* nothing */
+ atapiR3CmdOK(pCtl, s);
+ }
+ else
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2352);
+ }
+ else
+ {
+ switch (uChnSel)
+ {
+ case 0x00:
+ /* nothing */
+ atapiR3CmdOK(pCtl, s);
+ break;
+ case 0x10:
+ /* normal read */
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2048);
+ break;
+ case 0xf8:
+ /* read all data */
+ atapiR3ReadSectors(pDevIns, pCtl, 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(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ }
+ break;
+ }
+ case SCSI_SEEK_10:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, 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(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ atapiR3CmdOK(pCtl, s);
+ ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Linux expects this. Required by ATAPI 2.x when seek completes. */
+ 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. */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PPDMIMOUNT pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount)
+ {
+ ataR3LockLeave(pDevIns, pCtl);
+
+ rc = PDMDevHlpVMReqPriorityCallWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pDrvMount->pfnUnmount, 3,
+ pDrvMount, false /*=fForce*/, true /*=fEject*/);
+ Assert(RT_SUCCESS(rc) || rc == VERR_PDM_MEDIA_LOCKED || rc == VERR_PDM_MEDIA_NOT_MOUNTED);
+ if (RT_SUCCESS(rc) && pThisCC->pMediaNotify)
+ {
+ rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->pMediaNotify, s->iLUN);
+ AssertRC(rc);
+ }
+
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+ else
+ rc = VINF_SUCCESS;
+ break;
+ }
+ case 3: /* 11 - Load media */
+ /** @todo rc = pDevR3->pDrvMount->pfnLoadMedia(pDevR3->pDrvMount) */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ atapiR3CmdOK(pCtl, s);
+ ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Needed by NT 3.51/4.0, see @bugref{5869}. */
+ }
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+ break;
+ }
+ case SCSI_MECHANISM_STATUS:
+ {
+ cbMax = scsiBE2H_U16(pbPacket + 8);
+ ataR3StartTransfer(pDevIns, pCtl, 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(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, 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(pDevIns, pCtl, s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
+ break;
+ case 1:
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 12), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
+ break;
+ case 2:
+ ataR3StartTransfer(pDevIns, pCtl, s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
+ break;
+ default:
+ error_cmd:
+ atapiR3CmdErrorSimple(pCtl, 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(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ ataR3StartTransfer(pDevIns, pCtl, 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(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, 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(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, 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(pDevIns, pCtl, 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(pDevIns, pCtl, 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(pDevIns, pCtl, s, RT_MIN(cbMax, 4), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DVD_STRUCTURE, true);
+ break;
+ default:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ }
+}
+
+
+/*
+ * Parse ATAPI commands, passing them directly to the CD/DVD drive.
+ */
+static void atapiR3ParseCmdPassthrough(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket = &s->abATAPICmd[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(pDevIns, pCtl, 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(pDevIns, pCtl, 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->abATAPICmd), cbBuf, pDevR3->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(pDevIns, pCtl, 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(pCtl, 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(pCtl, s);
+ }
+}
+
+
+static void atapiR3ParseCmd(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket;
+
+ pbPacket = s->abATAPICmd;
+# 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(pDevIns, pCtl, s, pDevR3);
+ else
+ atapiR3ParseCmdVirtualATAPI(pDevIns, pCtl, s, pDevR3);
+}
+
+
+/**
+ * Sink/Source: PACKET
+ */
+static bool ataR3PacketSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ s->fDMA = !!(s->uATARegFeature & 1);
+ memcpy(s->abATAPICmd, s->abIOBuffer, ATAPI_PACKET_SIZE);
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ atapiR3ParseCmd(pDevIns, pCtl, s, pDevR3);
+ 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(PATADEVSTATE 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(PATADEVSTATE 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)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IMountNotify);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pIfR3->pDevIns, PATASTATE);
+ PATADEVSTATE pIf = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThis->aCts, pIfR3->iCtl).aIfs, pIfR3->iDev);
+ Log(("%s: changing LUN#%d\n", __FUNCTION__, pIfR3->iLUN));
+
+ /* Ignore the call if we're called while being attached. */
+ if (!pIfR3->pDrvMedia)
+ return;
+
+ uint32_t cRegions = pIfR3->pDrvMedia->pfnGetRegionCount(pIfR3->pDrvMedia);
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ int rc = pIfR3->pDrvMedia->pfnQueryRegionProperties(pIfR3->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)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IMountNotify);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pIfR3->pDevIns, PATASTATE);
+ PATADEVSTATE pIf = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThis->aCts, pIfR3->iCtl).aIfs, pIfR3->iDev);
+ 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);
+}
+
+/**
+ * Begin Transfer: PACKET
+ */
+static void ataR3PacketBT(PATACONTROLLER pCtl, PATADEVSTATE 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(pCtl, s, ATA_STAT_READY);
+}
+
+
+static void ataR3ResetDevice(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ LogFlowFunc(("\n"));
+ s->cMultSectors = ATA_MAX_MULT_SECTORS;
+ s->cNotifiedMediaChange = 0;
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
+ ASMAtomicWriteU32(&s->MediaTrackType, ATA_MEDIA_TYPE_UNKNOWN);
+ ataUnsetIRQ(pDevIns, pCtl, s);
+
+ s->uATARegSelect = 0x20;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK);
+ 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->XCHSGeometry = s->PCHSGeometry; /* Restore default CHS translation. */
+
+ s->uATARegFeature = 0;
+}
+
+
+static void ataR3DeviceDiag(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ ataR3SetSignature(s);
+ if (s->fATAPI)
+ ataSetStatusValue(pCtl, s, 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK);
+ s->uATARegError = 0x01;
+}
+
+
+/**
+ * Sink/Source: EXECUTE DEVICE DIAGNOTIC
+ */
+static bool ataR3ExecuteDeviceDiagnosticSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevIns, s, pDevR3);
+
+ /* EXECUTE DEVICE DIAGNOSTIC is a very special command which always
+ * gets executed, regardless of which device is selected. As a side
+ * effect, it always completes with device 0 selected.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ ataR3DeviceDiag(pCtl, &pCtl->aIfs[i]);
+
+ LogRel(("ATA: LUN#%d: EXECUTE DEVICE DIAGNOSTIC, status %02X\n", s->iLUN, s->uATARegStatus));
+ pCtl->iSelectedIf = 0;
+
+ return false;
+}
+
+
+/**
+ * Sink/Source: INITIALIZE DEVICE PARAMETERS
+ */
+static bool ataR3InitDevParmSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevR3);
+ LogFlowFunc(("\n"));
+
+ /* Technical Note:
+ * On ST506 type drives with a separate controller, the INITIALIZE DRIVE PARAMETERS command was
+ * required to inform the controller of drive geometry. The controller needed to know the
+ * number of heads and sectors per track so that it could correctly advance to the next track
+ * or cylinder when executing multi-sector commands. Setting a geometry that didn't match the
+ * drive made very little sense because sectors had fixed CHS addresses. It was at best
+ * possible to reduce the drive's capacity by limiting the number of heads and/or sectors
+ * per track.
+ *
+ * IDE drives inherently have to know their true geometry, but most of them also support
+ * programmable translation that can be set through the INITIALIZE DEVICE PARAMETERS command.
+ * In fact most older IDE drives typically weren't operated using their default (native) geometry,
+ * and with newer IDE drives that's not even an option.
+ *
+ * Up to and including ATA-5, the standard defined a CHS to LBA translation (since ATA-6, CHS
+ * support is optional):
+ *
+ * LBA = (((cyl_num * heads_per_cyl) + head_num) * sectors_per_track) + sector_num - 1
+ *
+ * The INITIALIZE DEVICE PARAMETERS command sets the heads_per_cyl and sectors_per_track
+ * values used in the above formula.
+ *
+ * Drives must obviously support an INITIALIZE DRIVE PARAMETERS command matching the drive's
+ * default CHS translation. Everything else is optional.
+ *
+ * We support any geometry with non-zero sectors per track because there's no reason not to;
+ * this behavior is common in many if not most IDE drives.
+ */
+
+ PDMMEDIAGEOMETRY Geom = { 0 };
+
+ Geom.cHeads = (s->uATARegSelect & 0x0f) + 1; /* Effective range 1-16. */
+ Geom.cSectors = s->uATARegNSector; /* Range 0-255, zero is not valid. */
+
+ if (Geom.cSectors)
+ {
+ uint64_t cCylinders = s->cTotalSectors / (Geom.cHeads * Geom.cSectors);
+ Geom.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+
+ s->XCHSGeometry = Geom;
+
+ ataR3LockLeave(pDevIns, pCtl);
+ LogRel(("ATA: LUN#%d: INITIALIZE DEVICE PARAMETERS: %u sectors per track, %u heads\n",
+ s->iLUN, s->uATARegNSector, (s->uATARegSelect & 0x0f) + 1));
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ ataR3LockLeave(pDevIns, pCtl);
+ LogRel(("ATA: LUN#%d: INITIALIZE DEVICE PARAMETERS error (zero sectors per track)!\n", s->iLUN));
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ }
+ return false;
+}
+
+
+/**
+ * Sink/Source: RECALIBRATE
+ */
+static bool ataR3RecalibrateSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevR3);
+ LogFlowFunc(("\n"));
+ ataR3LockLeave(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static int ataR3TrimSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, uint32_t cSectors, bool *pfRedo)
+{
+ RTRANGE TrimRange;
+ int rc;
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ TrimRange.offStart = u64Sector * s->cbSector;
+ TrimRange.cbRange = cSectors * s->cbSector;
+
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ rc = pDevR3->pDrvMedia->pfnDiscard(pDevR3->pDrvMedia, &TrimRange, 1);
+ s->Led.Actual.s.fWriting = 0;
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+/**
+ * Sink/Source: TRIM
+ */
+static bool ataR3TrimSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ uint32_t cRangesMax;
+ uint64_t *pu64Range = (uint64_t *)&s->abIOBuffer[0];
+ bool fRedo = false;
+
+ cRangesMax = RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer)) / sizeof(uint64_t);
+ Assert(cRangesMax);
+
+ while (cRangesMax-- > 0)
+ {
+ if (ATA_RANGE_LENGTH_GET(*pu64Range) == 0)
+ break;
+
+ rc = ataR3TrimSectors(pDevIns, pCtl, s, pDevR3, *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(pCtl, 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(pCtl, s, ID_ERR);
+ }
+
+ return false;
+}
+
+
+static void ataR3ParseCmd(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3, 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 (pDevR3->pDrvMedia && !s->fATAPI)
+ ataR3StartTransfer(pDevIns, pCtl, s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
+ else
+ {
+ if (s->fATAPI)
+ ataR3SetSignature(s);
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_RECALIBRATE:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_RECALIBRATE, false);
+ break;
+ case ATA_INITIALIZE_DEVICE_PARAMETERS:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, 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(pCtl, s, ABRT_ERR);
+ }
+ else
+ {
+ Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
+ s->cMultSectors = s->uATARegNSector;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ ataHCSetIRQ(pDevIns, pCtl, 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(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->iCurLBA = ataR3GetSector(s);
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->iCurLBA = ataR3GetSector(s);
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, 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:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->fLBA48 = true;
+ ataR3SetSector(s, s->cTotalSectors - 1);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SEEK: /* Used by the SCO OpenServer. Command is marked as obsolete */
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ ataR3SetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_CHECK_POWER_MODE:
+ s->uATARegNSector = 0xff; /* drive active or idle */
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SET_FEATURES:
+ Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
+ if (!pDevR3->pDrvMedia)
+ goto abort_cmd;
+ switch (s->uATARegFeature)
+ {
+ case 0x02: /* write cache enable */
+ Log2(("%s: write cache enable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xaa: /* read look-ahead enable */
+ Log2(("%s: read look-ahead enable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x55: /* read look-ahead disable */
+ Log2(("%s: read look-ahead disable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, 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(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, 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(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, 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(pDevIns, pCtl, 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(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, 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 (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case ATA_STANDBY_IMMEDIATE:
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_IDLE_IMMEDIATE:
+ LogRel(("PIIX3 ATA: LUN#%d: aborting current command\n", s->iLUN));
+ ataR3AbortCurrentCommand(pDevIns, pCtl, s, false);
+ break;
+ case ATA_SLEEP:
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ break;
+ /* ATAPI commands */
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ if (s->fATAPI)
+ ataR3StartTransfer(pDevIns, pCtl, s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
+ else
+ {
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
+ ataR3StartTransfer(pDevIns, pCtl, 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(pDevIns, pCtl, s, true);
+ break;
+ case ATA_PACKET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ /* overlapping commands not supported */
+ if (s->uATARegFeature & 0x02)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, ATAPI_PACKET_SIZE, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
+ break;
+ case ATA_DATA_SET_MANAGEMENT:
+ if (!pDevR3->pDrvMedia || !pDevR3->pDrvMedia->pfnDiscard)
+ goto abort_cmd;
+ if ( !(s->uATARegFeature & UINT8_C(0x01))
+ || (s->uATARegFeature & ~UINT8_C(0x01)))
+ goto abort_cmd;
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, s, (s->uATARegNSectorHOB << 8 | s->uATARegNSector) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_NULL, ATAFN_SS_TRIM, false);
+ break;
+ default:
+ abort_cmd:
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ if (s->fATAPI)
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, 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 VBOXSTRICTRC ataIOPortWriteU8(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t val, uintptr_t iCtl)
+{
+ RT_NOREF(iCtl);
+ Log2(("%s: LUN#%d write addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].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) & ATA_SELECTED_IF_MASK) != pCtl->iSelectedIf)
+ {
+ /* select another drive */
+ uintptr_t const iSelectedIf = (val >> 4) & ATA_SELECTED_IF_MASK;
+ pCtl->iSelectedIf = (uint8_t)iSelectedIf;
+ /* 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[iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
+ && pCtl->aIfs[iSelectedIf].fIrqPending != pCtl->aIfs[iSelectedIf ^ 1].fIrqPending)
+ {
+ if (pCtl->aIfs[iSelectedIf].fIrqPending)
+ {
+ Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[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[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 */
+ uintptr_t iSelectedIf = pCtl->iSelectedIf & ATA_SELECTED_IF_MASK;
+ PATADEVSTATE pDev = &pCtl->aIfs[iSelectedIf];
+ if (iSelectedIf && !pDev->fPresent) /** @todo r=bird the iSelectedIf test here looks bogus... explain. */
+ break;
+#ifndef IN_RING3
+ /* Don't do anything complicated in GC */
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ ataUnsetIRQ(pDevIns, pCtl, &pCtl->aIfs[iSelectedIf]);
+ ataR3ParseCmd(pDevIns, pCtl, &pCtl->aIfs[iSelectedIf], &pThisCC->aCts[iCtl].aIfs[iSelectedIf], val);
+ break;
+#endif /* !IN_RING3 */
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+static VBOXSTRICTRC ataIOPortReadU8(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
+{
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t val;
+ bool fHOB;
+
+ /* Check if the guest is reading from a non-existent device. */
+ if (RT_LIKELY(s->fPresent))
+ { /* likely */ }
+ else
+ {
+ if (pCtl->iSelectedIf) /* Device 1 selected, Device 0 responding for it. */
+ {
+ Assert(pCtl->aIfs[0].fPresent);
+
+ /* When an ATAPI device 0 responds for non-present device 1, it generally
+ * returns zeros on reads. The Error register is an exception. See clause 7.1,
+ * table 16 in ATA-6 specification.
+ */
+ 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). */
+ {
+ /* Because device 1 has no way to tell if there is device 0, the behavior is the same
+ * as for an empty bus; see comments in ataIOPortReadEmptyBus(). Note that EFI (TianoCore)
+ * relies on this behavior when detecting devices.
+ */
+ *pu32 = ATA_EMPTY_BUS_DATA;
+ Log2(("%s: addr=%#x: LUN#%d not attached, val=%#02x\n", __FUNCTION__, addr, s->iLUN, *pu32));
+ return VINF_SUCCESS;
+ }
+ }
+
+ 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->fPresent)
+ 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].fPresent && !pCtl->aIfs[1].fPresent)
+ val = 0;
+ else
+ val = s->uATARegSelect;
+ break;
+ default:
+ case 7: /* primary status */
+ {
+ if (!s->fPresent)
+ 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(pDevIns, 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. */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PATACONTROLLERR3 pCtlR3 = &RT_SAFE_SUBSCRIPT(pThisCC->aCts, pCtl->iCtl);
+ RTThreadPoke(pCtlR3->hAsyncIOThread);
+# endif
+ Assert(fYield);
+ }
+ }
+
+ if (fYield)
+ {
+ STAM_REL_PROFILE_ADV_START(&s->StatStatusYields, a);
+ RTThreadYield();
+ STAM_REL_PROFILE_ADV_STOP(&s->StatStatusYields, a);
+ }
+ ASMNopPause();
+
+ ataR3LockEnter(pDevIns, 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(pDevIns, pCtl, s);
+ break;
+ }
+ }
+ Log2(("%s: LUN#%d addr=%#x val=%#04x\n", __FUNCTION__, s->iLUN, addr, val));
+ *pu32 = val;
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Read the Alternate status register. Does not affect interrupts.
+ */
+static uint32_t ataStatusRead(PATACONTROLLER pCtl, uint32_t uIoPortForLog)
+{
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t val;
+ RT_NOREF(uIoPortForLog);
+
+ Assert(pCtl->aIfs[0].fPresent || pCtl->aIfs[1].fPresent); /* Channel must not be empty. */
+ if (pCtl->iSelectedIf == 1 && !s->fPresent)
+ val = 0; /* Device 1 selected, Device 0 responding for it. */
+ else
+ val = s->uATARegStatus;
+ Log2(("%s: LUN#%d read addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN, uIoPortForLog, val));
+ return val;
+}
+
+static int ataControlWrite(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t val, uint32_t uIoPortForLog)
+{
+ RT_NOREF(uIoPortForLog);
+#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: LUN#%d write addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN, uIoPortForLog, 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",
+ pCtl->iCtl, 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(pDevIns, pCtl, &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(pDevIns, pCtl);
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetA\n", __FUNCTION__, pCtl->iCtl));
+ 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(pDevIns, 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__, pCtl->iCtl));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+ ataHCAsyncIOPutRequest(pDevIns, 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 & ATA_SELECTED_IF_MASK].fIrqPending)
+ {
+ if (!(val & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].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 (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, 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(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ PATADEVSTATE s;
+
+ s = &pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK];
+ 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. */
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ uint8_t status = s->uATARegStatus;
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PATADEVSTATER3 pDevR3 = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThisCC->aCts, pCtl->iCtl).aIfs, s->iDev);
+
+ ataSetStatusValue(pCtl, s, ATA_STAT_BUSY);
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ return;
+ ataSetStatusValue(pCtl, s, status);
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ Assert(iSourceSink == ATAFN_SS_NULL);
+# 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(pCtl, 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(pDevIns, pCtl, s);
+}
+
+
+DECLINLINE(void) ataHCPIOTransferFinish(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE 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__, pCtl->iCtl));
+ 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). */
+ ataSetStatus(pCtl, s, ATA_STAT_BUSY);
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY | ATA_STAT_DRQ);
+
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, 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(pCtl, s, ATA_STAT_DRQ);
+ ataSetStatus(pCtl, 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(pDevIns, pCtl);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log2(("%s: Ctl#%d: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__, pCtl->iCtl));
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pDevIns, 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 offStart The start offset (iIOBufferPIODataStart).
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECL_NO_INLINE(static, void) ataCopyPioData124Slow(PATADEVSTATE pIf, uint8_t *pbDst, const uint8_t *pbSrc,
+ uint32_t offStart, uint32_t cbCopy)
+{
+ uint32_t const offNext = offStart + cbCopy;
+ uint32_t const cbIOBuffer = RT_MIN(pIf->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+
+ if (offStart + cbCopy > cbIOBuffer)
+ {
+ Log(("%s: cbCopy=%#x offStart=%#x cbIOBuffer=%#x offNext=%#x (iIOBufferPIODataEnd=%#x)\n",
+ __FUNCTION__, cbCopy, offStart, cbIOBuffer, offNext, pIf->iIOBufferPIODataEnd));
+ if (offStart < cbIOBuffer)
+ cbCopy = 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->abIOBuffer.
+ *
+ * @param pIf The device interface to work with.
+ * @param pbDst The destination buffer.
+ * @param pbSrc The source buffer.
+ * @param offStart The start offset (iIOBufferPIODataStart).
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECLINLINE(void) ataCopyPioData124(PATADEVSTATE pIf, uint8_t *pbDst, const uint8_t *pbSrc, uint32_t offStart, uint32_t cbCopy)
+{
+ /*
+ * Quick bounds checking can be done by checking that the abIOBuffer 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);
+ if (RT_LIKELY( !(offStart & (cbCopy - 1))
+ && offStart + cbCopy <= RT_MIN(pIf->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)))
+ {
+ 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, offStart, cbCopy);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for primary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(cb == 2 || cb == 4); /* Writes to the data port may be 16-bit or 32-bit. */
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t const iIOBufferPIODataStart = RT_MIN(s->iIOBufferPIODataStart, sizeof(s->abIOBuffer));
+ uint32_t const iIOBufferPIODataEnd = RT_MIN(s->iIOBufferPIODataEnd, sizeof(s->abIOBuffer));
+
+ if (iIOBufferPIODataStart < iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ uint8_t *pbDst = &s->abIOBuffer[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 (iIOBufferPIODataStart + cb < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, 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 (iIOBufferPIODataStart + cb < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ else if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE) /* paranoia */
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n", __FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ }
+
+#else /* IN_RING 3*/
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ if (s->iIOBufferPIODataStart >= iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif /* IN_RING 3*/
+ }
+ else
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+
+ Log3(("%s: addr=%#x val=%.*Rhxs rc=%d\n", __FUNCTION__, offPort, cb, &u32, VBOXSTRICTRC_VAL(rc)));
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, offPort, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for primary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == 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;
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ AssertMsg(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE, ("%#x\n", s->uTxDir));
+ uint32_t const iIOBufferPIODataStart = RT_MIN(s->iIOBufferPIODataStart, sizeof(s->abIOBuffer));
+ uint32_t const iIOBufferPIODataEnd = RT_MIN(s->iIOBufferPIODataEnd, sizeof(s->abIOBuffer));
+ uint8_t const *pbSrc = &s->abIOBuffer[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 (iIOBufferPIODataStart + cbActual < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, 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 (iIOBufferPIODataStart + cbActual < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ else if ( s->cbTotalTransfer == 0
+ || s->iSourceSink != ATAFN_SS_NULL
+ || s->iIOBufferCur <= s->iIOBufferEnd)
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n",__FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_READ;
+ }
+
+#else /* IN_RING3 */
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ if (s->iIOBufferPIODataStart >= iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pDevIns, 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__, offPort, cb, pu32, VBOXSTRICTRC_VAL(rc)));
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, offPort, VBOXSTRICTRC_VAL(rc)));
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWINSTRING,
+ * Port I/O Handler for primary port range IN string operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortReadStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ VBOXSTRICTRC rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ uint32_t const offEnd = s->iIOBufferPIODataEnd;
+ if (offStart < offEnd)
+ {
+ /*
+ * 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 = (offEnd - 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;
+ uint32_t const offEndThisXfer = offStart + cbTransfer;
+ if ( offEndThisXfer <= RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)
+ && offStart < RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) /* paranoia */
+ && cbTransfer > 0)
+ {
+ /*
+ * Do the transfer.
+ */
+ uint8_t const *pbSrc = &s->abIOBuffer[offStart];
+ memcpy(pbDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x cb=%#x cbTransfer=%#x val=%.*Rhxd\n", __FUNCTION__, offPort, cb, cbTransfer, cbTransfer, pbSrc));
+ s->iIOBufferPIODataStart = offEndThisXfer;
+#ifdef IN_RING3
+ if (offEndThisXfer >= offEnd)
+ ataHCPIOTransferFinish(pDevIns, 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;
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &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;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUTSTRING,
+ * Port I/O Handler for primary port range OUT string operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWriteStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ VBOXSTRICTRC rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ uint32_t const offEnd = s->iIOBufferPIODataEnd;
+ Log3Func(("offStart=%#x offEnd=%#x *pcTransfers=%d cb=%d\n", offStart, offEnd, *pcTransfers, cb));
+ if (offStart < offEnd)
+ {
+ /*
+ * 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 = (offEnd - 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;
+ uint32_t const offEndThisXfer = offStart + cbTransfer;
+ if ( offEndThisXfer <= RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)
+ && offStart < RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) /* paranoia */
+ && cbTransfer > 0)
+ {
+ /*
+ * Do the transfer.
+ */
+ void *pvDst = &s->abIOBuffer[offStart];
+ memcpy(pvDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, offPort, cbTransfer, pvDst));
+ s->iIOBufferPIODataStart = offEndThisXfer;
+#ifdef IN_RING3
+ if (offEndThisXfer >= offEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif
+ *pcTransfers = cRequested - cAvailable;
+ }
+ else
+ Log2(("ataIOPortWriteStr1Data: DUMMY/Overflow!\n"));
+ }
+ else
+ {
+ Log2(("ataIOPortWriteStr1Data: DUMMY data (%#x bytes)\n", *pcTransfers * cb));
+ *pcTransfers = 0;
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &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(PATADEVSTATE 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 pDevIns The device instance.
+ * @param pCtl Controller for which to perform the transfer, shared bits.
+ * @param pCtlR3 The ring-3 controller state.
+ */
+static void ataR3DMATransfer(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATACONTROLLERR3 pCtlR3)
+{
+ uint8_t const iAIOIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ PATADEVSTATE s = &pCtl->aIfs[iAIOIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[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 = RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer));
+ iIOBufferEnd = RT_MIN(s->iIOBufferEnd, sizeof(s->abIOBuffer));
+ iIOBufferCur = RT_MIN(RT_MIN(s->iIOBufferCur, sizeof(s->abIOBuffer)), 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(pDevIns, 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
+ {
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysDesc, &DMADesc, sizeof(BMDMADesc));
+ GCPhysBuffer = RT_LE2H_U32(DMADesc.GCPhysBuffer);
+ cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
+ fLastDesc = RT_BOOL(cbBuffer & UINT32_C(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(RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur),
+ sizeof(s->abIOBuffer) - RT_MIN(iIOBufferCur, sizeof(s->abIOBuffer)));
+ 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)
+ PDMDevHlpPCIPhysWriteUser(pDevIns, GCPhysBuffer, &s->abIOBuffer[iIOBufferCur], cbXfer);
+ else
+ PDMDevHlpPCIPhysReadUser(pDevIns, GCPhysBuffer, &s->abIOBuffer[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(pDevIns, 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. */
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->cbTotalTransfer = cbTotalTransfer;
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ 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 = RT_MIN(cbElementaryTransfer, sizeof(s->abIOBuffer));
+ }
+ pCtl->fRedo = fRedo;
+ }
+ else
+ {
+ /* This forces the loop to exit immediately. */
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+ if (RT_UNLIKELY(fRedo))
+ break;
+ }
+ }
+
+ if (RT_UNLIKELY(fRedo))
+ break;
+
+ /* end of transfer */
+ if (!cbTotalTransfer || fLastDesc)
+ break;
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: ABORT DMA%s\n", pCtl->iCtl, pCtl->fReset ? " due to RESET" : ""));
+ if (!pCtl->fReset)
+ ataR3DMATransferStop(s);
+ /* This forces the loop to exit immediately. */
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+ }
+
+ ataR3LockEnter(pDevIns, 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 pDevIns The device instance.
+ * @param pCtl The shared controller state.
+ * @param pCtlR3 The ring-3 controller state.
+ */
+static void ataR3AsyncSignalIdle(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATACONTROLLERR3 pCtlR3)
+{
+ /*
+ * Take the lock here and recheck the idle indicator to avoid
+ * unnecessary work and racing ataR3WaitForAsyncIOIsIdle.
+ */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if ( pCtlR3->fSignalIdle
+ && ataR3AsyncIOIsIdle(pDevIns, pCtl, false /*fStrict*/))
+ {
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ RTThreadUserSignal(pCtlR3->hAsyncIOThread); /* for ataR3Construct/ataR3ResetCommon. */
+ }
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &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)
+{
+ PATACONTROLLERR3 const pCtlR3 = (PATACONTROLLERR3)pvUser;
+ PPDMDEVINSR3 const pDevIns = pCtlR3->pDevIns;
+ PATASTATE const pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ uintptr_t const iCtl = pCtlR3 - &pThisCC->aCts[0];
+ PATACONTROLLER const pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, iCtl);
+ int rc = VINF_SUCCESS;
+ uint64_t u64TS = 0; /* shut up gcc */
+ uint64_t uWait;
+ const ATARequest *pReq;
+ RT_NOREF(hThreadSelf);
+ Assert(pCtl->iCtl == pCtlR3->iCtl);
+
+ pReq = NULL;
+ pCtl->fChainedTransfer = false;
+ while (!pCtlR3->fShutdown)
+ {
+ /* Keep this thread from doing anything as long as EMT is suspended. */
+ while (pCtl->fRedoIdle)
+ {
+ if (pCtlR3->fSignalIdle)
+ ataR3AsyncSignalIdle(pDevIns, pCtl, pCtlR3);
+ rc = RTSemEventWait(pCtlR3->hSuspendIOSem, 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) || pCtlR3->fShutdown)
+ break;
+
+ pCtl->fRedoIdle = false;
+ }
+
+ /* Wait for work. */
+ while (pReq == NULL)
+ {
+ if (pCtlR3->fSignalIdle)
+ ataR3AsyncSignalIdle(pDevIns, pCtl, pCtlR3);
+ rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, 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(pCtlR3->fShutdown))
+ break;
+
+ pReq = ataR3AsyncIOGetCurrentRequest(pDevIns, pCtl);
+ }
+
+ if (RT_FAILURE(rc) || pCtlR3->fShutdown)
+ break;
+
+ if (pReq == NULL)
+ continue;
+
+ ATAAIO ReqType = pReq->ReqType;
+
+ Log2(("%s: Ctl#%d: state=%d, req=%d\n", __FUNCTION__, pCtl->iCtl, 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(pDevIns, 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(pDevIns, 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:
+ {
+ uint8_t const iIf = pReq->u.t.iIf & ATA_SELECTED_IF_MASK;
+ pCtl->iAIOIf = iIf;
+ PATADEVSTATE s = &pCtl->aIfs[iIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[iIf];
+
+ 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;
+
+ uint8_t const iBeginTransfer = s->iBeginTransfer;
+ if ( iBeginTransfer != ATAFN_BT_NULL
+ && iBeginTransfer < RT_ELEMENTS(g_apfnBeginTransFuncs))
+ {
+ Log2(("%s: Ctl#%d: calling begin transfer function\n", __FUNCTION__, pCtl->iCtl));
+ g_apfnBeginTransFuncs[iBeginTransfer](pCtl, s);
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ if (s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ Assert(iBeginTransfer == ATAFN_BT_NULL);
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->iIOBufferEnd = s->cbTotalTransfer;
+ }
+ s->iIOBufferCur = 0;
+
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, pCtl->iCtl));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ 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__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, pReq);
+ break;
+ }
+ }
+ else
+ {
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ 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(pCtl, 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__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, 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(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataHCPIOTransfer(pDevIns, pCtl);
+ Assert(!pCtl->fRedo);
+ if (s->fATAPITransfer || s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ ataHCSetIRQ(pDevIns, pCtl, 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(pDevIns, pCtl);
+ Assert(!pCtl->fRedo);
+ if (!s->fATAPITransfer)
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ break;
+ }
+
+ case ATA_AIO_DMA:
+ {
+ BMDMAState *bm = &pCtl->BmDma;
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK];
+ 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(pDevIns, pCtl, pCtlR3);
+
+ if (RT_UNLIKELY(pCtl->fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo DMA operation\n", pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataDMARequest);
+ break;
+ }
+
+ /* The infamous delay IRQ hack. */
+ if ( iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
+ && s->cbTotalTransfer == 0
+ && pCtl->msDelayIRQ)
+ {
+ /* 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(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+
+ ataUnsetStatus(pCtl, 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__, pCtl->iCtl, s->uATARegNSector));
+ s->fATAPITransfer = false;
+ }
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ break;
+ }
+
+ case ATA_AIO_PIO:
+ {
+ uint8_t const iIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ pCtl->iAIOIf = iIf;
+ PATADEVSTATE s = &pCtl->aIfs[iIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[iIf];
+
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, pCtl->iCtl));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo PIO operation\n", pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataPIORequest);
+ break;
+ }
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ /* Continue a previously started transfer. */
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY);
+ ataSetStatus(pCtl, 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(pDevIns, pCtl);
+ ataHCSetIRQ(pDevIns, pCtl, 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
+ {
+ /* The infamous delay IRQ hack. */
+ if (RT_UNLIKELY(pCtl->msDelayIRQ))
+ {
+ /* Various antique guests have buggy disk drivers silently
+ * assuming that disk operations take a relatively long time.
+ * Work around such bugs by holding off interrupts a bit.
+ */
+ Log(("%s: delay IRQ hack (PIO)\n", __FUNCTION__));
+ ataR3LockLeave(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pDevIns, pCtl);
+ if ( !pCtl->fChainedTransfer
+ && !s->fATAPITransfer
+ && s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ break;
+ }
+
+ case ATA_AIO_RESET_ASSERTED:
+ pCtl->uAsyncIOState = ATA_AIO_RESET_CLEARED;
+ ataHCPIOTransferStop(pDevIns, pCtl, &pCtl->aIfs[0]);
+ ataHCPIOTransferStop(pDevIns, pCtl, &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", pCtl->iCtl));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ ataR3SetSignature(&pCtl->aIfs[i]);
+ if (pCtl->aIfs[i].fATAPI)
+ ataSetStatusValue(pCtl, &pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(pCtl, &pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
+ }
+ 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. */
+ PATADEVSTATE s = &pCtl->aIfs[pReq->u.a.iIf & ATA_SELECTED_IF_MASK];
+
+ 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(pDevIns, pCtl, s);
+ ataR3DeviceDiag(pCtl, s);
+ }
+ else
+ {
+ /* Stop any pending DMA transfer. */
+ s->fDMA = false;
+ ataHCPIOTransferStop(pDevIns, pCtl, s);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
+ ataSetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
+ }
+
+ ataR3AsyncIORemoveCurrentRequest(pDevIns, pCtl, ReqType);
+ pReq = ataR3AsyncIOGetCurrentRequest(pDevIns, 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;
+ uintptr_t const iAIOIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ Log(("%s: Ctl#%d: LUN#%d finished I/O transaction in %d microseconds\n",
+ __FUNCTION__, pCtl->iCtl, pCtl->aIfs[iAIOIf].iLUN, (uint32_t)(uWait)));
+ /* Mark command as finished. */
+ pCtl->aIfs[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[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[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[iAIOIf].abATAPICmd[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(pDevIns, pCtl);
+ }
+
+ /* Signal the ultimate idleness. */
+ RTThreadUserSignal(pCtlR3->hAsyncIOThread);
+ if (pCtlR3->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+
+ /* Cleanup the state. */
+ /* Do not destroy request lock yet, still needed for proper shutdown. */
+ pCtlR3->fShutdown = false;
+
+ Log2(("%s: Ctl#%d: return %Rrc\n", __FUNCTION__, pCtl->iCtl, rc));
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+static uint32_t ataBMDMACmdReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Cmd;
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+
+static void ataBMDMACmdWriteB(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF(pDevIns, 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__, pCtl->iCtl));
+ 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 & ATA_SELECTED_IF_MASK].fDMA
+ || (uOldBmDmaStatus & BM_STATUS_DMAING))
+ return;
+
+ if (pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK].uATARegStatus & ATA_STAT_DRQ)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, 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_NOREF(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_NOREF(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_NOREF(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_NOREF(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_NOREF(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_NOREF(addr);
+ pCtl->BmDma.GCPhysAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.GCPhysAddr);
+}
+
+/** Helper for ataBMDMAIOPortRead and ataBMDMAIOPortWrite. */
+#define VAL(port, size) ( ((port) & BM_DMA_CTL_IOPORTS_MASK) | ((size) << BM_DMA_CTL_IOPORTS_SHIFT) )
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for bus-master DMA IN operations - both controllers.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (offPort >> BM_DMA_CTL_IOPORTS_SHIFT));
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ switch (VAL(offPort, cb))
+ {
+ case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, offPort); break;
+ case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, offPort); break;
+ case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, offPort); break;
+ case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, offPort); break;
+ case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, offPort); break;
+ case VAL(0, 4):
+ /* The SCO OpenServer tries to read 4 bytes starting from offset 0. */
+ *pu32 = ataBMDMACmdReadB(pCtl, offPort) | (ataBMDMAStatusReadB(pCtl, offPort) << 16);
+ break;
+ default:
+ ASSERT_GUEST_MSG_FAILED(("Unsupported read from port %x size=%d\n", offPort, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ break;
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for bus-master DMA OUT operations - both controllers.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (offPort >> BM_DMA_CTL_IOPORTS_SHIFT));
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ switch (VAL(offPort, cb))
+ {
+ case VAL(0, 1):
+#ifdef IN_RC
+ if (u32 & BM_CMD_START)
+ {
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ break;
+ }
+#endif
+ ataBMDMACmdWriteB(pDevIns, pCtl, offPort, u32);
+ break;
+ case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, offPort, u32); break;
+ case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, offPort, u32); break;
+ case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, offPort, u32); break;
+ case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, offPort, u32); break;
+ default:
+ ASSERT_GUEST_MSG_FAILED(("Unsupported write to port %x size=%d val=%x\n", offPort, cb, u32));
+ break;
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+#undef VAL
+
+#ifdef IN_RING3
+
+/* -=-=-=-=-=- ATASTATE::IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ataR3Status_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PATASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, ATASTATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- ATASTATE::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)
+{
+ if (iLUN < 4)
+ {
+ PATASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, ATASTATER3, ILeds);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PATASTATE);
+ 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)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pIfR3->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pIfR3->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pIfR3->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)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IPort);
+ PPDMDEVINS pDevIns = pIfR3->pDevIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pIfR3->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/* -=-=-=-=-=- Wrappers -=-=-=-=-=- */
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for OUT operations on unpopulated IDE channels.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWriteEmptyBus(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, offPort, u32, cb);
+
+#ifdef VBOX_STRICT
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ Assert((uintptr_t)pvUser < 2);
+ Assert(!pCtl->aIfs[0].fPresent && !pCtl->aIfs[1].fPresent);
+#endif
+
+ /* This is simply a black hole, writes on unpopulated IDE channels elicit no response. */
+ LogFunc(("Empty bus: Ignoring write to port %x val=%x size=%d\n", offPort, u32, cb));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for IN operations on unpopulated IDE channels.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortReadEmptyBus(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pDevIns, offPort, pvUser);
+
+#ifdef VBOX_STRICT
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ Assert((uintptr_t)pvUser < 2);
+ Assert(cb <= 4);
+ Assert(!pCtl->aIfs[0].fPresent && !pCtl->aIfs[1].fPresent);
+#endif
+
+ /*
+ * Reads on unpopulated IDE channels behave in a unique way. Newer ATA specifications
+ * mandate that the host must have a pull-down resistor on signal DD7. As a consequence,
+ * bit 7 is always read as zero. This greatly aids in ATA device detection because
+ * the empty bus does not look to the host like a permanently busy drive, and no long
+ * timeouts (on the order of 30 seconds) are needed.
+ *
+ * The response is entirely static and does not require any locking or other fancy
+ * stuff. Breaking it out simplifies the I/O handling for non-empty IDE channels which
+ * is quite complicated enough already.
+ */
+ *pu32 = ATA_EMPTY_BUS_DATA_32 >> ((4 - cb) * 8);
+ LogFunc(("Empty bus: port %x val=%x size=%d\n", offPort, *pu32, cb));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for primary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ uintptr_t iCtl = (uintptr_t)pvUser % RT_ELEMENTS(pThis->aCts);
+ PATACONTROLLER pCtl = &pThis->aCts[iCtl];
+
+ Assert((uintptr_t)pvUser < 2);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &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", offPort, u32, cb));
+
+ rc = ataIOPortWriteU8(pDevIns, pCtl, offPort, u32, iCtl);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for primary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+
+ Assert((uintptr_t)pvUser < 2);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &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(pDevIns, pCtl, offPort, 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", offPort, cb));
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for secondary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ int rc;
+
+ Assert((uintptr_t)pvUser < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ rc = ataControlWrite(pDevIns, pCtl, u32, offPort);
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ else
+ {
+ Log(("ataIOPortWrite2: ignoring write to port %x+%x size=%d!\n", offPort, pCtl->IOPortBase2, cb));
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for secondary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ int rc;
+
+ Assert((uintptr_t)pvUser < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ *pu32 = ataStatusRead(pCtl, offPort);
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ else
+ {
+ Log(("ataIOPortRead2: ignoring read from port %x+%x size=%d!\n", offPort, pCtl->IOPortBase2, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * 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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ 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];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[iController];
+
+ unsigned iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ PATADEVSTATE pIf = &pCtl->aIfs[iInterface];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[iInterface];
+
+ /*
+ * Zero some important members.
+ */
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIfR3->pDrvMount = NULL;
+ pIf->fPresent = false;
+
+ /*
+ * In case there was a medium inserted.
+ */
+ ataR3MediumRemoved(pIf);
+}
+
+
+/**
+ * Configure a LUN.
+ *
+ * @returns VBox status code.
+ * @param pIf The ATA unit state, shared bits.
+ * @param pIfR3 The ATA unit state, ring-3 bits.
+ */
+static int ataR3ConfigLun(PATADEVSTATE pIf, PATADEVSTATER3 pIfR3)
+{
+ /*
+ * Query Block, Bios and Mount interfaces.
+ */
+ pIfR3->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pIfR3->pDrvBase, PDMIMEDIA);
+ if (!pIfR3->pDrvMedia)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ pIfR3->pDrvMount = PDMIBASE_QUERY_INTERFACE(pIfR3->pDrvBase, PDMIMOUNT);
+ pIf->fPresent = true;
+
+ /*
+ * Validate type.
+ */
+ PDMMEDIATYPE enmType = pIfR3->pDrvMedia->pfnGetType(pIfR3->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)
+ && !pIfR3->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 && pIfR3->pDrvMedia->pfnSendCmd != NULL;
+
+ /*
+ * Allocate I/O buffer.
+ */
+ if (pIf->fATAPI)
+ pIf->cbSector = 2048; /* Not required for ATAPI, one medium can have multiple sector sizes. */
+ else
+ {
+ pIf->cbSector = pIfR3->pDrvMedia->pfnGetSectorSize(pIfR3->pDrvMedia);
+ AssertLogRelMsgReturn(pIf->cbSector > 0 && pIf->cbSector <= ATA_MAX_SECTOR_SIZE,
+ ("Unsupported sector size on LUN#%u: %#x (%d)\n", pIf->iLUN, pIf->cbSector, pIf->cbSector),
+ VERR_OUT_OF_RANGE);
+ }
+
+ if (pIf->cbIOBuffer)
+ {
+ /* Buffer is (probably) already allocated. Validate the fields,
+ * because memory corruption can also overwrite pIf->cbIOBuffer. */
+ if (pIf->fATAPI)
+ AssertLogRelReturn(pIf->cbIOBuffer == _128K, VERR_BUFFER_OVERFLOW);
+ else
+ AssertLogRelReturn(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * pIf->cbSector, VERR_BUFFER_OVERFLOW);
+ }
+ else
+ {
+ if (pIf->fATAPI)
+ pIf->cbIOBuffer = _128K;
+ else
+ pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * pIf->cbSector;
+ }
+ AssertCompile(_128K <= ATA_MAX_IO_BUFFER_SIZE);
+ AssertCompileSize(pIf->abIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+ AssertLogRelMsgReturn(pIf->cbIOBuffer <= ATA_MAX_IO_BUFFER_SIZE,
+ ("LUN#%u: cbIOBuffer=%#x (%u)\n", pIf->iLUN, pIf->cbIOBuffer, pIf->cbIOBuffer),
+ VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Init geometry (only for non-CD/DVD media).
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cRegions = pIfR3->pDrvMedia->pfnGetRegionCount(pIfR3->pDrvMedia);
+ pIf->cTotalSectors = 0;
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ rc = pIfR3->pDrvMedia->pfnQueryRegionProperties(pIfR3->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 = pIfR3->pDrvMedia->pfnBiosGetPCHSGeometry(pIfR3->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. */
+ pIfR3->pDrvMedia->pfnBiosSetPCHSGeometry(pIfR3->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 (pIfR3->pDrvMedia->pfnDiscard)
+ LogRel(("PIIX3 ATA: LUN#%d: TRIM enabled\n", pIf->iLUN));
+ }
+ /* Initialize the translated geometry. */
+ pIf->XCHSGeometry = pIf->PCHSGeometry;
+
+ /*
+ * 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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PIIX3IDE: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Locate the controller and stuff.
+ */
+ unsigned const 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];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[iController];
+
+ unsigned const iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ PATADEVSTATE pIf = &pCtl->aIfs[iInterface];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[iInterface];
+
+ /* the usual paranoia */
+ AssertRelease(!pIfR3->pDrvBase);
+ AssertRelease(!pIfR3->pDrvMedia);
+ Assert(pIf->iLUN == iLUN);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ int rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIfR3->IBase, &pIfR3->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pIf, pIfR3);
+ /*
+ * 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))
+ {
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIfR3->pDrvMount = NULL;
+ pIf->fPresent = false;
+ }
+ return rc;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Resume(PPDMDEVINS pDevIns)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ Log(("%s:\n", __FUNCTION__));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].fRedo && pThis->aCts[i].fRedoIdle)
+ {
+ int rc = RTSemEventSignal(pThisCC->aCts[i].hSuspendIOSem);
+ 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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ bool fRc = ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/);
+ if (!fRc)
+ {
+ /* Make it signal PDM & itself when its done */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].AsyncIORequestLock, rcLock);
+
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, true);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+
+ fRc = ataR3AsyncIOIsIdle(pDevIns, &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(&pThisCC->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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ RT_NOREF(pSSM);
+
+ /* sanity - the suspend notification will wait on the async stuff. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ AssertLogRelMsgReturn(ataR3AsyncIOIsIdle(pDevIns, &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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(uPass);
+
+ pHlp->pfnSSMPutU8(pSSM, (uint8_t)pThis->enmChipset);
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, true); /* For controller enabled / disabled. */
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, pThisCC->aCts[i].aIfs[j].pDrvBase != NULL);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szSerialNumber);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szFirmwareRevision);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szModelNumber);
+ }
+ }
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @copydoc FNSSMDEVSAVEEXEC
+ */
+static DECLCALLBACK(int) ataR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ ataR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].iSelectedIf);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].iAIOIf);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].uAsyncIOState);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fChainedTransfer);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fReset);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedo);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedoIdle);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedoDMALastDesc);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysFirstDMADesc);
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysLastDMADesc);
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysRedoDMABuffer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fLBA48);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPI);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fIrqPending);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].cMultSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cCylinders);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cHeads);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aCts[i].aIfs[j].cTotalSectors);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeature);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegError);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSector);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSector);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCyl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCyl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSelect);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegStatus);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegCommand);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATATransferMode);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uTxDir);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].iBeginTransfer);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].iSourceSink);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fDMA);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPITransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferCur);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iCurLBA);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbATAPISector);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPICmd, sizeof(pThis->aCts[i].aIfs[j].abATAPICmd));
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].MediaEventStatus);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ pHlp->pfnSSMPutMem(pSSM, pThis->aCts[i].aIfs[j].abIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ }
+ }
+
+ return pHlp->pfnSSMPutU32(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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ uint32_t u32;
+
+ if ( uVersion != ATA_SAVED_STATE_VERSION
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA
+ && 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 = pHlp->pfnSSMGetU8(pSSM, &u8Type);
+ AssertRCReturn(rc, rc);
+ if ((CHIPSET)u8Type != pThis->enmChipset)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: enmChipset - saved=%u config=%u"), u8Type, pThis->enmChipset);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ bool fEnabled;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fEnabled);
+ AssertRCReturn(rc, rc);
+ if (!fEnabled)
+ return pHlp->pfnSSMSetCfgError(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];
+ ATADEVSTATER3 const *pIfR3 = &pThisCC->aCts[i].aIfs[j];
+
+ bool fInUse;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pIfR3->pDrvBase != NULL))
+ return pHlp->pfnSSMSetCfgError(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 = pHlp->pfnSSMGetStrZ(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 = pHlp->pfnSSMGetStrZ(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 = pHlp->pfnSSMGetStrZ(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 ATASTATE structure
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ /* integrity check */
+ if (!ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false))
+ {
+ AssertMsgFailed(("Async I/O for controller %d is active\n", i));
+ return VERR_INTERNAL_ERROR_4;
+ }
+
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].iSelectedIf);
+ AssertRCReturn(rc, rc);
+ AssertLogRelMsgStmt(pThis->aCts[i].iSelectedIf == (pThis->aCts[i].iSelectedIf & ATA_SELECTED_IF_MASK),
+ ("iSelectedIf = %d\n", pThis->aCts[i].iSelectedIf),
+ pThis->aCts[i].iSelectedIf &= ATA_SELECTED_IF_MASK);
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].iAIOIf);
+ AssertRCReturn(rc, rc);
+ AssertLogRelMsgStmt(pThis->aCts[i].iAIOIf == (pThis->aCts[i].iAIOIf & ATA_SELECTED_IF_MASK),
+ ("iAIOIf = %d\n", pThis->aCts[i].iAIOIf),
+ pThis->aCts[i].iAIOIf &= ATA_SELECTED_IF_MASK);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].uAsyncIOState);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fChainedTransfer);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fReset);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedo);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedoIdle);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedoDMALastDesc);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysFirstDMADesc);
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysLastDMADesc);
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysRedoDMABuffer);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fLBA48);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPI);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fIrqPending);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].cMultSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cCylinders);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cHeads);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aCts[i].aIfs[j].cTotalSectors);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeature);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegError);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSector);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSector);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCyl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCyl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSelect);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegStatus);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegCommand);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATATransferMode);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uTxDir);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].iBeginTransfer);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].iSourceSink);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fDMA);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPITransfer);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ pHlp->pfnSSMGetU32(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;
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferCur);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iCurLBA);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cbATAPISector);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPICmd, sizeof(pThis->aCts[i].aIfs[j].abATAPICmd));
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ pHlp->pfnSSMGetMem(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;
+ pHlp->pfnSSMGetU8(pSSM, &uATAPISenseKey);
+ pHlp->pfnSSMGetU8(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 */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aCts[i].aIfs[j].MediaEventStatus);
+ else
+ pThis->aCts[i].aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+
+ uint32_t cbIOBuffer = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbIOBuffer);
+ AssertRCReturn(rc, rc);
+
+ if ( (uVersion <= ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA)
+ && !pThis->aCts[i].aIfs[j].fATAPI)
+ {
+ pThis->aCts[i].aIfs[j].iCurLBA = ataR3GetSector(&pThis->aCts[i].aIfs[j]);
+ }
+
+ if (cbIOBuffer)
+ {
+ if (cbIOBuffer <= sizeof(pThis->aCts[i].aIfs[j].abIOBuffer))
+ {
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer != cbIOBuffer)
+ LogRel(("ATA: %u/%u: Restoring cbIOBuffer=%u; constructor set up %u!\n", i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer));
+ pThis->aCts[i].aIfs[j].cbIOBuffer = cbIOBuffer;
+ pHlp->pfnSSMGetMem(pSSM, pThis->aCts[i].aIfs[j].abIOBuffer, cbIOBuffer);
+ }
+ else
+ {
+ LogRel(("ATA: %u/%u: Restoring cbIOBuffer=%u, only prepared %u!\n", i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer));
+ if (pHlp->pfnSSMHandleGetAfter(pSSM) != SSMAFTER_DEBUG_IT)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("ATA: %u/%u: Restoring cbIOBuffer=%u, only prepared %u"),
+ i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer);
+
+ /* skip the buffer if we're loading for the debugger / animator. */
+ pHlp->pfnSSMSkip(pSSM, cbIOBuffer);
+ }
+ }
+ else
+ AssertLogRelMsgStmt(pThis->aCts[i].aIfs[j].cbIOBuffer == 0,
+ ("ATA: %u/%u: cbIOBuffer=%u restoring zero!\n", i, j, pThis->aCts[i].aIfs[j].cbIOBuffer),
+ pThis->aCts[i].aIfs[j].cbIOBuffer = 0);
+ }
+ }
+ if (uVersion <= ATA_SAVED_STATE_VERSION_VBOX_30)
+ PDMDEVHLP_SSM_GET_ENUM8_RET(pHlp, pSSM, pThis->enmChipset, CHIPSET);
+
+ rc = pHlp->pfnSSMGetU32(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.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ ataR3SuspendOrPowerOff(pDevIns);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+
+ if (!ataR3AllAsyncIOIsIdle(pDevIns))
+ return false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].lock, rcLock);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ ataR3ResetDevice(pDevIns, &pThis->aCts[i], &pThis->aCts[i].aIfs[j]);
+
+ PDMDevHlpCritSectLeave(pDevIns, &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)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].lock, rcLock);
+
+ 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 = (pThisCC->aCts[i].aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
+ | (pThisCC->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(pDevIns, &pThis->aCts[i]);
+ Log2(("%s: Ctl#%d: message to async I/O thread, reset controller\n", __FUNCTION__, i));
+ ataHCAsyncIOPutRequest(pDevIns, &pThis->aCts[i], &g_ataResetARequest);
+ ataHCAsyncIOPutRequest(pDevIns, &pThis->aCts[i], &g_ataResetCRequest);
+
+ PDMDevHlpCritSectLeave(pDevIns, &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 (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].AsyncIORequestLock, rc);
+
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, true);
+ rc = RTThreadUserReset(pThisCC->aCts[i].hAsyncIOThread);
+ AssertRC(rc);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+ AssertRC(rc);
+
+ if (!ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/))
+ {
+ rc = RTThreadUserWait(pThisCC->aCts[i].hAsyncIOThread, 30*1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ rc = RTThreadUserWait(pThisCC->aCts[i].hAsyncIOThread, 1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ rcRet = rc;
+ }
+ }
+ }
+ ASMAtomicWriteBool(&pThisCC->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*/);
+}
+
+/**
+ * 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)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ int rc;
+
+ Log(("ataR3Destruct\n"));
+
+ /*
+ * Tell the async I/O threads to terminate.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteU32(&pThisCC->aCts[i].fShutdown, true);
+ rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aCts[i].hAsyncIOSem);
+ AssertRC(rc);
+ rc = RTSemEventSignal(pThisCC->aCts[i].hSuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+
+ /*
+ * Wait for the threads to terminate before destroying their resources.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThisCC->aCts[i].hAsyncIOThread, 30000 /* 30 s*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThisCC->aCts[i].hAsyncIOThread = 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 (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->aCts[i].AsyncIORequestLock))
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+ if (pThis->aCts[i].hAsyncIOSem != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pThis->aCts[i].hAsyncIOSem);
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ }
+ if (pThisCC->aCts[i].hSuspendIOSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThisCC->aCts[i].hSuspendIOSem);
+ pThisCC->aCts[i].hSuspendIOSem = NIL_RTSEMEVENT;
+ }
+
+ /* try one final time */
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThisCC->aCts[i].hAsyncIOThread, 1 /*ms*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->aCts[i].hAsyncIOThread = 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 (pThisCC->aCts[i].aIfs[iIf].pTrackList)
+ {
+ ATAPIPassthroughTrackListDestroy(pThisCC->aCts[i].aIfs[iIf].pTrackList);
+ pThisCC->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 = pDevIns->pHlpR3->pfnCFGMQueryStringDef(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)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ PPDMIBASE pBase;
+ int rc;
+ uint32_t msDelayIRQ;
+
+ Assert(iInstance == 0);
+
+ /*
+ * Initialize NIL handle values (for the destructor).
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].iCtl = i;
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ pThis->aCts[i].hIoPorts1First = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPorts1Other = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPorts2 = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPortsEmpty1 = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPortsEmpty2 = NIL_IOMIOPORTHANDLE;
+
+ pThisCC->aCts[i].iCtl = i;
+ pThisCC->aCts[i].hSuspendIOSem = NIL_RTSEMEVENT;
+ pThisCC->aCts[i].hAsyncIOThread = NIL_RTTHREAD;
+ }
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQDelay|Type", "PrimaryMaster|PrimarySlave|SecondaryMaster|SecondarySlave");
+
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "IRQDelay", &msDelayIRQ, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 configuration error: failed to read IRQDelay as integer"));
+ Log(("%s: msDelayIRQ=%d\n", __FUNCTION__, msDelayIRQ));
+ Assert(msDelayIRQ < 50);
+
+ CHIPSET enmChipset = CHIPSET_PIIX3;
+ rc = ataR3ControllerFromCfg(pDevIns, pCfg, &enmChipset);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->enmChipset = enmChipset;
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* Status LUN. */
+ pThisCC->IBase.pfnQueryInterface = ataR3Status_QueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = ataR3Status_QueryStatusLed;
+
+ /* PCI configuration space. */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+ PDMPciDevSetVendorId(pPciDev, 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 (enmChipset)
+ {
+ case CHIPSET_ICH6:
+ PDMPciDevSetDeviceId(pPciDev, 0x269e); /* ICH6 IDE */
+ /** @todo do we need it? Do we need anything else? */
+ PDMPciDevSetByte(pPciDev, 0x48, 0x00); /* UDMACTL */
+ PDMPciDevSetByte(pPciDev, 0x4A, 0x00); /* UDMATIM */
+ PDMPciDevSetByte(pPciDev, 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);
+ PDMPciDevSetByte(pPciDev, 0x54, u16Config & 0xff);
+ PDMPciDevSetByte(pPciDev, 0x55, u16Config >> 8);
+ }
+ break;
+ case CHIPSET_PIIX4:
+ PDMPciDevSetDeviceId(pPciDev, 0x7111); /* PIIX4 IDE */
+ PDMPciDevSetRevisionId(pPciDev, 0x01); /* PIIX4E */
+ PDMPciDevSetByte(pPciDev, 0x48, 0x00); /* UDMACTL */
+ PDMPciDevSetByte(pPciDev, 0x4A, 0x00); /* UDMATIM */
+ PDMPciDevSetByte(pPciDev, 0x4B, 0x00);
+ break;
+ case CHIPSET_PIIX3:
+ PDMPciDevSetDeviceId(pPciDev, 0x7010); /* PIIX3 IDE */
+ break;
+ default:
+ AssertMsgFailed(("Unsupported IDE chipset type: %d\n", enmChipset));
+ }
+
+ /** @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.
+ */
+ PDMPciDevSetWord(pPciDev, 0x40, 0x8000); /* enable IDE0 */
+ PDMPciDevSetWord(pPciDev, 0x42, 0x8000); /* enable IDE1 */
+
+ PDMPciDevSetCommand( pPciDev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS | PCI_COMMAND_BUSMASTER);
+ PDMPciDevSetClassProg( pPciDev, 0x8a); /* programming interface = PCI_IDE bus-master is supported */
+ PDMPciDevSetClassSub( pPciDev, 0x01); /* class_sub = PCI_IDE */
+ PDMPciDevSetClassBase( pPciDev, 0x01); /* class_base = PCI_mass_storage */
+ PDMPciDevSetHeaderType(pPciDev, 0x00);
+
+ pThisCC->pDevIns = pDevIns;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThisCC->aCts[i].pDevIns = pDevIns;
+ pThisCC->aCts[i].iCtl = i;
+ pThis->aCts[i].iCtl = i;
+ pThis->aCts[i].msDelayIRQ = msDelayIRQ;
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ PATADEVSTATE pIf = &pThis->aCts[i].aIfs[j];
+ PATADEVSTATER3 pIfR3 = &pThisCC->aCts[i].aIfs[j];
+
+ pIfR3->iLUN = pIf->iLUN = i * RT_ELEMENTS(pThis->aCts) + j;
+ pIfR3->iCtl = pIf->iCtl = i;
+ pIfR3->iDev = pIf->iDev = j;
+ pIfR3->pDevIns = pDevIns;
+ pIfR3->IBase.pfnQueryInterface = ataR3QueryInterface;
+ pIfR3->IMountNotify.pfnMountNotify = ataR3MountNotify;
+ pIfR3->IMountNotify.pfnUnmountNotify = ataR3UnmountNotify;
+ pIfR3->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, pPciDev, 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"));
+
+ /* Region #4: I/O ports for the two bus-master DMA controllers. */
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 4 /*iPciRegion*/, 0x10 /*cPorts*/,
+ ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL /*pvUser*/, "ATA Bus Master DMA",
+ NULL /*paExtDescs*/, &pThis->hIoPortsBmDma);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register stats, create critical sections.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ PATADEVSTATE 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, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThisCC->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];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[i];
+
+ /*
+ * Start the worker thread.
+ */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pCtl->hAsyncIOSem);
+ AssertLogRelRCReturn(rc, rc);
+ rc = RTSemEventCreate(&pCtlR3->hSuspendIOSem);
+ AssertLogRelRCReturn(rc, rc);
+
+ ataR3AsyncIOClearRequests(pDevIns, pCtl);
+ rc = RTThreadCreateF(&pCtlR3->hAsyncIOThread, ataR3AsyncIOThread, pCtlR3, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ATA-%u", i);
+ AssertLogRelRCReturn(rc, rc);
+ Assert( pCtlR3->hAsyncIOThread != NIL_RTTHREAD && pCtl->hAsyncIOSem != NIL_SUPSEMEVENT
+ && pCtlR3->hSuspendIOSem != NIL_RTSEMEVENT && PDMDevHlpCritSectIsInitialized(pDevIns, &pCtl->AsyncIORequestLock));
+ Log(("%s: controller %d AIO thread id %#x; sem %p susp_sem %p\n", __FUNCTION__, i, pCtlR3->hAsyncIOThread, pCtl->hAsyncIOSem, pCtlR3->hSuspendIOSem));
+
+ 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.
+ */
+ PATADEVSTATE pIf = &pCtl->aIfs[j];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[j];
+
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIfR3->IBase, &pIfR3->pDrvBase, s_apszDescs[i][j]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pIf, pIfR3);
+ 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 (pIfR3->pDrvMedia)
+ rc = pIfR3->pDrvMedia->pfnGetUuid(pIfR3->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 = pHlp->pfnCFGMGetChild(pCfg, s_apszCFGMKeys[i][j]);
+ rc = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryStringDef(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 = pHlp->pfnCFGMQueryBoolDef(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)
+ {
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIf->cbIOBuffer = 0;
+ pIf->fPresent = false;
+ 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;
+ }
+ }
+
+ /*
+ * 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++)
+ {
+ Assert(pThis->aCts[i].aIfs[0].fPresent == (pThisCC->aCts[i].aIfs[0].pDrvMedia != NULL));
+ Assert(pThis->aCts[i].aIfs[1].fPresent == (pThisCC->aCts[i].aIfs[1].pDrvMedia != NULL));
+
+ if (!pThisCC->aCts[i].aIfs[0].pDrvMedia && !pThisCC->aCts[i].aIfs[1].pDrvMedia)
+ {
+ /* No device present on this ATA bus; requires special handling. */
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1, 8 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Empty Bus", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPortsEmpty1);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase2, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 2 - Empty Bus", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPortsEmpty2);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ else
+ {
+ /* At least one device present, register regular handlers. */
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite1Data, ataIOPortRead1Data,
+ ataIOPortWriteStr1Data, ataIOPortReadStr1Data, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Data", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts1First);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1 + 1, 7 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite1Other, ataIOPortRead1Other, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Other", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts1Other);
+ AssertLogRelRCReturn(rc, rc);
+
+
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase2, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite2, ataIOPortRead2, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 2", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts2);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ }
+
+ 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*/);
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ataRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsBmDma, ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].hIoPorts1First != NIL_IOMIOPORTHANDLE)
+ {
+ rc = PDMDevHlpIoPortSetUpContextEx(pDevIns, pThis->aCts[i].hIoPorts1First,
+ ataIOPortWrite1Data, ataIOPortRead1Data,
+ ataIOPortWriteStr1Data, ataIOPortReadStr1Data, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPorts1Other,
+ ataIOPortWrite1Other, ataIOPortRead1Other, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPorts2,
+ ataIOPortWrite2, ataIOPortRead2, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ else
+ {
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPortsEmpty1,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, (void *)(uintptr_t)i /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPortsEmpty2,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, (void *)(uintptr_t)i /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePIIX3IDE =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "piix3ide",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | 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,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(ATASTATE),
+ /* .cbInstanceCC = */ sizeof(ATASTATECC),
+ /* .cbInstanceRC = */ sizeof(ATASTATERC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .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.",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ataR3Construct,
+ /* .pfnDestruct = */ ataR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ataR3Reset,
+ /* .pfnSuspend = */ ataR3Suspend,
+ /* .pfnResume = */ ataR3Resume,
+ /* .pfnAttach = */ ataR3Attach,
+ /* .pfnDetach = */ ataR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ ataR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ataRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ataRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+#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..a5cf9f9a
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevBusLogic.cpp
@@ -0,0 +1,4506 @@
+/* $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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmcritsect.h>
+#include <VBox/AssertGuest.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 5
+/** Saved state version before VBoxSCSI got removed. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_VBOXSCSI_REMOVAL 4
+/** Saved state version before command buffer size was raised. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_CMDBUF_RESIZE 3
+/** Saved state version before 24-bit mailbox support was implemented. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_24BIT_MBOX 2
+/** Saved state version before the suspend on error feature was implemented. */
+#define BUSLOGIC_SAVED_STATE_MINOR_PRE_ERROR_HANDLING 1
+
+/** 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
+{
+ /** The ring-3 device instance (for getting our bearings when arriving in an
+ * interface method). */
+ PPDMDEVINSR3 pDevIns;
+
+ /** LUN of the device. */
+ uint32_t iLUN;
+
+ /** Flag whether device is present.
+ * @note This is mirrored in BUSLOGIC::afDevicePresent. */
+ bool fPresent;
+ bool afAlignment[3];
+
+ /** 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;
+
+ /** Number of outstanding tasks on the port. */
+ volatile uint32_t cOutstandingRequests;
+ /** The device name. */
+ char szName[12];
+} 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;
+
+/**
+ * The shared BusLogic device emulation state.
+ */
+typedef struct BUSLOGIC
+{
+ /** Status register - Readonly. */
+ volatile uint8_t regStatus;
+ /** Interrupt register - Readonly. */
+ volatile uint8_t regInterrupt;
+ /** Geometry register - Readonly. */
+ volatile uint8_t regGeometry;
+ /** Pending (delayed) interrupt. */
+ volatile uint8_t uPendingIntr;
+
+ /** Command code the guest issued. */
+ uint8_t uOperationCode;
+ /** Current position in the command buffer. */
+ uint8_t iParameter;
+ /** Parameters left until the command is complete. */
+ uint8_t cbCommandParametersLeft;
+ /** 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. */
+
+ /** Only for LOG_ENABLED builds! */
+ volatile uint32_t cInMailboxesReadyIfLogEnabled;
+
+ /** Position in the buffer we are reading next.
+ * @note aligned on 64 byte boundrary for cache-line mojo. Means IOISABase
+ * is at offset 130. */
+ uint8_t iReply;
+ /** Bytes left until the reply buffer is empty. */
+ uint8_t cbReplyParametersLeft;
+ /** Buffer to store reply data from the controller to the guest. */
+ uint8_t aReplyBuffer[BUSLOGIC_REPLY_SIZE_MAX]; /* Size of the biggest reply. */
+
+ /** 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;
+
+ /** Whether we are using the RAM or reply buffer. */
+ bool fUseLocalRam;
+
+ /** 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;
+
+ /** Number of mailboxes the guest set up. */
+ uint32_t cMailbox;
+
+ /** Time when HBA reset was last initiated. */
+ uint64_t u64ResetTime; /**< @todo does this need to be saved? */
+ /** 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;
+ /** Flag whether a BIOS request is pending. */
+ volatile bool fBiosReqPending;
+
+ /** Whether strict round robin is enabled. */
+ bool fStrictRoundRobinMode;
+ /** Whether the extended LUN CCB format is enabled for 32 possible logical units. */
+ bool fExtendedLunCCBFormat;
+ /** Last completed command, for debugging. */
+ uint8_t uPrevCmd;
+
+ /** Current incoming mailbox position. */
+ uint32_t uMailboxIncomingPositionCurrent;
+ /** Physical base address of the incoming mailboxes. */
+ RTGCPHYS GCPhysAddrMailboxIncomingBase;
+
+ /** Critical section protecting access to the interrupt status register. */
+ PDMCRITSECT CritSectIntr;
+
+ /** Device presence indicators.
+ * @note Copy of BUSLOGICDEVICE::fPresent accessible from ring-0. */
+ bool afDevicePresent[BUSLOGIC_MAX_DEVICES];
+
+ /** The event semaphore the processing thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+ /** ISA compatibility I/O ports. */
+ IOMIOPORTHANDLE hIoPortsIsa;
+ /** BIOS I/O ports for booting, optional. */
+ IOMIOPORTHANDLE hIoPortsBios;
+ /** PCI Region \#0: I/O ports. */
+ IOMIOPORTHANDLE hIoPortsPci;
+ /** PCI Region \#1: MMIO (32 bytes, but probably rounded up to 4KB). */
+ IOMMMIOHANDLE hMmio;
+
+ /** 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;
+} BUSLOGIC;
+/** Pointer to the shared BusLogic device emulation state. */
+typedef BUSLOGIC *PBUSLOGIC;
+
+
+/**
+ * The ring-3 BusLogic device emulation state.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct BUSLOGICR3
+{
+ /** The device instance - only for getting our bearings in interface methods. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** 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;
+
+ /** 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;
+
+ /** Worker thread. */
+ R3PTRTYPE(PPDMTHREAD) pThreadWrk;
+
+ /** Pointer to the array of addresses to redo. */
+ R3PTRTYPE(PRTGCPHYS) paGCPhysAddrCCBRedo;
+ /** Number of addresses the redo array holds. */
+ uint32_t cReqsRedo;
+} BUSLOGICR3;
+/** Pointer to the ring-3 BusLogic device emulation state. */
+typedef BUSLOGICR3 *PBUSLOGICR3;
+
+
+/**
+ * The ring-0 BusLogic device emulation state.
+ */
+typedef struct BUSLOGICR0
+{
+ uint64_t uUnused;
+} BUSLOGICR0;
+/** Pointer to the ring-0 BusLogic device emulation state. */
+typedef BUSLOGICR0 *PBUSLOGICR0;
+
+
+/**
+ * The raw-mode BusLogic device emulation state.
+ */
+typedef struct BUSLOGICRC
+{
+ uint64_t uUnused;
+} BUSLOGICRC;
+/** Pointer to the raw-mode BusLogic device emulation state. */
+typedef BUSLOGICRC *PBUSLOGICRC;
+
+
+/** The current context BusLogic device emulation state. */
+typedef CTX_SUFF(BUSLOGIC) BUSLOGICCC;
+/** Pointer to the current context BusLogic device emulation state. */
+typedef CTX_SUFF(PBUSLOGIC) PBUSLOGICCC;
+
+
+/** 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;
+
+/**
+ * S/G buffer copy arguments.
+ */
+typedef struct BUSLOGICCOPYARGS
+{
+ /** Pointer to the shared BusLogic instance data. */
+ PBUSLOGIC pThis;
+ /** Pointer to the device instance data. */
+ PPDMDEVINS pDevIns;
+ /** Pointer to the SCSI command buffer. */
+ PESCMD pCmd;
+ /** Number of bytes copied already. */
+ size_t cbCopied;
+} BUSLOGICCOPYARGS;
+/** Pointer to BUSLOGICCOPYARGS. */
+typedef BUSLOGICCOPYARGS *PBUSLOGICCOPYARGS;
+
+#ifdef IN_RING3
+/**
+ * Memory buffer callback.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @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 DECLCALLBACKTYPE(void, FNBUSLOGICR3MEMCOPYCALLBACK,(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhys,
+ PRTSGBUF pSgBuf, size_t cbCopy, size_t *pcbSkip));
+/** Pointer to a memory copy buffer callback. */
+typedef FNBUSLOGICR3MEMCOPYCALLBACK *PFNBUSLOGICR3MEMCOPYCALLBACK;
+#endif
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+static int buslogicR3RegisterISARange(PPDMDEVINS pDevIns, PBUSLOGIC pThis, uint8_t uBaseCode);
+#endif
+
+
+/**
+ * Assert IRQ line of the BusLogic adapter. Rather than using
+ * the more modern method of the guest explicitly only clearing
+ * the interrupt causes it handled, BusLogic never reports all
+ * interrupts at once. Instead, new interrupts are postponed if
+ * an interrupt of a different type is still pending.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param fSuppressIrq Flag to suppress IRQ generation regardless of fIRQEnabled
+ * @param uIrqType Type of interrupt being generated.
+ */
+static void buslogicSetInterrupt(PPDMDEVINS pDevIns, PBUSLOGIC pThis, bool fSuppressIrq, uint8_t uIrqType)
+{
+ LogFlowFunc(("pThis=%#p, setting %#02x (current %#02x, pending %#02x)\n",
+ pThis, uIrqType, pThis->regInterrupt, pThis->uPendingIntr));
+
+ /* A CMDC interrupt overrides other pending interrupts. The documentation may claim
+ * otherwise, but a real BT-958 replaces a pending IMBL with a CMDC; the IMBL simply
+ * vanishes. However, if there's a CMDC already active, another CMDC is latched and
+ * reported once the first CMDC is cleared.
+ */
+ if (uIrqType & BL_INTR_CMDC)
+ {
+ Assert(uIrqType == BL_INTR_CMDC);
+ if ((pThis->regInterrupt & BL_INTR_INTV) && !(pThis->regInterrupt & BL_INTR_CMDC))
+ Log(("CMDC overriding pending interrupt! (was %02x)\n", pThis->regInterrupt));
+ if (!(pThis->regInterrupt & BL_INTR_CMDC))
+ pThis->regInterrupt |= uIrqType | BL_INTR_INTV; /* Report now. */
+ else
+ pThis->uPendingIntr |= uIrqType; /* Report later. */
+ }
+ else if (uIrqType & (BL_INTR_IMBL | BL_INTR_OMBR))
+ {
+ /* If the CMDC interrupt is pending, store IMBL/OMBR for later. Note that IMBL
+ * and OMBR can be reported together even if an interrupt of the other type is
+ * already pending.
+ */
+ if (!(pThis->regInterrupt & BL_INTR_CMDC))
+ pThis->regInterrupt |= uIrqType | BL_INTR_INTV; /* Report now. */
+ else
+ pThis->uPendingIntr |= uIrqType; /* Report later. */
+ }
+ else /* We do not expect to see BL_INTR_RSTS at this point. */
+ AssertMsgFailed(("Invalid interrupt state (unknown interrupt cause)!\n"));
+ AssertMsg(pThis->regInterrupt, ("Invalid interrupt state (interrupt not set)!\n"));
+ AssertMsg(pThis->regInterrupt != BL_INTR_INTV, ("Invalid interrupt state (set but no cause)!\n"));
+
+ if (pThis->fIRQEnabled && !fSuppressIrq)
+ {
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pThis->uIsaIrq, 1);
+ }
+}
+
+/**
+ * Deasserts the interrupt line of the BusLogic adapter.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ */
+static void buslogicClearInterrupt(PPDMDEVINS pDevIns, PBUSLOGIC pThis)
+{
+ LogFlowFunc(("pThis=%#p, clearing %#02x (pending %#02x)\n",
+ pThis, pThis->regInterrupt, pThis->uPendingIntr));
+ pThis->regInterrupt = 0;
+ pThis->regStatus &= ~BL_STAT_CMDINV;
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pThis->uIsaIrq, 0);
+ /* If there's another pending interrupt, report it now. */
+ if (pThis->uPendingIntr)
+ {
+ buslogicSetInterrupt(pDevIns, pThis, false, pThis->uPendingIntr);
+ pThis->uPendingIntr = 0;
+ }
+}
+
+#if defined(IN_RING3)
+
+/**
+ * Advances the mailbox pointer to the next slot.
+ *
+ * @param pThis Pointer to the shared BusLogic instance data.
+ */
+DECLINLINE(void) buslogicR3OutgoingMailboxAdvance(PBUSLOGIC pThis)
+{
+ pThis->uMailboxOutgoingPositionCurrent = (pThis->uMailboxOutgoingPositionCurrent + 1) % pThis->cMailbox;
+}
+
+/**
+ * Initialize local RAM of host adapter with default values.
+ *
+ * @param pThis Pointer to the shared BusLogic instance data.
+ */
+static void buslogicR3InitializeLocalRam(PBUSLOGIC pThis)
+{
+ /*
+ * 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(pThis->LocalRam.u8View, 0, sizeof(HostAdapterLocalRam));
+ pThis->LocalRam.structured.autoSCSIData.fLevelSensitiveInterrupt = true;
+ pThis->LocalRam.structured.autoSCSIData.fParityCheckingEnabled = true;
+ pThis->LocalRam.structured.autoSCSIData.fExtendedTranslation = true; /* Same as in geometry register. */
+ pThis->LocalRam.structured.autoSCSIData.u16DeviceEnabledMask = UINT16_MAX; /* All enabled. Maybe mask out non present devices? */
+ pThis->LocalRam.structured.autoSCSIData.u16WidePermittedMask = UINT16_MAX;
+ pThis->LocalRam.structured.autoSCSIData.u16FastPermittedMask = UINT16_MAX;
+ pThis->LocalRam.structured.autoSCSIData.u16SynchronousPermittedMask = UINT16_MAX;
+ pThis->LocalRam.structured.autoSCSIData.u16DisconnectPermittedMask = UINT16_MAX;
+ pThis->LocalRam.structured.autoSCSIData.fStrictRoundRobinMode = pThis->fStrictRoundRobinMode;
+ pThis->LocalRam.structured.autoSCSIData.u16UltraPermittedMask = UINT16_MAX;
+ pThis->LocalRam.structured.autoSCSIData.uSCSIId = 7;
+ pThis->LocalRam.structured.autoSCSIData.uHostAdapterIoPortAddress = pThis->uDefaultISABaseCode == ISA_BASE_DISABLED ? 2 : pThis->uDefaultISABaseCode;
+ /** @todo calculate checksum? */
+}
+
+/**
+ * Do a hardware reset of the buslogic adapter.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param fResetIO Flag determining whether ISA I/O should be reset.
+ */
+static int buslogicR3HwReset(PPDMDEVINS pDevIns, PBUSLOGIC pThis, bool fResetIO)
+{
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /* Reset registers to default values. */
+ pThis->regStatus = BL_STAT_HARDY | BL_STAT_INREQ;
+ pThis->regGeometry = BL_GEOM_XLATEN;
+ pThis->uOperationCode = 0xff; /* No command executing. */
+ pThis->uPrevCmd = 0xff;
+ pThis->iParameter = 0;
+ pThis->cbCommandParametersLeft = 0;
+ pThis->fIRQEnabled = true;
+ pThis->fStrictRoundRobinMode = false;
+ pThis->fExtendedLunCCBFormat = false;
+ pThis->uMailboxOutgoingPositionCurrent = 0;
+ pThis->uMailboxIncomingPositionCurrent = 0;
+ pThis->uAhaSigIdx = 0;
+ pThis->cMailbox = 0;
+ pThis->GCPhysAddrMailboxIncomingBase = 0;
+ pThis->GCPhysAddrMailboxOutgoingBase = 0;
+
+ /* Clear any active/pending interrupts. */
+ pThis->uPendingIntr = 0;
+ buslogicClearInterrupt(pDevIns, pThis);
+
+ /* Guest-initiated HBA reset does not affect ISA port I/O. */
+ if (fResetIO)
+ buslogicR3RegisterISARange(pDevIns, pThis, pThis->uDefaultISABaseCode);
+ buslogicR3InitializeLocalRam(pThis);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Resets the command state machine for the next command and notifies the guest.
+ * Note that suppressing CMDC also suppresses the interrupt, but not vice versa.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param fSuppressIrq Flag to suppress IRQ generation regardless of current state
+ * @param fSuppressCMDC Flag to suppress command completion status as well
+ */
+static void buslogicCommandComplete(PPDMDEVINS pDevIns, PBUSLOGIC pThis, bool fSuppressIrq, bool fSuppressCMDC)
+{
+ LogFlowFunc(("pThis=%#p\n", pThis));
+ Assert(pThis->uOperationCode != BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND);
+
+ pThis->fUseLocalRam = false;
+ pThis->regStatus |= BL_STAT_HARDY;
+ pThis->regStatus &= ~BL_STAT_DIRRDY;
+ pThis->iReply = 0;
+
+ /* Some commands do not set CMDC when successful. */
+ if (!fSuppressCMDC)
+ {
+ /* Notify that the command is complete. */
+ buslogicSetInterrupt(pDevIns, pThis, fSuppressIrq, BL_INTR_CMDC);
+ }
+
+ pThis->uPrevCmd = pThis->uOperationCode;
+ pThis->uOperationCode = 0xff;
+ pThis->iParameter = 0;
+}
+
+/**
+ * Memory write helper to handle PCI/ISA differences - metadata writes.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param GCPhys Guest physical memory address
+ * @param pvBuf Host side buffer address
+ * @param cbWrite Number of bytes to write
+ */
+static void blPhysWriteMeta(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhys, const void *pvBuf, size_t cbWrite)
+{
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhys, pvBuf, cbWrite);
+ else
+ PDMDevHlpPhysWriteMeta(pDevIns, GCPhys, pvBuf, cbWrite);
+}
+
+/**
+ * Memory read helper to handle PCI/ISA differences - metadata reads.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param GCPhys Guest physical memory address.
+ * @param pvBuf Host side buffer address.
+ * @param cbRead Number of bytes to read.
+ */
+static void blPhysReadMeta(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhys, void *pvBuf, size_t cbRead)
+{
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhys, pvBuf, cbRead);
+ else
+ PDMDevHlpPhysReadMeta(pDevIns, GCPhys, pvBuf, cbRead);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Memory write helper to handle PCI/ISA differences - userdata writes.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param GCPhys Guest physical memory address
+ * @param pvBuf Host side buffer address
+ * @param cbWrite Number of bytes to write
+ */
+static void blPhysWriteUser(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhys, const void *pvBuf, size_t cbWrite)
+{
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCIPhysWriteUser(pDevIns, GCPhys, pvBuf, cbWrite);
+ else
+ PDMDevHlpPhysWriteUser(pDevIns, GCPhys, pvBuf, cbWrite);
+}
+
+/**
+ * Memory read helper to handle PCI/ISA differences - userdata reads.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param GCPhys Guest physical memory address.
+ * @param pvBuf Host side buffer address.
+ * @param cbRead Number of bytes to read.
+ */
+static void blPhysReadUser(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhys, void *pvBuf, size_t cbRead)
+{
+ if (!pThis->uIsaIrq)
+ PDMDevHlpPCIPhysReadUser(pDevIns, GCPhys, pvBuf, cbRead);
+ else
+ PDMDevHlpPhysReadUser(pDevIns, GCPhys, pvBuf, cbRead);
+}
+
+/**
+ * Initiates a hard reset which was issued from the guest.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param fHardReset Flag initiating a hard (vs. soft) reset.
+ */
+static void buslogicR3InitiateReset(PPDMDEVINS pDevIns, PBUSLOGIC pThis, bool fHardReset)
+{
+ LogFlowFunc(("pThis=%#p fHardReset=%d\n", pThis, fHardReset));
+
+ buslogicR3HwReset(pDevIns, pThis, false);
+
+ if (fHardReset)
+ {
+ /* Set the diagnostic active bit in the status register and clear the ready state. */
+ pThis->regStatus |= BL_STAT_DACT;
+ pThis->regStatus &= ~BL_STAT_HARDY;
+
+ /* Remember when the guest initiated a reset (after we're done resetting). */
+ pThis->u64ResetTime = PDMDevHlpTMTimeVirtGetNano(pDevIns);
+ }
+}
+
+
+/**
+ * Send a mailbox with set status codes to the guest.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @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(PPDMDEVINS pDevIns, PBUSLOGIC pThis, 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.uReserved = 0;
+ MbxIn.u.in.uCompletionCode = uMailboxCompletionCode;
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSectIntr, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CritSectIntr, rc);
+
+ RTGCPHYS GCPhysAddrMailboxIncoming = pThis->GCPhysAddrMailboxIncomingBase
+ + ( pThis->uMailboxIncomingPositionCurrent
+ * (pThis->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. */
+ blPhysWriteMeta(pDevIns, pThis, GCPhysAddrCCB, pCCBGuest, RT_UOFFSETOF(CCBC, abCDB));
+ }
+
+# ifdef RT_STRICT
+ uint8_t uCode;
+ unsigned uCodeOffs = pThis->fMbxIs24Bit ? RT_OFFSETOF(Mailbox24, uCmdState) : RT_OFFSETOF(Mailbox32, u.out.uActionCode);
+ blPhysReadMeta(pDevIns, pThis, GCPhysAddrMailboxIncoming + uCodeOffs, &uCode, sizeof(uCode));
+ Assert(uCode == BUSLOGIC_MAILBOX_INCOMING_COMPLETION_FREE);
+# endif
+
+ /* Update mailbox. */
+ if (pThis->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)));
+ blPhysWriteMeta(pDevIns, pThis, GCPhysAddrMailboxIncoming, &Mbx24, sizeof(Mailbox24));
+ }
+ else
+ {
+ Log(("32-bit mailbox: completion code=%u, CCB at %RGp\n", MbxIn.u.in.uCompletionCode, GCPhysAddrCCB));
+ blPhysWriteMeta(pDevIns, pThis, GCPhysAddrMailboxIncoming, &MbxIn, sizeof(Mailbox32));
+ }
+
+ /* Advance to next mailbox position. */
+ pThis->uMailboxIncomingPositionCurrent++;
+ if (pThis->uMailboxIncomingPositionCurrent >= pThis->cMailbox)
+ pThis->uMailboxIncomingPositionCurrent = 0;
+
+# ifdef LOG_ENABLED
+ ASMAtomicIncU32(&pThis->cInMailboxesReadyIfLogEnabled);
+# endif
+
+ buslogicSetInterrupt(pDevIns, pThis, false, BL_INTR_IMBL);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->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.
+ *
+ * @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. */
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ if (fIs24Bit)
+ {
+ SGE24 aSGE24[32];
+ Assert(cEntries <= RT_ELEMENTS(aSGE24));
+
+ Log2(("Converting %u 24-bit S/G entries to 32-bit\n", cEntries));
+ blPhysReadMeta(pDevIns, pThis, 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
+ blPhysReadMeta(pDevIns, pThis, 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 FNBUSLOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) buslogicR3CopyBufferFromGuestWorker(PPDMDEVINS pDevIns, 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);
+ blPhysReadUser(pDevIns, pThis, GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc FNBUSLOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) buslogicR3CopyBufferToGuestWorker(PPDMDEVINS pDevIns, 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);
+ blPhysWriteUser(pDevIns, 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 pDevIns The device instance.
+ * @param pThis Pointer to the Buslogic device state.
+ * @param pReq Pointer 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(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICREQ pReq,
+ PFNBUSLOGICR3MEMCOPYCALLBACK pfnCopyWorker,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ 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(pDevIns, 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(pDevIns, 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @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(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICREQ pReq,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ return buslogicR3SgBufWalker(pDevIns, 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @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(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICREQ pReq,
+ PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
+{
+ return buslogicR3SgBufWalker(pDevIns, 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.
+ *
+ * @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)
+ {
+ PPDMDEVINS pDevIns = pReq->pTargetDevice->pDevIns;
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, 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));
+ blPhysWriteMeta(pDevIns, 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 pDevIns The PDM device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ */
+static int buslogicProcessCommand(PPDMDEVINS pDevIns, PBUSLOGIC pThis)
+{
+ int rc = VINF_SUCCESS;
+ bool fSuppressIrq = false;
+ bool fSuppressCMDC = false;
+ bool fCmdComplete = true;
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+ AssertMsg(pThis->uOperationCode != 0xff, ("There is no command to execute\n"));
+
+ switch (pThis->uOperationCode)
+ {
+ case BUSLOGICCOMMAND_TEST_CMDC_INTERRUPT:
+ /* Valid command, no reply. */
+ pThis->cbReplyParametersLeft = 0;
+ break;
+ case BUSLOGICCOMMAND_INQUIRE_PCI_HOST_ADAPTER_INFORMATION:
+ {
+ PReplyInquirePCIHostAdapterInformation pReply = (PReplyInquirePCIHostAdapterInformation)pThis->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquirePCIHostAdapterInformation));
+
+ /* Modeled after a real BT-958(D) */
+ pReply->HighByteTerminated = 1;
+ pReply->LowByteTerminated = 1;
+ pReply->JP1 = 1; /* Closed; "Factory configured - do not alter" */
+ pReply->InformationIsValid = 1;
+ pReply->IsaIOPort = pThis->uISABaseCode < 6 ? pThis->uISABaseCode : 0xff;
+ pReply->IRQ = PCIDevGetInterruptLine(pDevIns->apPciDevs[0]);
+ pThis->cbReplyParametersLeft = sizeof(ReplyInquirePCIHostAdapterInformation);
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT:
+ {
+ /* no-op */
+ pThis->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 /* We can do this from ring-0 now, but we'd like to see the LogRel, so we keep going back to ring-3 anyway. */
+ uint8_t baseCode = pThis->aCommandBuffer[0];
+
+ Log(("ISA I/O for PCI (code %x)\n", baseCode));
+ pThis->cbReplyParametersLeft = 0;
+ if (baseCode < 8) {
+ buslogicR3RegisterISARange(pDevIns, pThis, baseCode);
+ fSuppressIrq = true;
+ fSuppressCMDC = true;
+ }
+ else
+ {
+ Log(("ISA base %#x not valid for this adapter\n", baseCode));
+ pThis->regStatus |= BL_STAT_CMDINV;
+ }
+ 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 (pThis->uDevType == DEV_AHA_1540B)
+ {
+ pThis->aReplyBuffer[0] = 'A'; /* Firmware option bytes */
+ pThis->aReplyBuffer[1] = '0'; /* Special option byte */
+ }
+ else
+ {
+ pThis->aReplyBuffer[0] = 'A'; /* Firmware option bytes */
+ pThis->aReplyBuffer[1] = 'A'; /* Special option byte */
+ }
+
+ /* We report version 5.07B. This reply will provide the first two digits. */
+ pThis->aReplyBuffer[2] = '5'; /* Major version 5 */
+ pThis->aReplyBuffer[3] = '0'; /* Minor version 0 */
+ pThis->cbReplyParametersLeft = 4; /* Reply is 4 bytes long */
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_3RD_LETTER:
+ {
+ if (pThis->uDevType == DEV_AHA_1540B)
+ {
+ /* Newer ASPI4DOS.SYS versions expect this command to fail. */
+ Log(("Command %#x not valid for this adapter\n", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ pThis->aReplyBuffer[0] = '7';
+ pThis->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_FIRMWARE_VERSION_LETTER:
+ {
+ pThis->aReplyBuffer[0] = 'B';
+ pThis->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS:
+ /* The parameter list length is determined by the first byte of the command buffer. */
+ if (pThis->iParameter == 1)
+ {
+ /* First pass - set the number of following parameter bytes. */
+ pThis->cbCommandParametersLeft = RT_MIN(pThis->aCommandBuffer[0], sizeof(pThis->aCommandBuffer) - 1);
+ Log(("Set HA options: %u bytes follow\n", pThis->cbCommandParametersLeft));
+ }
+ else
+ {
+ /* Second pass - process received data. */
+ Log(("Set HA options: received %u bytes\n", pThis->aCommandBuffer[0]));
+ /* We ignore the data - it only concerns the SCSI hardware protocol. */
+ }
+ pThis->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 (pThis->iParameter == 12)
+ {
+ /* First pass - set the number of following CDB bytes. */
+ pThis->cbCommandParametersLeft = RT_MIN(pThis->aCommandBuffer[11], sizeof(pThis->aCommandBuffer) - 12);
+ Log(("Execute SCSI cmd: %u more bytes follow\n", pThis->cbCommandParametersLeft));
+ }
+ else
+ {
+ PESCMD pCmd;
+
+ /* Second pass - process received data. */
+ Log(("Execute SCSI cmd: received %u bytes\n", pThis->aCommandBuffer[0]));
+ pCmd = (PESCMD)pThis->aCommandBuffer;
+ Log(("Addr %08X, cbData %08X, cbCDB=%u\n", pCmd->u32PhysAddrData, pCmd->cbData, pCmd->cbCDB));
+
+ if (!ASMAtomicXchgBool(&pThis->fBiosReqPending, true))
+ {
+ /* Wake up the worker thread. */
+ int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+ AssertRC(rc2);
+ }
+
+ fCmdComplete = false;
+ }
+ break;
+
+ case BUSLOGICCOMMAND_INQUIRE_HOST_ADAPTER_MODEL_NUMBER:
+ {
+ /* Not supported on AHA-154x. */
+ if (pThis->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->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 (pThis->aCommandBuffer[0] > sizeof(pThis->aReplyBuffer))
+ {
+ Log(("Requested too much adapter model number data (%u)!\n", pThis->aCommandBuffer[0]));
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pThis->cbReplyParametersLeft = pThis->aCommandBuffer[0];
+ memset(pThis->aReplyBuffer, 0, sizeof(pThis->aReplyBuffer));
+ const char aModelName[] = "958D "; /* Trailing \0 is fine, that's the filler anyway. */
+ int cCharsToTransfer = pThis->cbReplyParametersLeft <= sizeof(aModelName)
+ ? pThis->cbReplyParametersLeft
+ : sizeof(aModelName);
+
+ for (int i = 0; i < cCharsToTransfer; i++)
+ pThis->aReplyBuffer[i] = aModelName[i];
+
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_CONFIGURATION:
+ {
+ uint8_t uIrq;
+
+ if (pThis->uIsaIrq)
+ uIrq = pThis->uIsaIrq;
+ else
+ uIrq = PCIDevGetInterruptLine(pDevIns->apPciDevs[0]);
+
+ pThis->cbReplyParametersLeft = sizeof(ReplyInquireConfiguration);
+ PReplyInquireConfiguration pReply = (PReplyInquireConfiguration)pThis->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 (pThis->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ /* The reply length is set by the guest and is found in the first byte of the command buffer. */
+ pThis->cbReplyParametersLeft = pThis->aCommandBuffer[0];
+ PReplyInquireExtendedSetupInformation pReply = (PReplyInquireExtendedSetupInformation)pThis->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquireExtendedSetupInformation));
+
+ /** @todo should this reflect the RAM contents (AutoSCSIRam)? */
+ pReply->uBusType = 'E'; /* EISA style */
+ pReply->u16ScatterGatherLimit = 8192;
+ pReply->cMailbox = pThis->cMailbox;
+ pReply->uMailboxAddressBase = (uint32_t)pThis->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. */
+ pThis->cbReplyParametersLeft = pThis->aCommandBuffer[0];
+ PReplyInquireSetupInformation pReply = (PReplyInquireSetupInformation)pThis->aReplyBuffer;
+ memset(pReply, 0, sizeof(ReplyInquireSetupInformation));
+ pReply->fSynchronousInitiationEnabled = true;
+ pReply->fParityCheckingEnabled = true;
+ pReply->cMailbox = pThis->cMailbox;
+ U32_TO_ADDR(pReply->MailboxAddress, pThis->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 (pThis->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 = pThis->aCommandBuffer[0];
+ pThis->cbReplyParametersLeft = pThis->aCommandBuffer[1];
+
+ pThis->fUseLocalRam = true;
+ pThis->iReply = uOffset;
+ break;
+ }
+ case BUSLOGICCOMMAND_INITIALIZE_MAILBOX:
+ {
+ PRequestInitMbx pRequest = (PRequestInitMbx)pThis->aCommandBuffer;
+
+ pThis->cbReplyParametersLeft = 0;
+ if (!pRequest->cMailbox)
+ {
+ Log(("cMailboxes=%u (24-bit mode), fail!\n", pThis->cMailbox));
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pThis->fMbxIs24Bit = true;
+ pThis->cMailbox = pRequest->cMailbox;
+ pThis->uMailboxOutgoingPositionCurrent = pThis->uMailboxIncomingPositionCurrent = 0;
+ pThis->GCPhysAddrMailboxOutgoingBase = (RTGCPHYS)ADDR_TO_U32(pRequest->aMailboxBaseAddr);
+ /* The area for incoming mailboxes is right after the last entry of outgoing mailboxes. */
+ pThis->GCPhysAddrMailboxIncomingBase = pThis->GCPhysAddrMailboxOutgoingBase + (pThis->cMailbox * sizeof(Mailbox24));
+
+ Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pThis->GCPhysAddrMailboxOutgoingBase));
+ Log(("GCPhysAddrMailboxIncomingBase=%RGp\n", pThis->GCPhysAddrMailboxIncomingBase));
+ Log(("cMailboxes=%u (24-bit mode)\n", pThis->cMailbox));
+ LogRel(("Initialized 24-bit mailbox, %d entries at %08x\n", pRequest->cMailbox, ADDR_TO_U32(pRequest->aMailboxBaseAddr)));
+
+ pThis->regStatus &= ~BL_STAT_INREQ;
+ break;
+ }
+ case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+ {
+ if (pThis->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ PRequestInitializeExtendedMailbox pRequest = (PRequestInitializeExtendedMailbox)pThis->aCommandBuffer;
+
+ pThis->cbReplyParametersLeft = 0;
+ if (!pRequest->cMailbox)
+ {
+ Log(("cMailboxes=%u (32-bit mode), fail!\n", pThis->cMailbox));
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pThis->fMbxIs24Bit = false;
+ pThis->cMailbox = pRequest->cMailbox;
+ pThis->uMailboxOutgoingPositionCurrent = pThis->uMailboxIncomingPositionCurrent = 0;
+ pThis->GCPhysAddrMailboxOutgoingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress;
+ /* The area for incoming mailboxes is right after the last entry of outgoing mailboxes. */
+ pThis->GCPhysAddrMailboxIncomingBase = (RTGCPHYS)pRequest->uMailboxBaseAddress + (pThis->cMailbox * sizeof(Mailbox32));
+
+ Log(("GCPhysAddrMailboxOutgoingBase=%RGp\n", pThis->GCPhysAddrMailboxOutgoingBase));
+ Log(("GCPhysAddrMailboxIncomingBase=%RGp\n", pThis->GCPhysAddrMailboxIncomingBase));
+ Log(("cMailboxes=%u (32-bit mode)\n", pThis->cMailbox));
+ LogRel(("Initialized 32-bit mailbox, %d entries at %08x\n", pRequest->cMailbox, pRequest->uMailboxBaseAddress));
+
+ pThis->regStatus &= ~BL_STAT_INREQ;
+ break;
+ }
+ case BUSLOGICCOMMAND_ENABLE_STRICT_ROUND_ROBIN_MODE:
+ {
+ if (pThis->aCommandBuffer[0] == 0)
+ pThis->fStrictRoundRobinMode = false;
+ else if (pThis->aCommandBuffer[0] == 1)
+ pThis->fStrictRoundRobinMode = true;
+ else
+ AssertMsgFailed(("Invalid round robin mode %d\n", pThis->aCommandBuffer[0]));
+
+ pThis->cbReplyParametersLeft = 0;
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_CCB_FORMAT:
+ {
+ if (pThis->aCommandBuffer[0] == 0)
+ pThis->fExtendedLunCCBFormat = false;
+ else if (pThis->aCommandBuffer[0] == 1)
+ pThis->fExtendedLunCCBFormat = true;
+ else
+ AssertMsgFailed(("Invalid CCB format %d\n", pThis->aCommandBuffer[0]));
+
+ pThis->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(pThis->aReplyBuffer, 0, 8);
+ for (int i = 0; i < 8; ++i)
+ {
+ if (pThis->afDevicePresent[i])
+ pThis->aReplyBuffer[i] = 1;
+ }
+ pThis->aReplyBuffer[7] = 0; /* HA hardcoded at ID 7. */
+ pThis->cbReplyParametersLeft = 8;
+ break;
+ case BUSLOGICCOMMAND_INQUIRE_INSTALLED_DEVICES_ID_8_TO_15:
+ /* See note about cheating above. */
+ memset(pThis->aReplyBuffer, 0, 8);
+ for (int i = 0; i < 8; ++i)
+ {
+ if (pThis->afDevicePresent[i + 8])
+ pThis->aReplyBuffer[i] = 1;
+ }
+ pThis->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(pThis->afDevicePresent); i++)
+ {
+ if (pThis->afDevicePresent[i])
+ u16TargetsPresentMask |= (1 << i);
+ }
+ pThis->aReplyBuffer[0] = (uint8_t)u16TargetsPresentMask;
+ pThis->aReplyBuffer[1] = (uint8_t)(u16TargetsPresentMask >> 8);
+ pThis->cbReplyParametersLeft = 2;
+ break;
+ }
+ case BUSLOGICCOMMAND_INQUIRE_SYNCHRONOUS_PERIOD:
+ {
+ if (pThis->aCommandBuffer[0] > sizeof(pThis->aReplyBuffer))
+ {
+ Log(("Requested too much synch period inquiry (%u)!\n", pThis->aCommandBuffer[0]));
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+ pThis->cbReplyParametersLeft = pThis->aCommandBuffer[0];
+ for (uint8_t i = 0; i < pThis->cbReplyParametersLeft; i++)
+ pThis->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 (pThis->uDevType == DEV_AHA_1540B)
+ {
+ Log(("Command %#x not valid for this adapter\n", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->regStatus |= BL_STAT_CMDINV;
+ break;
+ }
+
+ pThis->cbReplyParametersLeft = 0;
+ if (pThis->aCommandBuffer[0] == 0)
+ pThis->fIRQEnabled = false;
+ else
+ pThis->fIRQEnabled = true;
+ /* No interrupt signaled regardless of enable/disable. NB: CMDC is still signaled! */
+ fSuppressIrq = true;
+ break;
+ }
+ case BUSLOGICCOMMAND_ECHO_COMMAND_DATA:
+ {
+ pThis->aReplyBuffer[0] = pThis->aCommandBuffer[0];
+ pThis->cbReplyParametersLeft = 1;
+ break;
+ }
+ case BUSLOGICCOMMAND_ENABLE_OUTGOING_MAILBOX_AVAILABLE_INTERRUPT:
+ {
+ uint8_t uEnable = pThis->aCommandBuffer[0];
+
+ pThis->cbReplyParametersLeft = 0;
+ Log(("Enable OMBR: %u\n", uEnable));
+ /* Only 0/1 are accepted. */
+ if (uEnable > 1)
+ pThis->regStatus |= BL_STAT_CMDINV;
+ else
+ {
+ pThis->LocalRam.structured.autoSCSIData.uReserved6 = uEnable;
+ fSuppressIrq = true;
+ fSuppressCMDC = true;
+ }
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_PREEMPT_TIME_ON_BUS:
+ {
+ pThis->cbReplyParametersLeft = 0;
+ pThis->LocalRam.structured.autoSCSIData.uBusOnDelay = pThis->aCommandBuffer[0];
+ Log(("Bus-on time: %d\n", pThis->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_TIME_OFF_BUS:
+ {
+ pThis->cbReplyParametersLeft = 0;
+ pThis->LocalRam.structured.autoSCSIData.uBusOffDelay = pThis->aCommandBuffer[0];
+ Log(("Bus-off time: %d\n", pThis->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_SET_BUS_TRANSFER_RATE:
+ {
+ pThis->cbReplyParametersLeft = 0;
+ pThis->LocalRam.structured.autoSCSIData.uDMATransferRate = pThis->aCommandBuffer[0];
+ Log(("Bus transfer rate: %02X\n", pThis->aCommandBuffer[0]));
+ break;
+ }
+ case BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO:
+ {
+ RTGCPHYS GCPhysFifoBuf;
+ Addr24 addr;
+
+ pThis->cbReplyParametersLeft = 0;
+ addr.hi = pThis->aCommandBuffer[0];
+ addr.mid = pThis->aCommandBuffer[1];
+ addr.lo = pThis->aCommandBuffer[2];
+ GCPhysFifoBuf = (RTGCPHYS)ADDR_TO_U32(addr);
+ Log(("Write busmaster FIFO at: %04X\n", ADDR_TO_U32(addr)));
+ blPhysReadMeta(pDevIns, pThis, GCPhysFifoBuf, &pThis->LocalRam.u8View[64], 64);
+ break;
+ }
+ case BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO:
+ {
+ RTGCPHYS GCPhysFifoBuf;
+ Addr24 addr;
+
+ pThis->cbReplyParametersLeft = 0;
+ addr.hi = pThis->aCommandBuffer[0];
+ addr.mid = pThis->aCommandBuffer[1];
+ addr.lo = pThis->aCommandBuffer[2];
+ GCPhysFifoBuf = (RTGCPHYS)ADDR_TO_U32(addr);
+ Log(("Read busmaster FIFO at: %04X\n", ADDR_TO_U32(addr)));
+ blPhysWriteMeta(pDevIns, pThis, GCPhysFifoBuf, &pThis->LocalRam.u8View[64], 64);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid command %#x\n", pThis->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", pThis->uOperationCode));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->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", pThis->uOperationCode, pThis->cbReplyParametersLeft));
+
+ /* Fail command if too much parameter data requested. */
+ if ((pThis->cbCommandParametersLeft + pThis->iParameter) > sizeof(pThis->aCommandBuffer))
+ {
+ Log(("Invalid command parameter length (%u)\n", pThis->cbCommandParametersLeft));
+ pThis->cbReplyParametersLeft = 0;
+ pThis->cbCommandParametersLeft = 0;
+ pThis->regStatus |= BL_STAT_CMDINV;
+ }
+
+ if (fCmdComplete)
+ {
+ /* Set the data in ready bit in the status register in case the command has a reply. */
+ if (pThis->cbReplyParametersLeft)
+ pThis->regStatus |= BL_STAT_DIRRDY;
+ else if (!pThis->cbCommandParametersLeft)
+ buslogicCommandComplete(pDevIns, pThis, fSuppressIrq, fSuppressCMDC);
+ }
+
+ return rc;
+}
+
+/**
+ * Read a register from the BusLogic adapter.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param iRegister The index of the register to read.
+ * @param pu32 Where to store the register content.
+ */
+static int buslogicRegisterRead(PPDMDEVINS pDevIns, PBUSLOGIC pThis, unsigned iRegister, uint32_t *pu32)
+{
+ static const char s_szAhaSig[] = "ADAP";
+ int rc = VINF_SUCCESS;
+
+ switch (iRegister)
+ {
+ case BUSLOGIC_REGISTER_STATUS:
+ {
+ *pu32 = pThis->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 (pThis->regStatus & BL_STAT_DACT)
+ {
+ uint64_t u64AccessTime = PDMDevHlpTMTimeVirtGetNano(pDevIns);
+
+ pThis->regStatus &= ~BL_STAT_DACT;
+ pThis->regStatus |= BL_STAT_HARDY;
+
+ if (u64AccessTime - pThis->u64ResetTime > BUSLOGIC_RESET_DURATION_NS)
+ {
+ /* If reset already expired, let the guest see that right away. */
+ *pu32 = pThis->regStatus;
+ pThis->u64ResetTime = 0;
+ }
+ }
+ break;
+ }
+ case BUSLOGIC_REGISTER_DATAIN:
+ {
+ AssertCompileSize(pThis->LocalRam, 256);
+ AssertCompileSize(pThis->iReply, sizeof(uint8_t));
+ AssertCompileSize(pThis->cbReplyParametersLeft, sizeof(uint8_t));
+
+ if (pThis->fUseLocalRam)
+ *pu32 = pThis->LocalRam.u8View[pThis->iReply];
+ else
+ {
+ /*
+ * Real adapters seem to pad the reply with zeroes and allow up to 255 bytes even
+ * if the real reply is shorter.
+ */
+ if (pThis->iReply >= sizeof(pThis->aReplyBuffer))
+ *pu32 = 0;
+ else
+ *pu32 = pThis->aReplyBuffer[pThis->iReply];
+ }
+
+ /* Careful about underflow - guest can read data register even if
+ * no data is available.
+ */
+ if (pThis->cbReplyParametersLeft)
+ {
+ pThis->iReply++;
+ pThis->cbReplyParametersLeft--;
+ if (!pThis->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 (pThis->uOperationCode == BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM)
+ buslogicCommandComplete(pDevIns, pThis, true /* fSuppressIrq */, true /* fSuppressCMDC */);
+ else
+ buslogicCommandComplete(pDevIns, pThis, false, false);
+ }
+ }
+ LogFlowFunc(("data=%02x, iReply=%d, cbReplyParametersLeft=%u\n", *pu32,
+ pThis->iReply, pThis->cbReplyParametersLeft));
+ break;
+ }
+ case BUSLOGIC_REGISTER_INTERRUPT:
+ {
+ *pu32 = pThis->regInterrupt;
+ break;
+ }
+ case BUSLOGIC_REGISTER_GEOMETRY:
+ {
+ if (pThis->uDevType == DEV_AHA_1540B)
+ {
+ uint8_t off = pThis->uAhaSigIdx & 3;
+ *pu32 = s_szAhaSig[off];
+ pThis->uAhaSigIdx = (off + 1) & 3;
+ }
+ else
+ *pu32 = pThis->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 pDevIns The PDM device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param iRegister The index of the register to read.
+ * @param uVal The value to write.
+ */
+static int buslogicRegisterWrite(PPDMDEVINS pDevIns, PBUSLOGIC pThis, 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(pDevIns, pThis, fHardReset);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ }
+
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSectIntr, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+#ifdef LOG_ENABLED
+ uint32_t cMailboxesReady = ASMAtomicXchgU32(&pThis->cInMailboxesReadyIfLogEnabled, 0);
+ Log(("%u incoming mailboxes were ready when this interrupt was cleared\n", cMailboxesReady));
+#endif
+
+ if (uVal & BL_CTRL_RINT)
+ buslogicClearInterrupt(pDevIns, pThis);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSectIntr);
+
+ break;
+ }
+ case BUSLOGIC_REGISTER_COMMAND:
+ {
+ /* Fast path for mailbox execution command. */
+ if ((uVal == BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND) && (pThis->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 (pThis->cMailbox)
+ {
+ ASMAtomicIncU32(&pThis->cMailboxesReady);
+ if (!ASMAtomicXchgBool(&pThis->fNotificationSent, true))
+ {
+ /* Wake up the worker thread. */
+ int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+ AssertRC(rc2);
+ }
+ }
+
+ return rc;
+ }
+
+ /*
+ * Check if we are already fetch command parameters from the guest.
+ * If not we initialize executing a new command.
+ */
+ if (pThis->uOperationCode == 0xff)
+ {
+ pThis->uOperationCode = uVal;
+ pThis->iParameter = 0;
+
+ /* Mark host adapter as busy and clear the invalid status bit. */
+ pThis->regStatus &= ~(BL_STAT_HARDY | BL_STAT_CMDINV);
+
+ /* Get the number of bytes for parameters from the command code. */
+ switch (pThis->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:
+ pThis->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 (pThis->uDevType == DEV_AHA_1540B)
+ {
+ pThis->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:
+ pThis->cbCommandParametersLeft = 1;
+ break;
+ case BUSLOGICCOMMAND_FETCH_HOST_ADAPTER_LOCAL_RAM:
+ pThis->cbCommandParametersLeft = 2;
+ break;
+ case BUSLOGICCOMMAND_READ_BUSMASTER_CHIP_FIFO:
+ case BUSLOGICCOMMAND_WRITE_BUSMASTER_CHIP_FIFO:
+ pThis->cbCommandParametersLeft = 3;
+ break;
+ case BUSLOGICCOMMAND_SET_SCSI_SELECTION_TIMEOUT:
+ pThis->cbCommandParametersLeft = 4;
+ break;
+ case BUSLOGICCOMMAND_INITIALIZE_MAILBOX:
+ pThis->cbCommandParametersLeft = sizeof(RequestInitMbx);
+ break;
+ case BUSLOGICCOMMAND_INITIALIZE_EXTENDED_MAILBOX:
+ /* Some Adaptec drivers (ASPI4DOS.SYS) test this command. */
+ if (pThis->uDevType == DEV_AHA_1540B)
+ {
+ pThis->cbCommandParametersLeft = 0;
+ break;
+ }
+ pThis->cbCommandParametersLeft = sizeof(RequestInitializeExtendedMailbox);
+ break;
+ case BUSLOGICCOMMAND_SET_ADAPTER_OPTIONS:
+ /* There must be at least one byte following this command. */
+ pThis->cbCommandParametersLeft = 1;
+ break;
+ case BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND:
+ /* 12 bytes + variable-length CDB. */
+ pThis->cbCommandParametersLeft = 12;
+ break;
+ case BUSLOGICCOMMAND_EXT_BIOS_INFO:
+ case BUSLOGICCOMMAND_UNLOCK_MAILBOX:
+ /* Invalid commands. */
+ pThis->cbCommandParametersLeft = 0;
+ break;
+ case BUSLOGICCOMMAND_EXECUTE_MAILBOX_COMMAND: /* Should not come here anymore. */
+ default:
+ AssertMsgFailed(("Invalid operation code %#x\n", uVal));
+ }
+ }
+ else if (pThis->cbCommandParametersLeft)
+ {
+#ifndef IN_RING3
+ /* This command must be executed in R3 as it rehooks the ISA I/O port. */
+ if (pThis->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.
+ */
+ pThis->aCommandBuffer[pThis->iParameter] = uVal;
+ pThis->iParameter++;
+ pThis->cbCommandParametersLeft--;
+ }
+
+ /* Start execution of command if there are no parameters left. */
+ if (!pThis->cbCommandParametersLeft)
+ {
+ rc = buslogicProcessCommand(pDevIns, pThis);
+ 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 (pThis->uDevType == DEV_AHA_1540B)
+ break;
+ pThis->regInterrupt = uVal;
+ break;
+
+ case BUSLOGIC_REGISTER_GEOMETRY:
+ if (pThis->uDevType == DEV_AHA_1540B)
+ break;
+ pThis->regGeometry = uVal;
+ break;
+
+ default:
+ AssertMsgFailed(("Register not available\n"));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) buslogicMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, off, pv, cb);
+
+ /* the linux driver does not make use of the MMIO area. */
+ ASSERT_GUEST_MSG_FAILED(("MMIO Read: %RGp LB %u\n", off, cb));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) buslogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, off, pv, cb);
+
+ /* the linux driver does not make use of the MMIO area. */
+ ASSERT_GUEST_MSG_FAILED(("MMIO Write: %RGp LB %u: %.*Rhxs\n", off, cb, cb, pv));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+buslogicIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ unsigned iRegister = offPort % 4;
+ RT_NOREF(pvUser, cb);
+
+ ASSERT_GUEST(cb == 1);
+
+ return buslogicRegisterRead(pDevIns, pThis, iRegister, pu32);
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+buslogicIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ unsigned iRegister = offPort % 4;
+ RT_NOREF(pvUser, cb);
+
+ ASSERT_GUEST(cb == 1);
+
+ int rc = buslogicRegisterWrite(pDevIns, pThis, iRegister, (uint8_t)u32);
+
+ Log2(("#%d %s: pvUser=%#p cb=%d u32=%#x offPort=%#x rc=%Rrc\n",
+ pDevIns->iInstance, __FUNCTION__, pvUser, cb, u32, offPort, rc));
+
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Update the ISA I/O range.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param uBaseCode Encoded ISA I/O base; only low 3 bits are used.
+ */
+static int buslogicR3RegisterISARange(PPDMDEVINS pDevIns, PBUSLOGIC pThis, 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 actually changed. */
+ if (uNewBase != pThis->IOISABase)
+ {
+ /* Unmap the old range, if necessary. */
+ if (pThis->IOISABase)
+ {
+ rc = PDMDevHlpIoPortUnmap(pDevIns, pThis->hIoPortsIsa);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->IOISABase = 0; /* First mark as unregistered. */
+ pThis->uISABaseCode = ISA_BASE_DISABLED;
+
+ if (uNewBase)
+ {
+ /* Register the new range if requested. */
+ rc = PDMDevHlpIoPortMap(pDevIns, pThis->hIoPortsIsa, uNewBase);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->IOISABase = uNewBase;
+ pThis->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;
+}
+
+/**
+ * Completes a request initiated by the BIOS through the BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND command.
+ *
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param u8ScsiSts The SCSI status code.
+ */
+static void buslogicR3ReqCompleteBios(PBUSLOGIC pThis, uint8_t u8ScsiSts)
+{
+ pThis->cbReplyParametersLeft = 4;
+ pThis->aReplyBuffer[0] = pThis->aReplyBuffer[1] = 0;
+ pThis->aReplyBuffer[2] = u8ScsiSts;
+ pThis->aReplyBuffer[3] = 0;
+
+ pThis->regStatus |= BL_STAT_DIRRDY;
+}
+
+static int buslogicR3ReqComplete(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICCC pThisCC, 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);
+ buslogicR3ReqCompleteBios(pThis, u8ScsiSts);
+ }
+ 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(pDevIns, 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(pDevIns, 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 && pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) buslogicR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaPort);
+ PPDMDEVINS pDevIns = pTgtDev->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;
+}
+
+static DECLCALLBACK(size_t) buslogicR3CopySgToGuestBios(PCRTSGBUF pSgBuf, const void *pvSrc, size_t cbSrc, void *pvUser)
+{
+ PBUSLOGICCOPYARGS pArgs = (PBUSLOGICCOPYARGS)pvUser;
+ size_t cbThisCopy = RT_MIN(cbSrc, pArgs->pCmd->cbData - pArgs->cbCopied);
+ RT_NOREF(pSgBuf);
+
+ blPhysWriteUser(pArgs->pDevIns, pArgs->pThis, pArgs->pCmd->u32PhysAddrData + pArgs->cbCopied, pvSrc, cbThisCopy);
+ pArgs->cbCopied += cbThisCopy;
+ return cbThisCopy;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) buslogicR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PBUSLOGICREQ pReq = (PBUSLOGICREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq);
+
+ size_t cbCopied = 0;
+ if (RT_LIKELY(!pReq->fBIOS))
+ cbCopied = buslogicR3CopySgBufToGuest(pDevIns, PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC), pReq, pSgBuf, offDst, cbCopy);
+ else
+ {
+ BUSLOGICCOPYARGS Args;
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PESCMD pCmd = (PESCMD)pThis->aCommandBuffer;
+
+ Args.pCmd = pCmd;
+ Args.pThis = pThis;
+ Args.pDevIns = pDevIns;
+ Args.cbCopied = 0;
+ cbCopied = RTSgBufCopyToFn(pSgBuf, RT_MIN(pCmd->cbData, cbCopy), buslogicR3CopySgToGuestBios, &Args);
+ }
+ return cbCopied == cbCopy ? VINF_SUCCESS : VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+}
+
+static DECLCALLBACK(size_t) buslogicR3CopySgFromGuestBios(PCRTSGBUF pSgBuf, void *pvDst, size_t cbDst, void *pvUser)
+{
+ PBUSLOGICCOPYARGS pArgs = (PBUSLOGICCOPYARGS)pvUser;
+ size_t cbThisCopy = RT_MIN(cbDst, pArgs->pCmd->cbData - pArgs->cbCopied);
+ RT_NOREF(pSgBuf);
+
+ blPhysReadUser(pArgs->pDevIns, pArgs->pThis, pArgs->pCmd->u32PhysAddrData + pArgs->cbCopied, pvDst, cbThisCopy);
+ pArgs->cbCopied += cbThisCopy;
+ return cbThisCopy;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) buslogicR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF(hIoReq);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PBUSLOGICREQ pReq = (PBUSLOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = 0;
+ if (RT_LIKELY(!pReq->fBIOS))
+ cbCopied = buslogicR3CopySgBufFromGuest(pDevIns, PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC), pReq, pSgBuf, offSrc, cbCopy);
+ else
+ {
+ BUSLOGICCOPYARGS Args;
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PESCMD pCmd = (PESCMD)pThis->aCommandBuffer;
+
+ Args.pCmd = pCmd;
+ Args.pThis = pThis;
+ Args.pDevIns = pDevIns;
+ Args.cbCopied = 0;
+ cbCopied = RTSgBufCopyFromFn(pSgBuf, RT_MIN(pCmd->cbData, cbCopy), buslogicR3CopySgFromGuestBios, &Args);
+ }
+
+ 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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ buslogicR3ReqComplete(pDevIns, PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC), PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC),
+ (PBUSLOGICREQ)pvIoReqAlloc, rcReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) buslogicR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF(hIoReq, pvIoReqAlloc, enmState);
+ PBUSLOGICDEVICE pTgtDev = RT_FROM_MEMBER(pInterface, BUSLOGICDEVICE, IMediaExPort);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ /* Make sure the request is not accounted for so the VM can suspend successfully. */
+ uint32_t cTasksActive = ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+ if (!cTasksActive && pThisCC->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ 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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ if (pThisCC->pMediaNotify)
+ {
+ int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->pMediaNotify, pTgtDev->iLUN);
+ AssertRC(rc);
+ }
+}
+
+static int buslogicR3DeviceSCSIRequestSetup(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICCC pThisCC, 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? */
+ blPhysReadMeta(pDevIns, pThis, GCPhysAddrCCB, &CCBGuest, sizeof(CCB32));
+
+ uTargetIdCCB = pThis->fMbxIs24Bit ? CCBGuest.o.uTargetId : CCBGuest.n.uTargetId;
+ if (RT_LIKELY(uTargetIdCCB < RT_ELEMENTS(pThisCC->aDeviceStates)))
+ {
+ PBUSLOGICDEVICE pTgtDev = &pThisCC->aDeviceStates[uTargetIdCCB];
+
+#ifdef LOG_ENABLED
+ buslogicR3DumpCCBInfo(&CCBGuest, pThis->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 = pThis->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(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, NULL, cbBuf, pReq->pbSenseBuffer, cbSense, NULL,
+ &pReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ buslogicR3ReqComplete(pDevIns, pThis, pThisCC, pReq, rc);
+ }
+ else
+ buslogicR3SendIncomingMailbox(pDevIns, pThis, 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(pDevIns, pThis, 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(pDevIns, pThis, 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(PPDMDEVINS pDevIns, PBUSLOGIC pThis, RTGCPHYS GCPhysAddrCCB)
+{
+ uint8_t uTargetIdCCB;
+ CCBU CCBGuest;
+
+ blPhysReadMeta(pDevIns, pThis, GCPhysAddrCCB, &CCBGuest, sizeof(CCB32));
+
+ uTargetIdCCB = pThis->fMbxIs24Bit ? CCBGuest.o.uTargetId : CCBGuest.n.uTargetId;
+ if (RT_LIKELY(uTargetIdCCB < RT_ELEMENTS(pThis->afDevicePresent)))
+ buslogicR3SendIncomingMailbox(pDevIns, pThis, 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(pDevIns, pThis, GCPhysAddrCCB, &CCBGuest,
+ BUSLOGIC_MAILBOX_INCOMING_ADAPTER_STATUS_INVALID_COMMAND_PARAMETER,
+ BUSLOGIC_MAILBOX_INCOMING_DEVICE_STATUS_OPERATION_GOOD,
+ BUSLOGIC_MAILBOX_INCOMING_COMPLETION_WITH_ERROR);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read a mailbox from guest memory. Convert 24-bit mailboxes to
+ * 32-bit format.
+ *
+ * @returns Mailbox guest physical address.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param pMbx Pointer to the mailbox to read into.
+ */
+static RTGCPHYS buslogicR3ReadOutgoingMailbox(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PMailbox32 pMbx)
+{
+ RTGCPHYS GCMailbox;
+
+ if (pThis->fMbxIs24Bit)
+ {
+ Mailbox24 Mbx24;
+
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase + (pThis->uMailboxOutgoingPositionCurrent * sizeof(Mailbox24));
+ blPhysReadMeta(pDevIns, pThis, GCMailbox, &Mbx24, sizeof(Mailbox24));
+ pMbx->u32PhysAddrCCB = ADDR_TO_U32(Mbx24.aPhysAddrCCB);
+ pMbx->u.out.uActionCode = Mbx24.uCmdState;
+ }
+ else
+ {
+ GCMailbox = pThis->GCPhysAddrMailboxOutgoingBase + (pThis->uMailboxOutgoingPositionCurrent * sizeof(Mailbox32));
+ blPhysReadMeta(pDevIns, pThis, GCMailbox, pMbx, sizeof(Mailbox32));
+ }
+
+ return GCMailbox;
+}
+
+/**
+ * Read mailbox from the guest and execute command.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param pThisCC Pointer to the ring-3 BusLogic instance data.
+ */
+static int buslogicR3ProcessMailboxNext(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICCC pThisCC)
+{
+ RTGCPHYS GCPhysAddrMailboxCurrent;
+ Mailbox32 MailboxGuest;
+ int rc = VINF_SUCCESS;
+
+ if (!pThis->fStrictRoundRobinMode)
+ {
+ /* Search for a filled mailbox - stop if we have scanned all mailboxes. */
+ uint8_t uMailboxPosCur = pThis->uMailboxOutgoingPositionCurrent;
+
+ do
+ {
+ /* Fetch mailbox from guest memory. */
+ GCPhysAddrMailboxCurrent = buslogicR3ReadOutgoingMailbox(pDevIns, pThis, &MailboxGuest);
+
+ /* Check the next mailbox. */
+ buslogicR3OutgoingMailboxAdvance(pThis);
+ } while ( MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_FREE
+ && uMailboxPosCur != pThis->uMailboxOutgoingPositionCurrent);
+ }
+ else
+ {
+ /* Fetch mailbox from guest memory. */
+ GCPhysAddrMailboxCurrent = buslogicR3ReadOutgoingMailbox(pDevIns, pThis, &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", pThis->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 = pThis->fMbxIs24Bit ? RT_OFFSETOF(Mailbox24, uCmdState) : RT_OFFSETOF(Mailbox32, u.out.uActionCode);
+ blPhysWriteMeta(pDevIns, pThis, GCPhysAddrMailboxCurrent + uCodeOffs, &uActionCode, sizeof(uActionCode));
+
+ if (MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_START_COMMAND)
+ rc = buslogicR3DeviceSCSIRequestSetup(pDevIns, pThis, pThisCC, (RTGCPHYS)MailboxGuest.u32PhysAddrCCB);
+ else if (MailboxGuest.u.out.uActionCode == BUSLOGIC_MAILBOX_OUTGOING_ACTION_ABORT_COMMAND)
+ {
+ LogFlow(("Aborting mailbox\n"));
+ rc = buslogicR3DeviceSCSIRequestAbort(pDevIns, pThis, (RTGCPHYS)MailboxGuest.u32PhysAddrCCB);
+ }
+ else
+ AssertMsgFailed(("Invalid outgoing mailbox action code %u\n", MailboxGuest.u.out.uActionCode));
+
+ AssertRC(rc);
+
+ /* Advance to the next mailbox. */
+ if (pThis->fStrictRoundRobinMode)
+ buslogicR3OutgoingMailboxAdvance(pThis);
+
+ return rc;
+}
+
+/**
+ * Processes a SCSI request issued by the BIOS with the BUSLOGICCOMMAND_EXECUTE_SCSI_COMMAND command.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared BusLogic instance data.
+ * @param pThisCC Pointer to the ring-3 BusLogic instance data.
+ */
+static void buslogicR3ProcessBiosReq(PPDMDEVINS pDevIns, PBUSLOGIC pThis, PBUSLOGICCC pThisCC)
+{
+ PESCMD pCmd = (PESCMD)pThis->aCommandBuffer;
+
+ if (RT_LIKELY( pCmd->uTargetId < RT_ELEMENTS(pThisCC->aDeviceStates)
+ && pCmd->cbCDB <= 16))
+ {
+ PBUSLOGICDEVICE pTgtDev = &pThisCC->aDeviceStates[pCmd->uTargetId];
+
+ /* 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;
+ int rc = pTgtDev->pDrvMediaEx->pfnIoReqAlloc(pTgtDev->pDrvMediaEx, &hIoReq, (void **)&pReq,
+ 0, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->pTargetDevice = pTgtDev;
+ pReq->GCPhysAddrCCB = 0;
+ pReq->fBIOS = true;
+ pReq->hIoReq = hIoReq;
+ pReq->fIs24Bit = false;
+
+ uint32_t uLun = pCmd->uLogicalUnit;
+
+ PDMMEDIAEXIOREQSCSITXDIR enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN;
+
+ if (pCmd->uDataDirection == 2)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE;
+ else if (pCmd->uDataDirection == 1)
+ enmXferDir = PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+
+ ASMAtomicIncU32(&pTgtDev->cOutstandingRequests);
+ rc = pTgtDev->pDrvMediaEx->pfnIoReqSendScsiCmd(pTgtDev->pDrvMediaEx, pReq->hIoReq, uLun,
+ &pCmd->abCDB[0], pCmd->cbCDB,
+ enmXferDir, NULL, pCmd->cbData, NULL, 0 /*cbSense*/, NULL,
+ &pReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ buslogicR3ReqComplete(pDevIns, pThis, pThisCC, pReq, rc);
+ }
+ else
+ buslogicR3ReqCompleteBios(pThis, SCSI_STATUS_CHECK_CONDITION);
+ }
+ else
+ buslogicR3ReqCompleteBios(pThis, SCSI_STATUS_CHECK_CONDITION);
+ }
+ else
+ buslogicR3ReqCompleteBios(pThis, SCSI_STATUS_CHECK_CONDITION);
+}
+
+
+/** @callback_method_impl{FNSSMDEVLIVEEXEC} */
+static DECLCALLBACK(int) buslogicR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(uPass);
+
+ /* Save the device config. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThisCC->aDeviceStates); i++)
+ pHlp->pfnSSMPutBool(pSSM, pThisCC->aDeviceStates[i].fPresent);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/** @callback_method_impl{FNSSMDEVSAVEEXEC} */
+static DECLCALLBACK(int) buslogicR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint32_t cReqsSuspended = 0;
+
+ /* Every device first. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThisCC->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pThisCC->aDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ pHlp->pfnSSMPutBool(pSSM, pDevice->fPresent);
+ pHlp->pfnSSMPutU32(pSSM, pDevice->cOutstandingRequests);
+
+ if (pDevice->fPresent)
+ cReqsSuspended += pDevice->pDrvMediaEx->pfnIoReqGetSuspendedCount(pDevice->pDrvMediaEx);
+ }
+ /* Now the main device state. */
+ pHlp->pfnSSMPutU8 (pSSM, pThis->regStatus);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->regInterrupt);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->regGeometry);
+ pHlp->pfnSSMPutMem (pSSM, &pThis->LocalRam, sizeof(pThis->LocalRam));
+ pHlp->pfnSSMPutU8 (pSSM, pThis->uOperationCode);
+ pHlp->pfnSSMPutMem (pSSM, &pThis->aCommandBuffer, sizeof(pThis->aCommandBuffer));
+ pHlp->pfnSSMPutU8 (pSSM, pThis->iParameter);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->cbCommandParametersLeft);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fUseLocalRam);
+ pHlp->pfnSSMPutMem (pSSM, pThis->aReplyBuffer, sizeof(pThis->aReplyBuffer));
+ pHlp->pfnSSMPutU8 (pSSM, pThis->iReply);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->cbReplyParametersLeft);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fIRQEnabled);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->uISABaseCode);
+ pHlp->pfnSSMPutU32 (pSSM, pThis->cMailbox);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fMbxIs24Bit);
+ pHlp->pfnSSMPutGCPhys(pSSM, pThis->GCPhysAddrMailboxOutgoingBase);
+ pHlp->pfnSSMPutU32 (pSSM, pThis->uMailboxOutgoingPositionCurrent);
+ pHlp->pfnSSMPutU32 (pSSM, pThis->cMailboxesReady);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fNotificationSent);
+ pHlp->pfnSSMPutGCPhys(pSSM, pThis->GCPhysAddrMailboxIncomingBase);
+ pHlp->pfnSSMPutU32 (pSSM, pThis->uMailboxIncomingPositionCurrent);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fStrictRoundRobinMode);
+ pHlp->pfnSSMPutBool (pSSM, pThis->fExtendedLunCCBFormat);
+
+ pHlp->pfnSSMPutU32(pSSM, cReqsSuspended);
+
+ /* Save the physical CCB address of all suspended requests. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThisCC->aDeviceStates) && cReqsSuspended; i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pThisCC->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 (;;)
+ {
+ pHlp->pfnSSMPutU32(pSSM, (uint32_t)pReq->GCPhysAddrCCB);
+
+ cThisReqsSuspended--;
+ if (!cThisReqsSuspended)
+ break;
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pDevice->pDrvMediaEx, hIoReq,
+ &hIoReq, (void **)&pReq);
+ AssertRCBreak(rc);
+ }
+ }
+ }
+ }
+
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX);
+}
+
+/** @callback_method_impl{FNSSMDEVLOADDONE} */
+static DECLCALLBACK(int) buslogicR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ RT_NOREF(pSSM);
+
+ buslogicR3RegisterISARange(pDevIns, pThis, pThis->uISABaseCode);
+
+ /* Kick of any requests we might need to redo. */
+ if (pThisCC->cReqsRedo)
+ {
+ for (unsigned i = 0; i < pThisCC->cReqsRedo; i++)
+ {
+ int rc = buslogicR3DeviceSCSIRequestSetup(pDevIns, pThis, pThisCC, pThisCC->paGCPhysAddrCCBRedo[i]);
+ AssertRC(rc);
+ }
+
+ RTMemFree(pThisCC->paGCPhysAddrCCBRedo);
+ pThisCC->paGCPhysAddrCCBRedo = NULL;
+ pThisCC->cReqsRedo = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @callback_method_impl{FNSSMDEVLOADEXEC} */
+static DECLCALLBACK(int) buslogicR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ 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(pThisCC->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pThisCC->aDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ bool fPresent;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fPresent);
+ AssertRCReturn(rc, rc);
+ if (pDevice->fPresent != fPresent)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Target %u config mismatch: config=%RTbool state=%RTbool"), i, pDevice->fPresent, fPresent);
+
+ if (uPass == SSM_PASS_FINAL)
+ pHlp->pfnSSMGetU32V(pSSM, &pDevice->cOutstandingRequests);
+ }
+
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ /* Now the main device state. */
+ pHlp->pfnSSMGetU8V (pSSM, &pThis->regStatus);
+ pHlp->pfnSSMGetU8V (pSSM, &pThis->regInterrupt);
+ pHlp->pfnSSMGetU8V (pSSM, &pThis->regGeometry);
+ pHlp->pfnSSMGetMem (pSSM, &pThis->LocalRam, sizeof(pThis->LocalRam));
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->uOperationCode);
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_PRE_CMDBUF_RESIZE)
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCommandBuffer, sizeof(pThis->aCommandBuffer));
+ else
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCommandBuffer, BUSLOGIC_COMMAND_SIZE_OLD);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->iParameter);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->cbCommandParametersLeft);
+ pHlp->pfnSSMGetBool (pSSM, &pThis->fUseLocalRam);
+ pHlp->pfnSSMGetMem (pSSM, pThis->aReplyBuffer, sizeof(pThis->aReplyBuffer));
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->iReply);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->cbReplyParametersLeft);
+ pHlp->pfnSSMGetBool (pSSM, &pThis->fIRQEnabled);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->uISABaseCode);
+ pHlp->pfnSSMGetU32 (pSSM, &pThis->cMailbox);
+ if (uVersion > BUSLOGIC_SAVED_STATE_MINOR_PRE_24BIT_MBOX)
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fMbxIs24Bit);
+ pHlp->pfnSSMGetGCPhys(pSSM, &pThis->GCPhysAddrMailboxOutgoingBase);
+ pHlp->pfnSSMGetU32 (pSSM, &pThis->uMailboxOutgoingPositionCurrent);
+ pHlp->pfnSSMGetU32V (pSSM, &pThis->cMailboxesReady);
+ pHlp->pfnSSMGetBoolV (pSSM, &pThis->fNotificationSent);
+ pHlp->pfnSSMGetGCPhys(pSSM, &pThis->GCPhysAddrMailboxIncomingBase);
+ pHlp->pfnSSMGetU32 (pSSM, &pThis->uMailboxIncomingPositionCurrent);
+ pHlp->pfnSSMGetBool (pSSM, &pThis->fStrictRoundRobinMode);
+ pHlp->pfnSSMGetBool (pSSM, &pThis->fExtendedLunCCBFormat);
+
+ if (uVersion <= BUSLOGIC_SAVED_STATE_MINOR_PRE_VBOXSCSI_REMOVAL)
+ {
+ rc = vboxscsiR3LoadExecLegacy(pDevIns->pHlpR3, 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;
+
+ pHlp->pfnSSMGetU32(pSSM, &cTasks);
+
+ if (cTasks)
+ {
+ pThisCC->paGCPhysAddrCCBRedo = (PRTGCPHYS)RTMemAllocZ(cTasks * sizeof(RTGCPHYS));
+ if (RT_LIKELY(pThisCC->paGCPhysAddrCCBRedo))
+ {
+ pThisCC->cReqsRedo = cTasks;
+
+ for (uint32_t i = 0; i < cTasks; i++)
+ {
+ uint32_t u32PhysAddrCCB;
+
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32PhysAddrCCB);
+ AssertRCBreak(rc);
+
+ pThisCC->paGCPhysAddrCCBRedo[i] = u32PhysAddrCCB;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t u32;
+ rc = pHlp->pfnSSMGetU32(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)
+{
+ PBUSLOGICCC pThisCC = RT_FROM_MEMBER(pInterface, BUSLOGICCC, ILeds);
+ if (iLUN < BUSLOGIC_MAX_DEVICES)
+ {
+ *ppLed = &pThisCC->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)
+{
+ PBUSLOGICCC pThisCC = RT_FROM_MEMBER(pInterface, BUSLOGICCC, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+ return NULL;
+}
+
+/**
+ * @callback_method_impl{FNPDMTHREADWAKEUPDEV}
+ */
+static DECLCALLBACK(int) buslogicR3Worker(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ int rc;
+
+ ASMAtomicWriteBool(&pThisCC->fWrkThreadSleeping, true);
+ bool fNotificationSent = ASMAtomicXchgBool(&pThis->fNotificationSent, false);
+ if (!fNotificationSent)
+ {
+ Assert(ASMAtomicReadBool(&pThisCC->fWrkThreadSleeping));
+ rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, 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(&pThisCC->fWrkThreadSleeping, false);
+
+ if (ASMAtomicXchgBool(&pThis->fBiosReqPending, false))
+ buslogicR3ProcessBiosReq(pDevIns, pThis, pThisCC);
+
+ if (ASMAtomicXchgU32(&pThis->cMailboxesReady, 0))
+ {
+ /* Process mailboxes. */
+ do
+ {
+ rc = buslogicR3ProcessMailboxNext(pDevIns, pThis, pThisCC);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_NO_DATA, ("Processing mailbox failed rc=%Rrc\n", rc));
+ } while (RT_SUCCESS(rc));
+ }
+ } /* While running */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMTHREADWAKEUPDEV}
+ */
+static DECLCALLBACK(int) buslogicR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ return PDMDevHlpSUPSemEventSignal(pDevIns, 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 = PDMDEVINS_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) ? "Unknown 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=%04x ISA I/O=%RTiop MMIO=%RGp IRQ=%u ",
+ PDMDevHlpIoPortGetMappingAddress(pDevIns, pThis->hIoPortsPci), pThis->IOISABase,
+ PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio),
+ PCIDevGetInterruptLine(pDevIns->apPciDevs[0]));
+ pHlp->pfnPrintf(pHlp, "RC=%RTbool R0=%RTbool\n", pDevIns->fRCEnabled, pDevIns->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);
+
+ /* Print the previous command, if any. */
+ if (pThis->uPrevCmd != 0xff )
+ pHlp->pfnPrintf(pHlp, "Last completed command: %02X\n", pThis->uPrevCmd);
+
+ 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)
+ {
+ blPhysReadMeta(pDevIns, pThis, 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)
+ {
+ blPhysReadMeta(pDevIns, pThis, 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)
+ {
+ blPhysReadMeta(pDevIns, pThis, 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, " Incoming mailbox entries (32-bit) at %08X:\n", (uint32_t)GCMailbox);
+ for (i = 0; i < pThis->cMailbox; ++i)
+ {
+ blPhysReadMeta(pDevIns, pThis, 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->uMailboxIncomingPositionCurrent == 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)
+{
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThisCC->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pThisDevice = &pThisCC->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;
+
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for buslogicR3Suspend and buslogicR3PowerOff.
+ */
+static void buslogicR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, true);
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, buslogicR3IsAsyncSuspendOrPowerOffDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+ AssertMsg(!pThis->fNotificationSent, ("The PDM Queue should be empty at this point\n"));
+ RT_NOREF(pThis);
+ }
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThisCC->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pThisDevice = &pThisCC->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)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PBUSLOGICDEVICE pDevice = &pThisCC->aDeviceStates[iLUN];
+ Log(("%s:\n", __FUNCTION__));
+ RT_NOREF(fFlags);
+
+
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("BusLogic: Device does not support hotplugging\n"));
+
+ /*
+ * Zero some important members.
+ */
+ pThis->afDevicePresent[iLUN] = false;
+ 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 = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PBUSLOGICDEVICE pDevice = &pThisCC->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(RT_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(RT_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);
+
+ pThis->afDevicePresent[iLUN] = true;
+ pDevice->fPresent = true;
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pDevice->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pThis->afDevicePresent[iLUN] = false;
+ 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 = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ return false;
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+
+ buslogicR3HwReset(pDevIns, pThis, true);
+ return true;
+}
+
+/**
+ * @copydoc FNPDMDEVRESET
+ */
+static DECLCALLBACK(void) buslogicR3Reset(PPDMDEVINS pDevIns)
+{
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, true);
+ if (!buslogicR3AllAsyncIOIsFinished(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, buslogicR3IsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThisCC->fSignalIdle, false);
+ buslogicR3HwReset(pDevIns, pThis, true);
+ }
+}
+
+/**
+ * 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)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CritSectIntr);
+
+ if (pThis->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEvtProcess);
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) buslogicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+ PBUSLOGICCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PBUSLOGICCC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /*
+ * Init instance data (do early because of constructor).
+ */
+ pThis->hMmio = NIL_IOMMMIOHANDLE;
+ pThis->hIoPortsIsa = NIL_IOMIOPORTHANDLE;
+ pThis->hIoPortsPci = NIL_IOMIOPORTHANDLE;
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->IBase.pfnQueryInterface = buslogicR3StatusQueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = buslogicR3StatusQueryStatusLed;
+
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, 0x104b); /* BusLogic */
+ PDMPciDevSetDeviceId(pPciDev, 0x1040); /* BT-958 */
+ PDMPciDevSetCommand(pPciDev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS);
+ PDMPciDevSetRevisionId(pPciDev, 0x01);
+ PDMPciDevSetClassProg(pPciDev, 0x00); /* SCSI */
+ PDMPciDevSetClassSub(pPciDev, 0x00); /* SCSI */
+ PDMPciDevSetClassBase(pPciDev, 0x01); /* Mass storage */
+ PDMPciDevSetBaseAddress(pPciDev, 0, true /*IO*/, false /*Pref*/, false /*64-bit*/, 0x00000000);
+ PDMPciDevSetBaseAddress(pPciDev, 1, false /*IO*/, false /*Pref*/, false /*64-bit*/, 0x00000000);
+ PDMPciDevSetSubSystemVendorId(pPciDev, 0x104b);
+ PDMPciDevSetSubSystemId(pPciDev, 0x1040);
+ PDMPciDevSetInterruptLine(pPciDev, 0x00);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Bootable|" /* Keep it for legacy configs, even though it doesn't do anything anymore, see @bugref{4841}. */
+ "AdapterType|"
+ "ISACompat",
+ "");
+
+ /* Figure out the emulated device type. */
+ char szCfgStr[16];
+ int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "AdapterType", szCfgStr, sizeof(szCfgStr), "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__, szCfgStr));
+
+ /* Grok the AdapterType setting. */
+ if (!strcmp(szCfgStr, "BT-958D")) /* Default PCI device, 32-bit and 24-bit addressing. */
+ {
+ pThis->uDevType = DEV_BT_958D;
+ pThis->uDefaultISABaseCode = ISA_BASE_DISABLED;
+ }
+ else if (!strcmp(szCfgStr, "BT-545C")) /* ISA device, 24-bit addressing only. */
+ {
+ pThis->uDevType = DEV_BT_545C;
+ pThis->uIsaIrq = 11;
+ }
+ else if (!strcmp(szCfgStr, "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 = pHlp->pfnCFGMQueryStringDef(pCfg, "ISACompat", szCfgStr, sizeof(szCfgStr), "Alternate");
+ else
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "ISACompat", szCfgStr, sizeof(szCfgStr), "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__, szCfgStr));
+
+ /* Grok the ISACompat setting. */
+ if (!strcmp(szCfgStr, "Disabled"))
+ pThis->uDefaultISABaseCode = ISA_BASE_DISABLED;
+ else if (!strcmp(szCfgStr, "Primary"))
+ pThis->uDefaultISABaseCode = 0; /* I/O base at 330h. */
+ else if (!strcmp(szCfgStr, "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, pPciDev);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, 32 /*cPorts*/,
+ buslogicIOPortWrite, buslogicIOPortRead, NULL /*pvUser*/,
+ "BusLogic PCI", NULL /*paExtDescs*/, &pThis->hIoPortsPci);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 1 /*iPciRegion*/, 32 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM,
+ buslogicMMIOWrite, buslogicMMIORead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ "BusLogic MMIO", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+ }
+
+ /* Set up the compatibility I/O range. */
+ rc = PDMDevHlpIoPortCreate(pDevIns, 4 /*cPorts*/, NULL /*pPciDev*/, UINT32_MAX /*iPciRegion*/,
+ buslogicIOPortWrite, buslogicIOPortRead, NULL /*pvUser*/,
+ "BusLogic ISA", NULL /*paExtDescs*/, &pThis->hIoPortsIsa);
+ AssertRCReturn(rc, rc);
+
+ rc = buslogicR3RegisterISARange(pDevIns, pThis, pThis->uDefaultISABaseCode);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("BusLogic cannot register ISA I/O handlers"));
+
+
+ /* Init the interrupt critsect. */
+ 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 = PDMDevHlpSUPSemEventCreate(pDevIns, &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, &pThisCC->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(pThisCC->aDeviceStates); i++)
+ {
+ PBUSLOGICDEVICE pDevice = &pThisCC->aDeviceStates[i];
+
+ /* Initialize static parts of the device. */
+ pDevice->iLUN = i;
+ pDevice->pDevIns = 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;
+ RTStrPrintf(pDevice->szName, sizeof(pDevice->szName), "Device%u", i);
+
+ /* Attach SCSI driver. */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, pDevice->szName);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(RT_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(RT_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);
+
+ pThis->afDevicePresent[i] = true;
+ pDevice->fPresent = true;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pThis->afDevicePresent[i] = false;
+ pDevice->fPresent = false;
+ pDevice->pDrvBase = NULL;
+ pDevice->pDrvMedia = NULL;
+ pDevice->pDrvMediaEx = NULL;
+ rc = VINF_SUCCESS;
+ Log(("BusLogic: no driver attached to device %s\n", pDevice->szName));
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("BusLogic: Failed to attach %s\n", pDevice->szName));
+ return rc;
+ }
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else
+ AssertMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc),
+ 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(pDevIns, pThis, true);
+ AssertMsgRC(rc, ("hardware reset of BusLogic host adapter failed rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) buslogicRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PBUSLOGIC pThis = PDMDEVINS_2_DATA(pDevIns, PBUSLOGIC);
+
+ if (!pThis->uIsaIrq)
+ {
+ int rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsPci, buslogicIOPortWrite, buslogicIOPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, buslogicMMIOWrite, buslogicMMIORead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ }
+
+ int rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsIsa, buslogicIOPortWrite, buslogicIOPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceBusLogic =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "buslogic",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | 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,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(BUSLOGIC),
+ /* .cbInstanceCC = */ sizeof(BUSLOGICCC),
+ /* .cbInstanceRC = */ sizeof(BUSLOGICRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "BusLogic BT-958 SCSI host adapter.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ buslogicR3Construct,
+ /* .pfnDestruct = */ buslogicR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ buslogicR3Reset,
+ /* .pfnSuspend = */ buslogicR3Suspend,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ buslogicR3Attach,
+ /* .pfnDetach = */ buslogicR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ buslogicR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ buslogicRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ buslogicRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#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..bed747af
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevFdc.cpp
@@ -0,0 +1,3208 @@
+/* $Id: DevFdc.cpp $ */
+/** @file
+ * VBox storage devices - Floppy disk controller
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ * --------------------------------------------------------------------
+ *
+ * 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 <VBox/AssertGuest.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name FDC saved state versions
+ * @{ */
+#define FDC_SAVESTATE_CURRENT 3 /**< Current version. */
+#define FDC_SAVESTATE_PRE_DELAY 2 /**< Pre IRQDelay. */
+#define FDC_SAVESTATE_OLD 1 /**< The original saved state. */
+/** @}*/
+
+#define MAX_FD 2
+
+
+/********************************************************/
+/* debug Floppy devices */
+/* #define DEBUG_FLOPPY */
+
+#ifdef LOG_ENABLED
+# define FLOPPY_DPRINTF(...) Log(("floppy: " __VA_ARGS__))
+#else
+# define FLOPPY_DPRINTF(...) do { } while (0)
+#endif
+
+#define FLOPPY_ERROR RTLogPrintf
+
+typedef struct fdctrl_t fdctrl_t;
+
+/********************************************************/
+/* Floppy drive emulation */
+
+/* Drive selection note:
+ * For many commands, the FDC can select one of four drives through the
+ * second command byte. The Digital Output Register (DOR) can also select
+ * one of four drives. On PCs, the FDC drive selection is ignored, but
+ * should be reflected back in command status. Only the DOR drive selection
+ * is effective; on old PCs with a discrete NEC uPD765 or similar, the FDC
+ * drive selection signals (US0/US1) are not connected at all.
+ * NB: A drive is actually selected only when its motor on bit in the DOR
+ * is also set. It is possible to have no drive selected.
+ *
+ * The FDC cur_drv field tracks the drive the FDC thinks is selected, but
+ * the DOR is used for actual drive selection.
+ */
+
+#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 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 */
+ FDRIVE_DRV_FAKE_15_6 = 0x0e, /* Fake 15.6 MB drive. */
+ FDRIVE_DRV_FAKE_63_5 = 0x0f /* Fake 63.5 MB drive. */
+} 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 {
+ /** 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;
+ /* 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 */
+ 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. */
+ 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 h=%d c=%02x =s%02x) -> 5\n",
+ 1, NUM_SIDES(drv) - 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 h=%d c=%02x s=%02x (max=%d h=%d c=%02x s=%02x) -> 2\n",
+ head, track, sect,
+ 1, NUM_SIDES(drv) - 1, drv->max_track, drv->last_sect);
+ return 2;
+ }
+ if (sect > drv->last_sect || sect < 1) {
+ FLOPPY_DPRINTF("try to read h=%d c=%02x s=%02x (max=%d h=%d c=%02x s=%02x) -> 3\n",
+ head, track, sect,
+ 1, NUM_SIDES(drv) - 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;
+ 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 const fd_formats[] = {
+ /* First entry is default format */
+ /* 1.44 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB 3\"1/2", },
+ /* 2.88 MB 3"1/2 floppy disks */
+ { FDRIVE_DRV_288, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB 3\"1/2", },
+ { FDRIVE_DRV_288, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB 3\"1/2", },
+ { FDRIVE_DRV_288, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB 3\"1/2", },
+ { FDRIVE_DRV_288, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB 3\"1/2", },
+ { FDRIVE_DRV_288, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB 3\"1/2", },
+ /* 720 kB 3"1/2 floppy disks */
+ { FDRIVE_DRV_144, 9, 80, 1, FDRIVE_RATE_250K, "720 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 10, 80, 1, FDRIVE_RATE_250K, "800 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 10, 82, 1, FDRIVE_RATE_250K, "820 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 10, 83, 1, FDRIVE_RATE_250K, "830 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB 3\"1/2", },
+ /* 1.2 MB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, 15, 80, 1, FDRIVE_RATE_500K, "1.2 MB 5\"1/4", },
+ { FDRIVE_DRV_120, 16, 80, 1, FDRIVE_RATE_500K, "1.28 MB 5\"1/4", }, /* CP Backup 5.25" HD */
+ { FDRIVE_DRV_120, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB 5\"1/4", },
+ { FDRIVE_DRV_120, 18, 82, 1, FDRIVE_RATE_500K, "1.48 MB 5\"1/4", },
+ { FDRIVE_DRV_120, 18, 83, 1, FDRIVE_RATE_500K, "1.49 MB 5\"1/4", },
+ { FDRIVE_DRV_120, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB 5\"1/4", },
+ /* 720 kB 5"1/4 floppy disks */
+ { FDRIVE_DRV_120, 9, 80, 1, FDRIVE_RATE_300K, "720 kB 5\"1/4", },
+ { FDRIVE_DRV_120, 11, 80, 1, FDRIVE_RATE_300K, "880 kB 5\"1/4", },
+ /* 360 kB 5"1/4 floppy disks (newer 9-sector formats) */
+ { FDRIVE_DRV_120, 9, 40, 1, FDRIVE_RATE_300K, "360 kB 5\"1/4", },
+ { FDRIVE_DRV_120, 9, 40, 0, FDRIVE_RATE_300K, "180 kB 5\"1/4", },
+ { FDRIVE_DRV_120, 10, 40, 1, FDRIVE_RATE_300K, "400 kB 5\"1/4", }, /* CP Backup 5.25" DD */
+ { FDRIVE_DRV_120, 10, 41, 1, FDRIVE_RATE_300K, "410 kB 5\"1/4", },
+ { FDRIVE_DRV_120, 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, 8, 40, 1, FDRIVE_RATE_300K, "320 kB 5\"1/4", },
+ { FDRIVE_DRV_120, 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, 15, 80, 1, FDRIVE_RATE_500K, "1.2 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 16, 80, 1, FDRIVE_RATE_500K, "1.28 MB 3\"1/2", },
+ { FDRIVE_DRV_144, 10, 40, 1, FDRIVE_RATE_300K, "400 kB 3\"1/2", }, /* CP Backup 5.25" DD */
+ { FDRIVE_DRV_144, 9, 40, 1, FDRIVE_RATE_300K, "360 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 9, 40, 0, FDRIVE_RATE_300K, "180 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 8, 40, 1, FDRIVE_RATE_300K, "320 kB 3\"1/2", },
+ { FDRIVE_DRV_144, 8, 40, 0, FDRIVE_RATE_300K, "160 kB 3\"1/2", },
+ /* 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, 63, 255, 1, FDRIVE_RATE_1M, "15.6 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 9, 80, 1, FDRIVE_RATE_250K, "720 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 10, 80, 1, FDRIVE_RATE_250K, "800 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 10, 82, 1, FDRIVE_RATE_250K, "820 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 10, 83, 1, FDRIVE_RATE_250K, "830 kB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB fake 15.6", },
+ { FDRIVE_DRV_FAKE_15_6, 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, 255, 255, 1, FDRIVE_RATE_1M, "63.5 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 63, 255, 1, FDRIVE_RATE_1M, "15.6 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 36, 80, 1, FDRIVE_RATE_1M, "2.88 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 39, 80, 1, FDRIVE_RATE_1M, "3.12 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 40, 80, 1, FDRIVE_RATE_1M, "3.2 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 44, 80, 1, FDRIVE_RATE_1M, "3.52 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 48, 80, 1, FDRIVE_RATE_1M, "3.84 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 18, 80, 1, FDRIVE_RATE_500K, "1.44 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 20, 80, 1, FDRIVE_RATE_500K, "1.6 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 21, 80, 1, FDRIVE_RATE_500K, "1.68 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 21, 82, 1, FDRIVE_RATE_500K, "1.72 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 21, 83, 1, FDRIVE_RATE_500K, "1.74 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 22, 80, 1, FDRIVE_RATE_500K, "1.76 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 23, 80, 1, FDRIVE_RATE_500K, "1.84 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 24, 80, 1, FDRIVE_RATE_500K, "1.92 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 9, 80, 1, FDRIVE_RATE_250K, "720 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 10, 80, 1, FDRIVE_RATE_250K, "800 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 10, 82, 1, FDRIVE_RATE_250K, "820 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 10, 83, 1, FDRIVE_RATE_250K, "830 kB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 13, 80, 1, FDRIVE_RATE_250K, "1.04 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 14, 80, 1, FDRIVE_RATE_250K, "1.12 MB fake 63.5", },
+ { FDRIVE_DRV_FAKE_63_5, 9, 80, 0, FDRIVE_RATE_250K, "360 kB fake 63.5", },
+ /* end */
+ { FDRIVE_DRV_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");
+ if ( drv->pDrvMedia
+ && drv->pDrvMount
+ && drv->pDrvMount->pfnIsMounted (drv->pDrvMount)) {
+ ro = drv->pDrvMedia->pfnIsReadOnly (drv->pDrvMedia);
+ nb_heads = max_track = last_sect = 0;
+ 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 {
+ uint64_t size2 = drv->pDrvMedia->pfnGetSize (drv->pDrvMedia);
+ nb_sectors = size2 / FD_SECTOR_LEN;
+ 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;
+ drv->media_rate = parse->rate;
+ 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);
+static fdrive_t *get_cur_drv(fdctrl_t *fdctrl);
+
+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_DRV_SELMASK = 0x03,
+#else
+ FD_DRV_SELMASK = 0x01,
+#endif
+};
+
+enum {
+ FD_DOR_SELMASK = 0x03, /* Always two bits regardless of FD_DRV_SELMASK. */
+ 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)
+
+/**
+ * Floppy controller state.
+ *
+ * @implements PDMILEDPORTS
+ */
+struct fdctrl_t {
+ /* Controller's identification */
+ uint8_t version;
+ /* HW */
+ uint8_t irq_lvl;
+ uint8_t dma_chann;
+ uint16_t io_base;
+ /* Controller state */
+ TMTIMERHANDLE hResultTimer;
+
+ /* Interrupt delay timers. */
+ TMTIMERHANDLE hXferDelayTimer;
+ TMTIMERHANDLE hIrqDelayTimer;
+ uint16_t uIrqDelayMsec;
+ uint8_t st0;
+ uint8_t st1;
+ uint8_t st2;
+
+ 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 */
+ /* Debugging only */
+ uint8_t cur_cmd;
+ uint8_t prev_cmd;
+ /* 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;
+ /** 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;
+
+ /** I/O ports: 0x3f0 */
+ IOMIOPORTHANDLE hIoPorts0;
+ /** I/O ports: 0x3f1..0x3f5 */
+ IOMIOPORTHANDLE hIoPorts1;
+ /** I/O port: 0x3f7 */
+ IOMIOPORTHANDLE hIoPorts2;
+};
+
+static uint32_t fdctrl_read (fdctrl_t *fdctrl, uint32_t reg)
+{
+ 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_MAX;
+ break;
+ }
+ FLOPPY_DPRINTF("read reg%d: 0x%02x\n", reg & 7, retval);
+
+ return retval;
+}
+
+static void fdctrl_write (fdctrl_t *fdctrl, uint32_t reg, uint32_t value)
+{
+ 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");
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 0);
+ fdctrl->sra &= ~FD_SRA_INTPEND;
+}
+
+static void fdctrl_raise_irq_now(fdctrl_t *fdctrl, uint8_t status0)
+{
+ if (!(fdctrl->sra & FD_SRA_INTPEND)) {
+ FLOPPY_DPRINTF("Raising interrupt...\n");
+ PDMDevHlpISASetIrq (fdctrl->pDevIns, fdctrl->irq_lvl, 1);
+ 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);
+}
+
+static void fdctrl_raise_irq(fdctrl_t *fdctrl, uint8_t status0)
+{
+ if (!fdctrl->uIrqDelayMsec)
+ {
+ /* If not IRQ delay needed, trigger the interrupt now. */
+ fdctrl_raise_irq_now(fdctrl, status0);
+ }
+ else
+ {
+ /* Otherwise schedule completion after a short while. */
+ fdctrl->st0 = status0;
+ PDMDevHlpTimerSetMillies(fdctrl->pDevIns, fdctrl->hIrqDelayTimer, fdctrl->uIrqDelayMsec);
+ }
+}
+
+/* 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;
+ if (!fdctrl->drives[1].pDrvMedia)
+ 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->dor & FD_DRV_SELMASK) {
+ 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;
+
+ 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;
+ }
+ }
+
+ 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)
+{
+ return drv->dsk_chg;
+}
+
+/* Digital input register : 0x07 (read-only) */
+static uint32_t fdctrl_read_dir(fdctrl_t *fdctrl)
+{
+ uint32_t retval = 0;
+
+ /* 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->dor & FD_DOR_SELMASK))))
+ 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);
+ fdctrl->prev_cmd = fdctrl->cur_cmd;
+ fdctrl->cur_cmd = 0;
+}
+
+/* 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_now(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)) {
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 0);
+ }
+ fdctrl->msr |= FD_MSR_RQM | FD_MSR_DIO;
+ fdctrl->msr &= ~FD_MSR_NONDMA;
+ fdctrl_set_fifo(fdctrl, 7, 1);
+}
+
+static void fdctrl_stop_transfer(fdctrl_t *fdctrl, uint8_t status0,
+ uint8_t status1, uint8_t status2)
+{
+ if (!fdctrl->uIrqDelayMsec)
+ {
+ /* If not IRQ delay needed, just stop the transfer and trigger IRQ now. */
+ fdctrl_stop_transfer_now(fdctrl, status0, status1, status2);
+ }
+ else
+ {
+ /* Otherwise schedule completion after a short while. */
+ fdctrl->st0 = status0;
+ fdctrl->st1 = status1;
+ fdctrl->st2 = status2;
+ PDMDevHlpTimerSetMillies(fdctrl->pDevIns, fdctrl->hXferDelayTimer, fdctrl->uIrqDelayMsec);
+ }
+}
+
+/* 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.
+ */
+ 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;
+ }
+ /* 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 */
+ dma_mode = PDMDevHlpDMAGetChannelMode (fdctrl->pDevIns, fdctrl->dma_chann);
+ 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...
+ */
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 1);
+ PDMDevHlpDMASchedule (fdctrl->pDevIns);
+ 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 */
+ dma_mode = PDMDevHlpDMAGetChannelMode (fdctrl->pDevIns, fdctrl->dma_chann);
+ 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...
+ */
+ PDMDevHlpDMASetDREQ (fdctrl->pDevIns, fdctrl->dma_chann, 1);
+ PDMDevHlpDMASchedule (fdctrl->pDevIns);
+ 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);
+}
+
+/* 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;
+}
+
+/**
+ * @callback_method_impl{FNDMATRANSFERHANDLER, handlers for DMA transfers}
+ */
+static DECLCALLBACK(uint32_t) fdctrl_transfer_handler(PPDMDEVINS pDevIns, void *pvUser,
+ unsigned uChannel, uint32_t off, uint32_t cb)
+{
+ RT_NOREF(pDevIns, off);
+ fdctrl_t *fdctrl;
+ fdrive_t *cur_drv;
+ int rc;
+ uint32_t len = 0;
+ uint32_t start_pos, rel_pos;
+ uint8_t status0 = 0x00, status1 = 0x00, status2 = 0x00;
+
+ fdctrl = (fdctrl_t *)pvUser;
+ 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 (cb > fdctrl->data_len)
+ cb = fdctrl->data_len;
+ if (cur_drv->pDrvMedia == NULL)
+ {
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ rel_pos = fdctrl->data_pos % FD_SECTOR_LEN;
+ for (start_pos = fdctrl->data_pos; fdctrl->data_pos < cb;) {
+ len = cb - 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, cb, 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 */
+ rc = blk_read(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+ {
+ 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 */
+ {
+ uint32_t read;
+ int rc2 = PDMDevHlpDMAWriteMemory(fdctrl->pDevIns, uChannel,
+ fdctrl->fifo + rel_pos,
+ fdctrl->data_pos,
+ len, &read);
+ AssertMsgRC (rc2, ("DMAWriteMemory -> %Rrc\n", rc2));
+ }
+ break;
+ case FD_DIR_WRITE:
+ /* WRITE commands */
+ {
+ uint32_t written;
+ int rc2 = PDMDevHlpDMAReadMemory(fdctrl->pDevIns, uChannel,
+ 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))
+ {
+ 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;
+ 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, uChannel,
+ 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;
+ default:
+ /* SCAN commands */
+ {
+ uint8_t tmpbuf[FD_SECTOR_LEN];
+ int ret;
+ uint32_t read;
+ int rc2 = PDMDevHlpDMAReadMemory(fdctrl->pDevIns, uChannel, tmpbuf,
+ fdctrl->data_pos, len, &read);
+ AssertMsg(RT_SUCCESS(rc2), ("DMAReadMemory -> %Rrc2\n", rc2)); NOREF(rc2);
+ 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;
+ int rc;
+
+ 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 (cur_drv->pDrvMedia == NULL)
+ {
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ } else 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;
+ }
+
+ rc = blk_read(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ if (RT_FAILURE(rc))
+ {
+ 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;
+ int ok = 0, rc;
+
+ 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);
+ 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) {
+ 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;
+ PDMDevHlpTimerSetMillies(fdctrl->pDevIns, fdctrl->hResultTimer, 1000 / 50);
+}
+
+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);
+
+ /* 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));
+}
+
+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, 0x9f, "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 (cur_drv->pDrvMedia == NULL)
+ {
+ if (fdctrl->data_dir == FD_DIR_WRITE)
+ fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK, 0x00, 0x00);
+ else
+ fdctrl_stop_transfer_now(fdctrl, FD_SR0_ABNTERM, 0x00, 0x00);
+ } else if (pos == FD_SECTOR_LEN - 1 ||
+ fdctrl->data_pos == fdctrl->data_len) {
+ blk_write(cur_drv, fd_sector(cur_drv), fdctrl->fifo, 1);
+ }
+ /* 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;
+ fdctrl->cur_cmd = value & 0xff;
+ }
+
+ 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);
+ }
+}
+
+
+/* -=-=-=-=-=-=-=-=- Timer Callback -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) fdcTimerCallback(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ fdctrl_t *fdctrl = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ fdrive_t *cur_drv = get_cur_drv(fdctrl);
+ RT_NOREF(hTimer, pvUser);
+
+ /* 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! */
+ 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_now(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_now(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_now(fdctrl, FD_SR0_ABNTERM, FD_SR1_MA | FD_SR1_ND, FD_SR2_MD);
+ }
+ else
+ fdctrl_stop_transfer_now(fdctrl, 0x00, 0x00, 0x00);
+}
+
+
+/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT, Handling 0x3f0 accesses.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort0Write(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pvUser);
+
+ if (cb == 1)
+ fdctrl_write(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), offPort, u32);
+ else
+ ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN, Handling 0x3f0 accesses.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort0Read(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser);
+
+ if (cb == 1)
+ {
+ *pu32 = fdctrl_read(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), offPort);
+ return VINF_SUCCESS;
+ }
+ return VERR_IOM_IOPORT_UNUSED;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT, Handling 0x3f1..0x3f5 accesses.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort1Write(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pvUser);
+
+ if (cb == 1)
+ fdctrl_write(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), offPort + 1, u32);
+ else
+ ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) fdcTransferDelayTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ fdctrl_t *fdctrl = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ RT_NOREF(pvUser, hTimer);
+ fdctrl_stop_transfer_now(fdctrl, fdctrl->st0, fdctrl->st1, fdctrl->st2);
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) fdcIrqDelayTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ fdctrl_t *fdctrl = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ RT_NOREF(pvUser, hTimer);
+ fdctrl_raise_irq_now(fdctrl, fdctrl->st0);
+}
+
+
+
+/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN, Handling 0x3f1..0x3f5 accesses.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort1Read(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser);
+
+ if (cb == 1)
+ {
+ *pu32 = fdctrl_read(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), offPort + 1);
+ return VINF_SUCCESS;
+ }
+ return VERR_IOM_IOPORT_UNUSED;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT, Handling 0x3f7 access.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort2Write(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(offPort, pvUser);
+ Assert(offPort == 0);
+
+ if (cb == 1)
+ fdctrl_write(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), 7, u32);
+ else
+ ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN, Handling 0x3f7 access.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) fdcIoPort2Read(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser, offPort);
+ Assert(offPort == 0);
+
+ if (cb == 1)
+ {
+ *pu32 = fdctrl_read(PDMDEVINS_2_DATA(pDevIns, fdctrl_t *), 7);
+ return VINF_SUCCESS;
+ }
+ return VERR_IOM_IOPORT_UNUSED;
+}
+
+
+/* -=-=-=-=-=-=-=-=- Debugger callback -=-=-=-=-=-=-=-=- */
+
+/**
+ * FDC debugger info callback.
+ *
+ * @param pDevIns The device instance.
+ * @param pHlp The output helpers.
+ * @param pszArgs The arguments.
+ */
+static DECLCALLBACK(void) fdcInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ fdctrl_t *pThis = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ unsigned i;
+ bool fVerbose = false;
+
+ /* Parse arguments. */
+ if (pszArgs)
+ fVerbose = strstr(pszArgs, "verbose") != NULL;
+
+ /* Show basic information. */
+ pHlp->pfnPrintf(pHlp, "%s#%d: ",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance);
+ pHlp->pfnPrintf(pHlp, "I/O=%X IRQ=%u DMA=%u ",
+ pThis->io_base,
+ pThis->irq_lvl,
+ pThis->dma_chann);
+ pHlp->pfnPrintf(pHlp, "RC=%RTbool R0=%RTbool\n", pDevIns->fRCEnabled, pDevIns->fR0Enabled);
+
+ /* Print register contents. */
+ pHlp->pfnPrintf(pHlp, "Registers: MSR=%02X DSR=%02X DOR=%02X\n",
+ pThis->msr, pThis->dsr, pThis->dor);
+ pHlp->pfnPrintf(pHlp, " DIR=%02X\n",
+ fdctrl_read_dir(pThis));
+
+ /* Print the current command, if any. */
+ if (pThis->cur_cmd)
+ pHlp->pfnPrintf(pHlp, "Curr cmd: %02X (%s)\n",
+ pThis->cur_cmd,
+ handlers[command_to_handler[pThis->cur_cmd]].name);
+ if (pThis->prev_cmd)
+ pHlp->pfnPrintf(pHlp, "Prev cmd: %02X (%s)\n",
+ pThis->prev_cmd,
+ handlers[command_to_handler[pThis->prev_cmd]].name);
+
+
+ for (i = 0; i < pThis->num_floppies; ++i)
+ {
+ fdrive_t *drv = &pThis->drives[i];
+ pHlp->pfnPrintf(pHlp, " Drive %u state:\n", i);
+ pHlp->pfnPrintf(pHlp, " Medium : %u tracks, %u sectors\n",
+ drv->max_track,
+ drv->last_sect);
+ pHlp->pfnPrintf(pHlp, " Current: track %u, head %u, sector %u\n",
+ drv->track,
+ drv->head,
+ drv->sect);
+ }
+}
+
+
+/* -=-=-=-=-=-=-=-=- Saved state -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) fdcSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ fdctrl_t *pThis = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ unsigned int i;
+ int rc;
+
+ /* Save the FDC I/O registers... */
+ pHlp->pfnSSMPutU8(pSSM, pThis->sra);
+ pHlp->pfnSSMPutU8(pSSM, pThis->srb);
+ pHlp->pfnSSMPutU8(pSSM, pThis->dor);
+ pHlp->pfnSSMPutU8(pSSM, pThis->tdr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->dsr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->msr);
+ /* ...the status registers... */
+ pHlp->pfnSSMPutU8(pSSM, pThis->status0);
+ pHlp->pfnSSMPutU8(pSSM, pThis->status1);
+ pHlp->pfnSSMPutU8(pSSM, pThis->status2);
+ /* ...the command FIFO... */
+ pHlp->pfnSSMPutU32(pSSM, sizeof(pThis->fifo));
+ pHlp->pfnSSMPutMem(pSSM, &pThis->fifo, sizeof(pThis->fifo));
+ pHlp->pfnSSMPutU32(pSSM, pThis->data_pos);
+ pHlp->pfnSSMPutU32(pSSM, pThis->data_len);
+ pHlp->pfnSSMPutU8(pSSM, pThis->data_state);
+ pHlp->pfnSSMPutU8(pSSM, pThis->data_dir);
+ /* ...and miscellaneous internal FDC state. */
+ pHlp->pfnSSMPutU8(pSSM, pThis->reset_sensei);
+ pHlp->pfnSSMPutU8(pSSM, pThis->eot);
+ pHlp->pfnSSMPutU8(pSSM, pThis->timer0);
+ pHlp->pfnSSMPutU8(pSSM, pThis->timer1);
+ pHlp->pfnSSMPutU8(pSSM, pThis->precomp_trk);
+ pHlp->pfnSSMPutU8(pSSM, pThis->config);
+ pHlp->pfnSSMPutU8(pSSM, pThis->lock);
+ pHlp->pfnSSMPutU8(pSSM, pThis->pwrd);
+ pHlp->pfnSSMPutU8(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.
+ */
+ pHlp->pfnSSMPutU8(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];
+
+ pHlp->pfnSSMPutMem(pSSM, &d->Led, sizeof(d->Led));
+ pHlp->pfnSSMPutU32(pSSM, d->drive);
+ pHlp->pfnSSMPutU8(pSSM, d->dsk_chg);
+ pHlp->pfnSSMPutU8(pSSM, d->perpendicular);
+ pHlp->pfnSSMPutU8(pSSM, d->head);
+ pHlp->pfnSSMPutU8(pSSM, d->track);
+ pHlp->pfnSSMPutU8(pSSM, d->sect);
+ }
+ rc = pHlp->pfnTimerSave(pDevIns, pThis->hXferDelayTimer, pSSM);
+ AssertRCReturn(rc, rc);
+ rc = pHlp->pfnTimerSave(pDevIns, pThis->hIrqDelayTimer, pSSM);
+ AssertRCReturn(rc, rc);
+ return pHlp->pfnTimerSave(pDevIns, pThis->hResultTimer, pSSM);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) fdcLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ fdctrl_t *pThis = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ unsigned int i;
+ uint32_t val32;
+ uint8_t val8;
+ int rc;
+
+ if (uVersion > FDC_SAVESTATE_CURRENT)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ if (uVersion > FDC_SAVESTATE_OLD)
+ {
+ /* Load the FDC I/O registers... */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->sra);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->srb);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->dor);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->tdr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->dsr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->msr);
+ /* ...the status registers... */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->status0);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->status1);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->status2);
+ /* ...the command FIFO, if the size matches... */
+ rc = pHlp->pfnSSMGetU32(pSSM, &val32);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(sizeof(pThis->fifo) == val32,
+ ("The size of FIFO in saved state doesn't match!\n"),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->fifo, sizeof(pThis->fifo));
+ pHlp->pfnSSMGetU32(pSSM, &pThis->data_pos);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->data_len);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->data_state);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->data_dir);
+ /* ...and miscellaneous internal FDC state. */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->reset_sensei);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->eot);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->timer0);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->timer1);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->precomp_trk);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->config);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->lock);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->pwrd);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->version);
+
+ /* Validate the number of drives. */
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->num_floppies);
+ AssertRCReturn(rc, rc);
+ 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];
+
+ pHlp->pfnSSMGetMem(pSSM, &d->Led, sizeof(d->Led));
+ rc = pHlp->pfnSSMGetU32(pSSM, &val32);
+ AssertRCReturn(rc, rc);
+ d->drive = (fdrive_type_t)val32;
+ pHlp->pfnSSMGetU8(pSSM, &d->dsk_chg);
+ pHlp->pfnSSMGetU8(pSSM, &d->perpendicular);
+ pHlp->pfnSSMGetU8(pSSM, &d->head);
+ pHlp->pfnSSMGetU8(pSSM, &d->track);
+ pHlp->pfnSSMGetU8(pSSM, &d->sect);
+ }
+
+ if (uVersion > FDC_SAVESTATE_PRE_DELAY)
+ {
+ pHlp->pfnTimerLoad(pDevIns, pThis->hXferDelayTimer, pSSM);
+ pHlp->pfnTimerLoad(pDevIns, pThis->hIrqDelayTimer, pSSM);
+ }
+ }
+ else if (uVersion == FDC_SAVESTATE_OLD)
+ {
+ /* 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.
+ */
+
+ /* 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. */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->version);
+ /* Toss IRQ level, DMA channel, I/O base, and state. */
+ pHlp->pfnSSMGetU8(pSSM, &val8);
+ pHlp->pfnSSMGetU8(pSSM, &val8);
+ pHlp->pfnSSMGetU32(pSSM, &val32);
+ pHlp->pfnSSMGetU8(pSSM, &val8);
+ /* Translate dma_en. */
+ rc = pHlp->pfnSSMGetU8(pSSM, &val8);
+ AssertRCReturn(rc, rc);
+ if (val8)
+ pThis->dor |= FD_DOR_DMAEN;
+ pHlp->pfnSSMGetU8(pSSM, &pThis->cur_drv);
+ /* Translate bootsel. */
+ rc = pHlp->pfnSSMGetU8(pSSM, &val8);
+ AssertRCReturn(rc, rc);
+ pThis->tdr |= val8 << 2;
+ pHlp->pfnSSMGetMem(pSSM, &pThis->fifo, FD_SECTOR_LEN);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->data_pos);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->data_len);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->data_state);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->data_dir);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->status0);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->eot);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->timer0);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->timer1);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->precomp_trk);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->config);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->lock);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->pwrd);
+
+ for (i = 0; i < 2; ++i)
+ {
+ fdrive_t *d = &pThis->drives[i];
+
+ pHlp->pfnSSMGetMem(pSSM, &d->Led, sizeof (d->Led));
+ rc = pHlp->pfnSSMGetU32(pSSM, &val32);
+ d->drive = (fdrive_type_t)val32;
+ AssertRCReturn(rc, rc);
+ pHlp->pfnSSMGetU32(pSSM, &val32); /* Toss drflags */
+ pHlp->pfnSSMGetU8(pSSM, &d->perpendicular);
+ pHlp->pfnSSMGetU8(pSSM, &d->head);
+ pHlp->pfnSSMGetU8(pSSM, &d->track);
+ pHlp->pfnSSMGetU8(pSSM, &d->sect);
+ pHlp->pfnSSMGetU8(pSSM, &val8); /* Toss dir, rw */
+ pHlp->pfnSSMGetU8(pSSM, &val8);
+ rc = pHlp->pfnSSMGetU32(pSSM, &val32);
+ AssertRCReturn(rc, rc);
+ d->flags = (fdrive_flags_t)val32;
+ pHlp->pfnSSMGetU8(pSSM, &d->last_sect);
+ pHlp->pfnSSMGetU8(pSSM, &d->max_track);
+ pHlp->pfnSSMGetU16(pSSM, &d->bps);
+ pHlp->pfnSSMGetU8(pSSM, &d->ro);
+ }
+ }
+ else
+ AssertFailedReturn(VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ return pHlp->pfnTimerLoad(pDevIns, pThis->hResultTimer, 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 = PDMDEVINS_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 = PDMDEVINS_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 = PDMDEVINS_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)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ fdctrl_t *pThis = PDMDEVINS_2_DATA(pDevIns, fdctrl_t *);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+
+ RT_NOREF(iInstance);
+ Assert(iInstance == 0);
+
+ /*
+ * Validate configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|MemMapped|IOBase|StatusA|IRQDelay", "");
+
+ /*
+ * Read the configuration.
+ */
+ rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pThis->irq_lvl, 6);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U8 IRQ, rc=%Rrc\n", rc), rc);
+
+ rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pThis->dma_chann, 2);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U8 DMA, rc=%Rrc\n", rc), rc);
+
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "IOBase", &pThis->io_base, 0x3f0);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U16 IOBase, rc=%Rrc\n", rc), rc);
+
+ bool fMemMapped;
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "MemMapped", &fMemMapped, false);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read bool value MemMapped rc=%Rrc\n", rc), rc);
+
+ uint16_t uIrqDelay;
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "IRQDelay", &uIrqDelay, 0);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read U16 IRQDelay, rc=%Rrc\n", rc), rc);
+
+ bool fStatusA;
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "StatusA", &fStatusA, false);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to read bool value fStatusA rc=%Rrc\n", rc), rc);
+
+ /*
+ * Initialize data.
+ */
+ LogFlow(("fdcConstruct: irq_lvl=%d dma_chann=%d io_base=%#x\n", pThis->irq_lvl, pThis->dma_chann, pThis->io_base));
+ pThis->pDevIns = pDevIns;
+ pThis->version = 0x90; /* Intel 82078 controller */
+ pThis->config = FD_CONFIG_EIS | FD_CONFIG_EFIFO; /* Implicit seek, polling & FIFO enabled */
+ pThis->num_floppies = MAX_FD;
+ pThis->hIoPorts0 = NIL_IOMMMIOHANDLE;
+ pThis->hIoPorts1 = NIL_IOMMMIOHANDLE;
+ pThis->hIoPorts2 = NIL_IOMMMIOHANDLE;
+
+ /* Fill 'command_to_handler' lookup table */
+ for (int ii = RT_ELEMENTS(handlers) - 1; ii >= 0; ii--)
+ for (unsigned 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 (unsigned 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 = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, fdcTimerCallback, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
+ "FDC Timer", &pThis->hResultTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the transfer delay timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, fdcTransferDelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
+ "FDC Transfer Delay", &pThis->hXferDelayTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the IRQ delay timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, fdcIrqDelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
+ "FDC IRQ Delay", &pThis->hIrqDelayTimer);
+ AssertRCReturn(rc, rc);
+
+ pThis->uIrqDelayMsec = uIrqDelay;
+
+ /*
+ * Register DMA channel.
+ */
+ if (pThis->dma_chann != 0xff)
+ {
+ rc = PDMDevHlpDMARegister(pDevIns, pThis->dma_chann, &fdctrl_transfer_handler, pThis);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * IO / MMIO.
+ *
+ * We must skip I/O port 0x3f6 as it is the ATA alternate status register.
+ * Why we skip registering status register A, though, isn't as clear.
+ */
+ if (!fMemMapped)
+ {
+ static const IOMIOPORTDESC s_aDescs[] =
+ {
+ { "SRA", NULL, "Status register A", NULL },
+ { "SRB", NULL, "Status register B", NULL },
+ { "DOR", "DOR", "Digital output register", "Digital output register"},
+ { "TDR", "TDR", "Tape driver register", "Tape driver register"},
+ { "MSR", "DSR", "Main status register", "Datarate select register" },
+ { "FIFO", "FIFO", "Data FIFO", "Data FIFO" },
+ { "ATA", "ATA", NULL, NULL },
+ { "DIR", "CCR", "Digital input register", "Configuration control register"},
+ { NULL, NULL, NULL, NULL }
+ };
+
+ /* 0x3f0 */
+ if (fStatusA)
+ {
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->io_base, 1 /*cPorts*/, fdcIoPort0Write, fdcIoPort0Read,
+ "FDC-SRA", s_aDescs, &pThis->hIoPorts0);
+ AssertRCReturn(rc, rc);
+ }
+
+ /* 0x3f1..0x3f5 */
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->io_base + 0x1, 5, fdcIoPort1Write, fdcIoPort1Read,
+ "FDC#1", &s_aDescs[1], &pThis->hIoPorts1);
+ AssertRCReturn(rc, rc);
+
+ /* 0x3f7 */
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pThis->io_base + 0x7, 1, fdcIoPort2Write, fdcIoPort2Read,
+ "FDC#2", &s_aDescs[7], &pThis->hIoPorts2);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ AssertMsgFailedReturn(("Memory mapped floppy not support by now\n"), VERR_NOT_SUPPORTED);
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegister(pDevIns, FDC_SAVESTATE_CURRENT, sizeof(*pThis), fdcSaveExec, fdcLoadExec);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the debugger info callback.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "fdc", "FDC info", fdcInfo);
+
+ /*
+ * Attach the status port (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->IBaseStatus, &pBase, "Status Port");
+ if (RT_SUCCESS (rc))
+ pThis->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else
+ AssertMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc), rc);
+
+ /*
+ * Initialize drives.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->drives); i++)
+ {
+ rc = fdConfig(&pThis->drives[i], pDevIns, true /*fInit*/);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: failed to configure drive %d, rc=%Rrc\n", i, rc),
+ rc);
+ }
+
+ fdctrl_reset(pThis, 0);
+
+ for (unsigned 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,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "i82078",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_STORAGE,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(fdctrl_t),
+ /* .cbInstanceCC = */ 0,
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 0,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Floppy drive controller (Intel 82078)",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "",
+ /* .pszR0Mod = */ "",
+ /* .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,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ NULL,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+/*
+ * 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..b089f3c4
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp
@@ -0,0 +1,5526 @@
+/* $Id: DevLsiLogicSCSI.cpp $ */
+/** @file
+ * DevLsiLogicSCSI - LsiLogic LSI53c1030 SCSI controller.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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/AssertGuest.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 6
+/** The saved state version used by VirtualBox before removal of the
+ * VBoxSCSI BIOS interface. */
+#define LSILOGIC_SAVED_STATE_VERSION_PRE_VBOXSCSI_REMOVAL 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 shared instance data for the LsiLogic emulation. */
+typedef struct LSILOGICSCSI *PLSILOGICSCSI;
+
+#ifdef IN_RING3
+/**
+ * Memory buffer callback.
+ *
+ * @param pDevIns The device 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 DECLCALLBACKTYPE(void, FNLSILOGICR3MEMCOPYCALLBACK,(PPDMDEVINS pDevIns, RTGCPHYS GCPhys,
+ PRTSGBUF pSgBuf, size_t cbCopy, size_t *pcbSkip));
+/** Pointer to a memory copy buffer callback. */
+typedef FNLSILOGICR3MEMCOPYCALLBACK *PFNLSILOGICR3MEMCOPYCALLBACK;
+#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 */
+ PPDMDEVINSR3 pDevIns;
+
+ /** LUN of the device. */
+ uint32_t iLUN;
+ /** Number of outstanding tasks on the port. */
+ volatile uint32_t cOutstandingRequests;
+
+ /** 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;
+ /** Device name. */
+ char szName[16];
+} LSILOGICDEVICE;
+/** Pointer to a device state. */
+typedef LSILOGICDEVICE *PLSILOGICDEVICE;
+
+/** Pointer to a task state. */
+typedef struct LSILOGICREQ *PLSILOGICREQ;
+
+
+/**
+ * Shared instance data for the LsiLogic emulation.
+ */
+typedef struct LSILOGICSCSI
+{
+ /** 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;
+
+ /** Number of device states allocated. */
+ uint32_t cDeviceStates;
+ uint32_t u32Padding1;
+
+ /** 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 times 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;
+ uint16_t u16Padding2;
+
+ /** 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 configured for the reply queue. */
+ uint32_t cReplyQueueEntries;
+ /** Number entries configured 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;
+
+ /** The reply free qeueue (only the first cReplyQueueEntries are used). */
+ uint32_t volatile aReplyFreeQueue[LSILOGICSCSI_REPLY_QUEUE_DEPTH_MAX];
+ /** The reply post qeueue (only the first cReplyQueueEntries are used). */
+ uint32_t volatile aReplyPostQueue[LSILOGICSCSI_REPLY_QUEUE_DEPTH_MAX];
+ /** The request qeueue (only the first cRequestQueueEntries are used). */
+ uint32_t volatile aRequestQueue[LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MAX];
+
+ /** 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;
+
+ /** 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;
+ bool afPadding3[2];
+
+ /** Current address to read from or write to in the diagnostic memory region. */
+ uint32_t u32DiagMemAddr;
+
+ /** Emulated controller type */
+ LSILOGICCTRLTYPE enmCtrlType;
+ /** Handle counter */
+ uint16_t u16NextHandle;
+
+ /** Number of ports this controller has. */
+ uint8_t cPorts;
+ uint8_t afPadding4;
+
+ /** The event semaphore the processing thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+ /** PCI Region \#0: I/O ports register access. */
+ IOMIOPORTHANDLE hIoPortsReg;
+ /** PCI Region \#1: MMIO register access. */
+ IOMMMIOHANDLE hMmioReg;
+ /** PCI Region \#2: MMIO diag. */
+ IOMMMIOHANDLE hMmioDiag;
+ /** ISA Ports for the BIOS (when booting is configured). */
+ IOMIOPORTHANDLE hIoPortsBios;
+} LSILOGICSCSI;
+AssertCompileMemberAlignment(LSILOGICSCSI, ReplyPostQueueCritSect, 8);
+
+/**
+ * Ring-3 instance data for the LsiLogic emulation.
+ */
+typedef struct LSILOGICSCSIR3
+{
+ /** States for attached devices. */
+ R3PTRTYPE(PLSILOGICDEVICE) paDeviceStates;
+ /** 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;
+
+ /** Current size of the memory regions. */
+ uint32_t cbMemRegns;
+ uint32_t u32Padding3;
+
+ /** Critical section protecting the memory regions. */
+ RTCRITSECT CritSectMemRegns;
+ /** List of memory regions - PLSILOGICMEMREGN. */
+ RTLISTANCHORR3 ListMemRegns;
+
+ /** Worker thread. */
+ R3PTRTYPE(PPDMTHREAD) pThreadWrk;
+
+ /** The device instace - only for getting bearings in interface methods. */
+ PPDMDEVINSR3 pDevIns;
+} LSILOGICSCSIR3;
+/** Pointer to the ring-3 instance data for the LsiLogic emulation. */
+typedef LSILOGICSCSIR3 *PLSILOGICSCSIR3;
+
+
+/**
+ * Ring-0 instance data for the LsiLogic emulation.
+ */
+typedef struct LSILOGICSCSIR0
+{
+ uint64_t u64Unused;
+} LSILOGICSCSIR0;
+/** Pointer to the ring-0 instance data for the LsiLogic emulation. */
+typedef LSILOGICSCSIR0 *PLSILOGICSCSIR0;
+
+
+/**
+ * Raw-mode instance data for the LsiLogic emulation.
+ */
+typedef struct LSILOGICSCSIRC
+{
+ uint64_t u64Unused;
+} LSILOGICSCSIRC;
+/** Pointer to the raw-mode instance data for the LsiLogic emulation. */
+typedef LSILOGICSCSIRC *PLSILOGICSCSIRC;
+
+
+/** The current context instance data for the LsiLogic emulation. */
+typedef CTX_SUFF(LSILOGICSCSI) LSILOGICSCSICC;
+/** Pointer to the current context instance data for the LsiLogic emulation. */
+typedef CTX_SUFF(PLSILOGICSCSI) PLSILOGICSCSICC;
+
+
+/**
+ * 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];
+ /** SCSI status code. */
+ uint8_t u8ScsiSts;
+} LSILOGICREQ;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+static void lsilogicR3InitializeConfigurationPages(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC);
+static void lsilogicR3ConfigurationPagesFree(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC);
+static int lsilogicR3ProcessConfigurationRequest(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC,
+ 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ */
+static void lsilogicUpdateInterrupt(PPDMDEVINS pDevIns, 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(pDevIns, 0, 1);
+ }
+ else
+ {
+ LogFlowFunc(("Clearing interrupt\n"));
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ }
+}
+
+/**
+ * Sets a given interrupt status bit in the status register and
+ * updates the interrupt status.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param uStatus The status bit to set.
+ */
+DECLINLINE(void) lsilogicSetInterrupt(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, uint32_t uStatus)
+{
+ ASMAtomicOrU32(&pThis->uInterruptStatus, uStatus);
+ lsilogicUpdateInterrupt(pDevIns, pThis);
+}
+
+/**
+ * Clears a given interrupt status bit in the status register and
+ * updates the interrupt status.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param uStatus The status bit to set.
+ */
+DECLINLINE(void) lsilogicClearInterrupt(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, uint32_t uStatus)
+{
+ ASMAtomicAndU32(&pThis->uInterruptStatus, ~uStatus);
+ lsilogicUpdateInterrupt(pDevIns, pThis);
+}
+
+
+#ifdef IN_RING3
+/**
+ * Sets the I/O controller into fault state and sets the fault code.
+ *
+ * @param pThis Pointer to the shared 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 shared 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 shared 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static int lsilogicR3HardReset(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+ 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(pDevIns, 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;
+
+ lsilogicR3InitializeConfigurationPages(pDevIns, pThis, pThisCC);
+
+ /* Mark that we finished performing the reset. */
+ pThis->enmState = LSILOGICSTATE_READY;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Allocates the configuration pages based on the device.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static int lsilogicR3ConfigurationPagesAlloc(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+ pThisCC->pConfigurationPages = (PMptConfigurationPagesSupported)RTMemAllocZ(sizeof(MptConfigurationPagesSupported));
+ if (!pThisCC->pConfigurationPages)
+ return VERR_NO_MEMORY;
+
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PMptConfigurationPagesSas pPages = &pThisCC->pConfigurationPages->u.SasPages;
+
+ pPages->cbManufacturingPage7 = LSILOGICSCSI_MANUFACTURING7_GET_SIZE(pThis->cPorts);
+ PMptConfigurationPageManufacturing7 pManufacturingPage7 = (PMptConfigurationPageManufacturing7)RTMemAllocZ(pPages->cbManufacturingPage7);
+ AssertPtrReturn(pManufacturingPage7, VERR_NO_MEMORY);
+ 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);
+ AssertPtrReturn(pSASPage0, VERR_NO_MEMORY);
+ 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);
+ AssertPtrReturn(pSASPage1, VERR_NO_MEMORY);
+ pPages->pSASIOUnitPage1 = pSASPage1;
+
+ pPages->cPHYs = pThis->cPorts;
+ pPages->paPHYs = (PMptPHY)RTMemAllocZ(pPages->cPHYs * sizeof(MptPHY));
+ AssertPtrReturn(pPages->paPHYs, VERR_NO_MEMORY);
+
+ /* Initialize the PHY configuration */
+ for (unsigned i = 0; i < pThis->cPorts; i++)
+ {
+ /* Settings for present devices. */
+ if (pThisCC->paDeviceStates[i].pDrvBase)
+ {
+ PMptSASDevice pSASDevice = (PMptSASDevice)RTMemAllocZ(sizeof(MptSASDevice));
+ AssertPtrReturn(pSASDevice, VERR_NO_MEMORY);
+
+ /* 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++;
+ }
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Frees the configuration pages if allocated.
+ *
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static void lsilogicR3ConfigurationPagesFree(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+
+ if (pThisCC->pConfigurationPages)
+ {
+ /* Destroy device list if we emulate a SAS controller. */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PMptConfigurationPagesSas pSasPages = &pThisCC->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);
+
+ pSasPages->pSASDeviceHead = NULL;
+ pSasPages->paPHYs = NULL;
+ pSasPages->pManufacturingPage7 = NULL;
+ pSasPages->pSASIOUnitPage0 = NULL;
+ pSasPages->pSASIOUnitPage1 = NULL;
+ }
+
+ RTMemFree(pThisCC->pConfigurationPages);
+ pThisCC->pConfigurationPages = NULL;
+ }
+}
+
+/**
+ * Finishes a context reply.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param u32MessageContext The message context ID to post.
+ */
+static void lsilogicR3FinishContextReply(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, uint32_t u32MessageContext)
+{
+ 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. */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->ReplyPostQueueCritSect, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->ReplyPostQueueCritSect, rc);
+
+ /* Check for a entry in the queue. */
+ if (!lsilogicReplyPostQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->ReplyPostQueueCritSect);
+ return;
+ }
+
+ /* We have a context reply. */
+ ASMAtomicWriteU32(&pThis->aReplyPostQueue[pThis->uReplyPostQueueNextEntryFreeWrite], u32MessageContext);
+ ASMAtomicIncU32(&pThis->uReplyPostQueueNextEntryFreeWrite);
+ pThis->uReplyPostQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+
+ /* Set interrupt. */
+ lsilogicSetInterrupt(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->ReplyPostQueueCritSect);
+}
+
+
+/**
+ * Takes necessary steps to finish a reply frame.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared 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(PPDMDEVINS pDevIns, 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(pDevIns, 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
+ /* Grab a free reply message from the queue. */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->ReplyFreeQueueCritSect, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->ReplyFreeQueueCritSect, rc);
+
+ /* Check for a free reply frame. */
+ if (!lsilogicReplyFreeQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->ReplyFreeQueueCritSect);
+ return;
+ }
+
+ uint32_t u32ReplyFrameAddressLow = pThis->aReplyFreeQueue[pThis->uReplyFreeQueueNextAddressRead];
+
+ pThis->uReplyFreeQueueNextAddressRead++;
+ pThis->uReplyFreeQueueNextAddressRead %= pThis->cReplyQueueEntries;
+
+ PDMDevHlpCritSectLeave(pDevIns, &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. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysReplyMessage, pReply, cbReplyCopied);
+
+ /* Write low 32bits of reply frame into post reply queue. */
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->ReplyPostQueueCritSect, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->ReplyPostQueueCritSect, rc);
+
+ /* Check for a entry in the queue. */
+ if (!lsilogicReplyPostQueueGetFrameCount(pThis))
+ {
+ /* Set error code. */
+ lsilogicSetIOCFaultCode(pThis, LSILOGIC_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->ReplyPostQueueCritSect);
+ return;
+ }
+
+ /* We have a address reply. Set the 31th bit to indicate that. */
+ ASMAtomicWriteU32(&pThis->aReplyPostQueue[pThis->uReplyPostQueueNextEntryFreeWrite],
+ RT_BIT(31) | (u32ReplyFrameAddressLow >> 1));
+ ASMAtomicIncU32(&pThis->uReplyPostQueueNextEntryFreeWrite);
+ pThis->uReplyPostQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+
+ if (fForceReplyFifo)
+ {
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ lsilogicSetInterrupt(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+
+ /* Set interrupt. */
+ lsilogicSetInterrupt(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+
+ PDMDevHlpCritSectLeave(pDevIns, &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 pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param u32Addr The 32bit address to search for.
+ */
+static PLSILOGICMEMREGN lsilogicR3MemRegionFindByAddr(PLSILOGICSCSICC pThisCC, uint32_t u32Addr)
+{
+ PLSILOGICMEMREGN pRegion = NULL;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThisCC->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ if ( u32Addr >= pIt->u32AddrStart
+ && u32Addr <= pIt->u32AddrEnd)
+ {
+ pRegion = pIt;
+ break;
+ }
+ }
+
+ return pRegion;
+}
+
+/**
+ * Frees all allocated memory regions.
+ *
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static void lsilogicR3MemRegionsFree(PLSILOGICSCSICC pThisCC)
+{
+ PLSILOGICMEMREGN pItNext;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEachSafe(&pThisCC->ListMemRegns, pIt, pItNext, LSILOGICMEMREGN, NodeList)
+ {
+ RTListNodeRemove(&pIt->NodeList);
+ RTMemFree(pIt);
+ }
+ pThisCC->cbMemRegns = 0;
+}
+
+/**
+ * Inserts a given memory region into the list.
+ *
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param pRegion The region to insert.
+ */
+static void lsilogicR3MemRegionInsert(PLSILOGICSCSICC pThisCC, PLSILOGICMEMREGN pRegion)
+{
+ bool fInserted = false;
+
+ /* Insert at the right position. */
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThisCC->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ if (pRegion->u32AddrEnd < pIt->u32AddrStart)
+ {
+ RTListNodeInsertBefore(&pIt->NodeList, &pRegion->NodeList);
+ fInserted = true;
+ break;
+ }
+ }
+ if (!fInserted)
+ RTListAppend(&pThisCC->ListMemRegns, &pRegion->NodeList);
+}
+
+/**
+ * Count number of memory regions.
+ *
+ * @returns Number of memory regions.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static uint32_t lsilogicR3MemRegionsCount(PLSILOGICSCSICC pThisCC)
+{
+ uint32_t cRegions = 0;
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThisCC->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ cRegions++;
+ }
+
+ return cRegions;
+}
+
+/**
+ * Handles a write to the diagnostic data register.
+ *
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param u32Data Data to write.
+ */
+static void lsilogicR3DiagRegDataWrite(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC, uint32_t u32Data)
+{
+ RTCritSectEnter(&pThisCC->CritSectMemRegns);
+
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, 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(&pThisCC->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 (pThisCC->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);
+ pThisCC->cbMemRegns += 512 * sizeof(uint32_t);
+ }
+ /* else: Silently fail, there is nothing we can do here and the guest might work nevertheless. */
+
+ lsilogicR3MemRegionInsert(pThisCC, pRegion);
+ }
+ }
+ else
+ {
+ if (pThisCC->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;
+ pThisCC->cbMemRegns += 512 * sizeof(uint32_t);
+
+ lsilogicR3MemRegionInsert(pThisCC, 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);
+ RTCritSectLeave(&pThisCC->CritSectMemRegns);
+}
+
+/**
+ * Handles a read from the diagnostic data register.
+ *
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param pu32Data Where to store the data.
+ */
+static void lsilogicR3DiagRegDataRead(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC, uint32_t *pu32Data)
+{
+ RTCritSectEnter(&pThisCC->CritSectMemRegns);
+
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, 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);
+ RTCritSectLeave(&pThisCC->CritSectMemRegns);
+}
+
+/**
+ * Handles a write to the diagnostic memory address register.
+ *
+ * @param pThis Pointer to the shared 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.
+ *
+ * @param pThis Pointer to the shared 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param pMessageHdr Pointer to the message header of the request.
+ * @param pReply Pointer to the reply.
+ */
+static int lsilogicR3ProcessMessageRequest(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC,
+ 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;
+
+ pReply->IOCFacts.u16ProductID = 0xcafe; /* Our own product ID :) */
+ pReply->IOCFacts.u32FwImageSize = 0; /* No image needed. */
+ pReply->IOCFacts.u32FWVersion = 0;
+
+ /* Check for a valid firmware image in the IOC memory which was downloaded by the guest earlier and use that. */
+ RTCritSectEnter(&pThisCC->CritSectMemRegns);
+ PLSILOGICMEMREGN pRegion = lsilogicR3MemRegionFindByAddr(pThisCC, LSILOGIC_FWIMGHDR_LOAD_ADDRESS);
+ if (pRegion)
+ {
+ uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart);
+ if (pRegion->u32AddrEnd - offImgHdr + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */
+ {
+ PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr / 4];
+
+ /* 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;
+ }
+ }
+ }
+ RTCritSectLeave(&pThisCC->CritSectMemRegns);
+ 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(pDevIns, pThis, pThisCC, 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(pDevIns, pThis, pReply, fForceReplyPostFifo);
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Writes a value to a register at a given offset.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The devie instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param offReg Offset of the register to write.
+ * @param u32 The value being written.
+ */
+static VBOXSTRICTRC lsilogicRegisterWrite(PPDMDEVINS pDevIns, 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 = PDMDevHlpCritSectEnter(pDevIns, &pThis->ReplyFreeQueueWriteCritSect, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ /* Add the entry to the reply free queue. */
+ ASMAtomicWriteU32(&pThis->aReplyFreeQueue[pThis->uReplyFreeQueueNextEntryFreeWrite], u32);
+ pThis->uReplyFreeQueueNextEntryFreeWrite++;
+ pThis->uReplyFreeQueueNextEntryFreeWrite %= pThis->cReplyQueueEntries;
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->ReplyFreeQueueWriteCritSect);
+ break;
+ }
+ case LSILOGIC_REG_REQUEST_QUEUE:
+ {
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->RequestQueueCritSect, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint32_t uNextWrite = ASMAtomicReadU32(&pThis->uRequestQueueNextEntryFreeWrite);
+
+ ASMAtomicWriteU32(&pThis->aRequestQueue[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);
+ PDMDevHlpCritSectLeave(pDevIns, &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))
+ {
+ LogFlowFunc(("Signal event semaphore\n"));
+ rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+ AssertRC(rc);
+ }
+ }
+ 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(pDevIns, 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;
+
+ /* This is not supposed to happen and the result is undefined, just stay in the current state. */
+ AssertMsgReturn(pThis->cMessage <= RT_ELEMENTS(pThis->aMessage),
+ ("Message doesn't fit into the buffer, cMessage=%u", pThis->cMessage),
+ VINF_SUCCESS);
+
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_FN_HANDSHAKE;
+ /* Update the interrupt status to notify the guest that a doorbell function was started. */
+ lsilogicSetInterrupt(pDevIns, 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(pDevIns, 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, ignore any excess writes.
+ */
+ AssertMsgReturn(pThis->iMessage < pThis->cMessage,
+ ("Guest is trying to write more than was indicated in the handshake\n"),
+ VINF_SUCCESS);
+
+ /*
+ * 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(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC),
+ (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(pDevIns, pThis);
+ break;
+ }
+ case LSILOGIC_REG_HOST_INTR_MASK:
+ {
+ ASMAtomicWriteU32(&pThis->uInterruptMask, u32 & LSILOGIC_REG_HOST_INTR_MASK_W_MASK);
+ lsilogicUpdateInterrupt(pDevIns, 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(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC));
+ 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, PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC), 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param offReg Offset of the register to read.
+ * @param pu32 Where to store the content of the register.
+ */
+static VBOXSTRICTRC lsilogicRegisterRead(PPDMDEVINS pDevIns, 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 = PDMDevHlpCritSectEnter(pDevIns, &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->aReplyPostQueue[idxReplyPostQueueRead];
+ idxReplyPostQueueRead++;
+ idxReplyPostQueueRead %= pThis->cReplyQueueEntries;
+ ASMAtomicWriteU32(&pThis->uReplyPostQueueNextAddressRead, idxReplyPostQueueRead);
+ }
+ else
+ {
+ /* The reply post queue is empty. Reset interrupt. */
+ u32 = UINT32_C(0xffffffff);
+ lsilogicClearInterrupt(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR);
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &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(pDevIns, 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(pDevIns, 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(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ break;
+ }
+ case LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW:
+ if (pThis->uReplyFreeQueueNextEntryFreeWrite != pThis->uReplyFreeQueueNextAddressRead)
+ {
+ u32 |= pThis->aReplyFreeQueue[pThis->uReplyFreeQueueNextAddressRead] & UINT32_C(0xffff);
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_HIGH;
+ lsilogicSetInterrupt(pDevIns, pThis, LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL);
+ }
+ break;
+ case LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_HIGH:
+ u32 |= pThis->aReplyFreeQueue[pThis->uReplyFreeQueueNextAddressRead] >> 16;
+ pThis->uReplyFreeQueueNextAddressRead++;
+ pThis->uReplyFreeQueueNextAddressRead %= pThis->cReplyQueueEntries;
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_RFR_NEXT_FRAME_LOW;
+ lsilogicSetInterrupt(pDevIns, 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, PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC), &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{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+lsilogicIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ VBOXSTRICTRC rcStrict;
+ RT_NOREF2(pvUser, cb);
+
+ if (!(offPort & 3))
+ {
+ rcStrict = lsilogicRegisterWrite(pDevIns, pThis, offPort, u32);
+ if (rcStrict == VINF_IOM_R3_MMIO_WRITE)
+ rcStrict = VINF_IOM_R3_IOPORT_WRITE;
+ }
+ else
+ {
+ Log(("lsilogicIOPortWrite: Ignoring misaligned write - offPort=%#x u32=%#x cb=%#x\n", offPort, u32, cb));
+ rcStrict = VINF_SUCCESS;
+ }
+
+ return rcStrict;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+lsilogicIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ RT_NOREF_PV(pvUser);
+ RT_NOREF_PV(cb);
+
+ VBOXSTRICTRC rcStrict = lsilogicRegisterRead(pDevIns, pThis, offPort & ~(uint32_t)3, pu32);
+ if (rcStrict == VINF_IOM_R3_MMIO_READ)
+ rcStrict = VINF_IOM_R3_IOPORT_READ;
+
+ return rcStrict;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) lsilogicMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t u32;
+ RT_NOREF_PV(pvUser);
+
+ /* See comments in lsilogicR3Construct 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 - off=%#RGp u32=%#x cb=%#x\n", off, u32, cb));
+ }
+
+ VBOXSTRICTRC rcStrict;
+ if (!(off & 3))
+ rcStrict = lsilogicRegisterWrite(pDevIns, pThis, (uint32_t)off, u32);
+ else
+ {
+ Log(("lsilogicMMIOWrite: Ignoring misaligned write - off=%#RGp u32=%#x cb=%#x\n", off, u32, cb));
+ rcStrict = VINF_SUCCESS;
+ }
+ return rcStrict;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) lsilogicMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ Assert(!(off & 3)); Assert(cb == 4); /* If any of these trigger you've changed the registration flags or IOM is busted. */
+ RT_NOREF2(pvUser, cb);
+
+ return lsilogicRegisterRead(pDevIns, pThis, off, (uint32_t *)pv);
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+lsilogicDiagnosticWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, off, pv, cb);
+ LogFlowFunc(("pThis=%#p GCPhysAddr=%RGp pv=%#p{%.*Rhxs} cb=%u\n", PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI), off, pv, cb, pv, cb));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) lsilogicDiagnosticRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, off, pv, cb);
+ LogFlowFunc(("pThis=%#p off=%RGp pv=%#p{%.*Rhxs} cb=%u\n", PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI), off, pv, cb, pv, cb));
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+# ifdef LOG_ENABLED
+/**
+ * Dump an SG entry.
+ *
+ * @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 FNLSILOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) lsilogicR3CopyBufferFromGuestWorker(PPDMDEVINS pDevIns, 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);
+ PDMDevHlpPCIPhysReadUser(pDevIns, GCPhys, pvSeg, cbSeg);
+ GCPhys += cbSeg;
+ cbCopy -= cbSeg;
+ }
+}
+
+/**
+ * Copy from host to guest memory worker.
+ *
+ * @copydoc FNLSILOGICR3MEMCOPYCALLBACK
+ */
+static DECLCALLBACK(void) lsilogicR3CopyBufferToGuestWorker(PPDMDEVINS pDevIns, 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);
+ PDMDevHlpPCIPhysWriteUser(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 pDevIns The device instance.
+ * @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(PPDMDEVINS pDevIns, PLSILOGICREQ pLsiReq,
+ PFNLSILOGICR3MEMCOPYCALLBACK 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;
+ 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. */
+ PDMDevHlpPCIPhysReadMeta(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);
+
+ size_t cbCopyThis = RT_MIN(SGEntry.Simple32.u24Length, cbCopy);
+ RTGCPHYS GCPhysAddrDataBuffer = SGEntry.Simple32.u32DataBufferAddressLow;
+
+ if (SGEntry.Simple32.f64BitAddress)
+ {
+ GCPhysAddrDataBuffer |= ((uint64_t)SGEntry.Simple64.u32DataBufferAddressHigh) << 32;
+ GCPhysSgEntryNext += sizeof(MptSGEntrySimple64);
+ }
+ else
+ GCPhysSgEntryNext += sizeof(MptSGEntrySimple32);
+
+ pfnCopyWorker(pDevIns, 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;
+
+ PDMDevHlpPCIPhysReadMeta(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 pDevIns The 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(PPDMDEVINS pDevIns, PLSILOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return lsilogicSgBufWalker(pDevIns, 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 pDevIns The 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(PPDMDEVINS pDevIns, PLSILOGICREQ pReq, PRTSGBUF pSgBuf,
+ size_t cbSkip, size_t cbCopy)
+{
+ return lsilogicSgBufWalker(pDevIns, 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pReq The request to complete.
+ * @param rcReq Status code of the request.
+ */
+static void lsilogicR3ReqComplete(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICREQ pReq, int rcReq)
+{
+ PLSILOGICDEVICE pTgtDev = pReq->pTargetDevice;
+ RTGCPHYS GCPhysAddrSenseBuffer;
+
+ GCPhysAddrSenseBuffer = pReq->GuestRequest.SCSIIO.u32SenseBufferLowAddress;
+ GCPhysAddrSenseBuffer |= ((uint64_t)pThis->u32SenseBufferHighAddr << 32);
+
+ /* Copy the sense buffer over. */
+ if (pReq->GuestRequest.SCSIIO.u8SenseBufferLength > 0)
+ PDMDevHlpPCIPhysWriteMeta(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(pDevIns, 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(pDevIns, pThis, &IOCReply, false);
+ }
+
+ ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+
+ if (pTgtDev->cOutstandingRequests == 0 && pThis->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+}
+
+/**
+ * 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 pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 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(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC,
+ 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 = &pThisCC->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->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, NULL, pLsiReq->GuestRequest.SCSIIO.u32DataLength,
+ &pLsiReq->abSenseBuffer[0], sizeof(pLsiReq->abSenseBuffer), NULL,
+ &pLsiReq->u8ScsiSts, 30 * RT_MS_1SEC);
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ lsilogicR3ReqComplete(pDevIns, 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", pDevIns->iInstance,
+ pGuestReq->SCSIIO.u8TargetID, pGuestReq->SCSIIO.u8Bus));
+ /* Log the CDB too */
+ LogRel(("LsiLogic#%d: Guest issued CDB {%#x",
+ 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.u8Reserved = 0;
+ IOCReply.SCSIIOError.u8MessageFlags = 0;
+ 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(pDevIns, 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->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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PLSILOGICREQ pReq = (PLSILOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = lsilogicR3CopySgBufToGuest(pDevIns, 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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PLSILOGICREQ pReq = (PLSILOGICREQ)pvIoReqAlloc;
+
+ size_t cbCopied = lsilogicR3CopySgBufFromGuest(pDevIns, 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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ lsilogicR3ReqComplete(pDevIns, pThis, (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. */
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ uint32_t cTasksActive = ASMAtomicDecU32(&pTgtDev->cOutstandingRequests);
+ if (!cTasksActive && pThis->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ 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);
+ PPDMDEVINS pDevIns = pTgtDev->pDevIns;
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+
+ if (pThisCC->pMediaNotify)
+ {
+ int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->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 Pointer to the shared LsiLogic device state. 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 Pointer to the shared LsiLogic device state. 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 Pointer to the shared LsiLogic device state. 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 Pointer to the shared LsiLogic device state. 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 Pointer to the shared LsiLogic device state. 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 Pointer to the shared LsiLogic device state. 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(PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ 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(PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ MptConfigurationPageAddress PageAddress,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ 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(PMptConfigurationPagesSupported pPages,
+ uint8_t u8PageNumber,
+ MptConfigurationPageAddress PageAddress,
+ PMptExtendedConfigurationPageHeader *ppPageHeader,
+ uint8_t **ppbPageData, size_t *pcbPage)
+{
+ 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 pThisCC Pointer to the ring-3 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(PLSILOGICSCSICC pThisCC, 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(pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ ppPageHeader, ppbPageData, pcbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASPHYS:
+ {
+ rc = lsilogicR3ConfigurationSASPHYPageGetFromNumber(pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ pConfigurationReq->PageAddress,
+ ppPageHeader, ppbPageData, pcbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED_SASDEVICE:
+ {
+ rc = lsilogicR3ConfigurationSASDevicePageGetFromNumber(pThisCC->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 pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ * @param pConfigurationReq Pointer to the request structure.
+ * @param pReply Pointer to the reply message frame
+ */
+static int lsilogicR3ProcessConfigurationRequest(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC,
+ 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,
+ pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_IOC:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationIOCPageGetFromNumber(pThis,
+ pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_MANUFACTURING:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationManufacturingPageGetFromNumber(pThis,
+ pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_SCSI_SPI_PORT:
+ {
+ /* Get the page data. */
+ rc = lsilogicR3ConfigurationSCSISPIPortPageGetFromNumber(pThis,
+ pThisCC->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,
+ pThisCC->pConfigurationPages,
+ pConfigurationReq->PageAddress.BusAndTargetId.u8Bus,
+ pConfigurationReq->PageAddress.BusAndTargetId.u8TargetID,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_BIOS:
+ {
+ rc = lsilogicR3ConfigurationBiosPageGetFromNumber(pThis,
+ pThisCC->pConfigurationPages,
+ pConfigurationReq->u8PageNumber,
+ &pPageHeader, &pbPageData, &cbPage);
+ break;
+ }
+ case MPT_CONFIGURATION_PAGE_TYPE_EXTENDED:
+ {
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ rc = lsilogicR3ConfigurationPageGetExtended(pThisCC,
+ pConfigurationReq,
+ &pExtPageHeader, &pbPageData, &cbPage);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ 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;
+
+ PDMDevHlpPCIPhysWriteMeta(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));
+
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysAddrPageBuffer, pbPageData, RT_MIN(cbBuffer, cbPage));
+ }
+ break;
+ }
+ default:
+ AssertMsgFailed(("todo\n"));
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the configuration pages for the SPI SCSI controller.
+ *
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPagesSpi(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+ PMptConfigurationPagesSpi pPages = &pThisCC->pConfigurationPages->u.SpiPages;
+
+ AssertMsg(pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI, ("Controller is not the SPI SCSI one\n"));
+ LogFlowFunc(("pThis=%#p\n", pThis));
+ RT_NOREF(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 shared LsiLogic device state.
+ */
+DECLINLINE(uint16_t) lsilogicGetHandle(PLSILOGICSCSI pThis)
+{
+ uint16_t u16Handle = pThis->u16NextHandle++;
+ return u16Handle;
+}
+
+/**
+ * Generates a SAS address (WWID)
+ *
+ * @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.
+ *
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPagesSas(PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+ PMptConfigurationPagesSas pPages = &pThisCC->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. */
+ PMptConfigurationPageManufacturing7 pManufacturingPage7 = pPages->pManufacturingPage7;
+ 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;
+
+ /* SAS I/O unit page 0 - Port specific information. */
+ PMptConfigurationPageSASIOUnit0 pSASPage0 = pPages->pSASIOUnitPage0;
+ 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. */
+ PMptConfigurationPageSASIOUnit1 pSASPage1 = pPages->pSASIOUnitPage1;
+ 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;
+
+ /* 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;
+
+ Assert(pPages->cPHYs == pThis->cPorts);
+ AssertPtr(pPages->paPHYs);
+
+ /* Initialize the PHY configuration */
+ PMptSASDevice pSASDevice = pPages->pSASDeviceHead;
+ 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 (pThisCC->paDeviceStates[i].pDrvBase)
+ {
+ uint16_t u16DeviceHandle = lsilogicGetHandle(pThis);
+ SASADDRESS SASAddress;
+ 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;
+
+ pSASDevice = pSASDevice->pNext;
+ }
+ }
+}
+
+/**
+ * Initializes the configuration pages.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ * @param pThisCC Pointer to the ring-3 LsiLogic device state.
+ */
+static void lsilogicR3InitializeConfigurationPages(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis, PLSILOGICSCSICC pThisCC)
+{
+ /* Initialize the common pages. */
+
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ AssertPtrReturnVoid(pThisCC->pConfigurationPages);
+ PMptConfigurationPagesSupported pPages = pThisCC->pConfigurationPages;
+
+ /* 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 = pDevIns->apPciDevs[0]->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, pThisCC);
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ lsilogicR3InitializeConfigurationPagesSas(pThis, pThisCC);
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+}
+
+/**
+ * Sets the emulated controller type from a given string.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the shared 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{PFNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) lsilogicR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+
+ /*
+ * Parse args.
+ */
+ bool const fVerbose = pszArgs && strstr(pszArgs, "verbose") != NULL;
+
+ /*
+ * Show info.
+ */
+ pHlp->pfnPrintf(pHlp,
+ "%s#%d: port=%04x mmio=%RGp max-devices=%u GC=%RTbool R0=%RTbool\n",
+ pDevIns->pReg->szName, pDevIns->iInstance,
+ PDMDevHlpIoPortGetMappingAddress(pDevIns, pThis->hIoPortsReg),
+ PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmioReg),
+ pThis->cDeviceStates, pDevIns->fRCEnabled, pDevIns->fR0Enabled);
+
+ /*
+ * 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->aReplyFreeQueue[i]);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnPrintf(pHlp, "RPQ[%u]=%#x\n", i, pThis->aReplyPostQueue[i]);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ pHlp->pfnPrintf(pHlp, "ReqQ[%u]=%#x\n", i, pThis->aRequestQueue[i]);
+ }
+
+ /*
+ * Print the device status.
+ */
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThisCC->paDeviceStates[i];
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ pHlp->pfnPrintf(pHlp, "Device[%u]: device-attached=%RTbool cOutstandingRequests=%u\n",
+ i, pDevice->pDrvBase != NULL, pDevice->cOutstandingRequests);
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNPDMTHREADDEV}
+ */
+static DECLCALLBACK(int) lsilogicR3Worker(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ 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 = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, 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);
+
+ /* 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->aRequestQueue[pThis->uRequestQueueNextAddressRead];
+ RTGCPHYS GCPhysMessageFrameAddr = LSILOGIC_RTGCPHYS_FROM_U32(pThis->u32HostMFAHighAddr,
+ (u32RequestMessageFrameDesc & ~0x07));
+
+ /* Read the message header from the guest first. */
+ PDMDevHlpPCIPhysReadMeta(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. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysMessageFrameAddr, &GuestRequest, cbRequest);
+
+ /* Handle SCSI I/O requests now. */
+ if (GuestRequest.Header.u8Function == MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST)
+ {
+ rc = lsilogicR3ProcessSCSIIORequest(pDevIns, pThis, pThisCC, GCPhysMessageFrameAddr, &GuestRequest);
+ AssertRC(rc);
+ }
+ else
+ {
+ MptReplyUnion Reply;
+ rc = lsilogicR3ProcessMessageRequest(pDevIns, pThis, pThisCC, &GuestRequest.Header, &Reply);
+ AssertRC(rc);
+ }
+
+ pThis->uRequestQueueNextAddressRead++;
+ pThis->uRequestQueueNextAddressRead %= pThis->cRequestQueueEntries;
+ }
+ } /* While request frames available. */
+ } /* While running */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMTHREADWAKEUPDEV}
+ */
+static DECLCALLBACK(int) lsilogicR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+}
+
+
+/**
+ * Kicks the controller to process pending tasks after the VM was resumed
+ * or loaded from a saved state.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared LsiLogic device state.
+ */
+static void lsilogicR3Kick(PPDMDEVINS pDevIns, PLSILOGICSCSI pThis)
+{
+ if (pThis->fNotificationSent)
+ {
+ /* Notify the worker thread that there are pending requests. */
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+ AssertRC(rc);
+ }
+}
+
+
+/*
+ * Saved state.
+ */
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU32(pSSM, pThis->enmCtrlType);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cDeviceStates);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cPorts);
+
+ /* Save the device config. */
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ pHlp->pfnSSMPutBool(pSSM, pThisCC->paDeviceStates[i].pDrvBase != NULL);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /* Every device first. */
+ lsilogicR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+ for (unsigned i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pDevice = &pThisCC->paDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ pHlp->pfnSSMPutU32(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 (;;)
+ {
+ /* Write only the lower 32bit part of the address. */
+ ASMAtomicWriteU32(&pThis->aRequestQueue[pThis->uRequestQueueNextEntryFreeWrite],
+ pReq->GCPhysMessageFrameAddr & UINT32_C(0xffffffff));
+
+ pThis->uRequestQueueNextEntryFreeWrite++;
+ pThis->uRequestQueueNextEntryFreeWrite %= pThis->cRequestQueueEntries;
+
+ cReqsRedo--;
+ if (!cReqsRedo)
+ break;
+
+ rc = pDevice->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pDevice->pDrvMediaEx, hIoReq,
+ &hIoReq, (void **)&pReq);
+ AssertRCBreak(rc);
+ }
+ }
+ }
+ }
+
+ /* Now the main device state. */
+ pHlp->pfnSSMPutU32(pSSM, pThis->enmState);
+ pHlp->pfnSSMPutU32(pSSM, pThis->enmWhoInit);
+ pHlp->pfnSSMPutU32(pSSM, pThis->enmDoorbellState);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fDiagnosticEnabled);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fNotificationSent);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fEventNotificationEnabled);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uInterruptMask);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uInterruptStatus);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aMessage); i++)
+ pHlp->pfnSSMPutU32(pSSM, pThis->aMessage[i]);
+ pHlp->pfnSSMPutU32(pSSM, pThis->iMessage);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cMessage);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->ReplyBuffer, sizeof(pThis->ReplyBuffer));
+ pHlp->pfnSSMPutU32(pSSM, pThis->uNextReplyEntryRead);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cReplySize);
+ pHlp->pfnSSMPutU16(pSSM, pThis->u16IOCFaultCode);
+ pHlp->pfnSSMPutU32(pSSM, pThis->u32HostMFAHighAddr);
+ pHlp->pfnSSMPutU32(pSSM, pThis->u32SenseBufferHighAddr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->cMaxDevices);
+ pHlp->pfnSSMPutU8(pSSM, pThis->cMaxBuses);
+ pHlp->pfnSSMPutU16(pSSM, pThis->cbReplyFrame);
+ pHlp->pfnSSMPutU32(pSSM, pThis->iDiagnosticAccess);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cReplyQueueEntries);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cRequestQueueEntries);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uReplyFreeQueueNextEntryFreeWrite);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uReplyFreeQueueNextAddressRead);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uReplyPostQueueNextEntryFreeWrite);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uReplyPostQueueNextAddressRead);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uRequestQueueNextEntryFreeWrite);
+ pHlp->pfnSSMPutU32(pSSM, pThis->uRequestQueueNextAddressRead);
+
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnSSMPutU32(pSSM, pThis->aReplyFreeQueue[i]);
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnSSMPutU32(pSSM, pThis->aReplyPostQueue[i]);
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ pHlp->pfnSSMPutU32(pSSM, pThis->aRequestQueue[i]);
+
+ pHlp->pfnSSMPutU16(pSSM, pThis->u16NextHandle);
+
+ /* Save diagnostic memory register and data regions. */
+ pHlp->pfnSSMPutU32(pSSM, pThis->u32DiagMemAddr);
+ pHlp->pfnSSMPutU32(pSSM, lsilogicR3MemRegionsCount(pThisCC));
+
+ PLSILOGICMEMREGN pIt;
+ RTListForEach(&pThisCC->ListMemRegns, pIt, LSILOGICMEMREGN, NodeList)
+ {
+ pHlp->pfnSSMPutU32(pSSM, pIt->u32AddrStart);
+ pHlp->pfnSSMPutU32(pSSM, pIt->u32AddrEnd);
+ pHlp->pfnSSMPutMem(pSSM, &pIt->au32Data[0], (pIt->u32AddrEnd - pIt->u32AddrStart + 1) * sizeof(uint32_t));
+ }
+
+ PMptConfigurationPagesSupported pPages = pThisCC->pConfigurationPages;
+
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage0, sizeof(MptConfigurationPageManufacturing0));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage1, sizeof(MptConfigurationPageManufacturing1));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage2, sizeof(MptConfigurationPageManufacturing2));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage3, sizeof(MptConfigurationPageManufacturing3));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage4, sizeof(MptConfigurationPageManufacturing4));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage5, sizeof(MptConfigurationPageManufacturing5));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage6, sizeof(MptConfigurationPageManufacturing6));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage8, sizeof(MptConfigurationPageManufacturing8));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage9, sizeof(MptConfigurationPageManufacturing9));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->ManufacturingPage10, sizeof(MptConfigurationPageManufacturing10));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOUnitPage0, sizeof(MptConfigurationPageIOUnit0));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOUnitPage1, sizeof(MptConfigurationPageIOUnit1));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOUnitPage2, sizeof(MptConfigurationPageIOUnit2));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOUnitPage3, sizeof(MptConfigurationPageIOUnit3));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOUnitPage4, sizeof(MptConfigurationPageIOUnit4));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage0, sizeof(MptConfigurationPageIOC0));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage1, sizeof(MptConfigurationPageIOC1));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage2, sizeof(MptConfigurationPageIOC2));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage3, sizeof(MptConfigurationPageIOC3));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage4, sizeof(MptConfigurationPageIOC4));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->IOCPage6, sizeof(MptConfigurationPageIOC6));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->BIOSPage1, sizeof(MptConfigurationPageBIOS1));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->BIOSPage2, sizeof(MptConfigurationPageBIOS2));
+ pHlp->pfnSSMPutMem(pSSM, &pPages->BIOSPage4, sizeof(MptConfigurationPageBIOS4));
+
+ /* Device dependent pages */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PMptConfigurationPagesSpi pSpiPages = &pPages->u.SpiPages;
+
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage0, sizeof(MptConfigurationPageSCSISPIPort0));
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage1, sizeof(MptConfigurationPageSCSISPIPort1));
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage2, sizeof(MptConfigurationPageSCSISPIPort2));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pSpiPages->aBuses[0].aDevicePages); i++)
+ {
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage0, sizeof(MptConfigurationPageSCSISPIDevice0));
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage1, sizeof(MptConfigurationPageSCSISPIDevice1));
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage2, sizeof(MptConfigurationPageSCSISPIDevice2));
+ pHlp->pfnSSMPutMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage3, sizeof(MptConfigurationPageSCSISPIDevice3));
+ }
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PMptConfigurationPagesSas pSasPages = &pPages->u.SasPages;
+
+ pHlp->pfnSSMPutU32(pSSM, pSasPages->cbManufacturingPage7);
+ pHlp->pfnSSMPutU32(pSSM, pSasPages->cbSASIOUnitPage0);
+ pHlp->pfnSSMPutU32(pSSM, pSasPages->cbSASIOUnitPage1);
+
+ pHlp->pfnSSMPutMem(pSSM, pSasPages->pManufacturingPage7, pSasPages->cbManufacturingPage7);
+ pHlp->pfnSSMPutMem(pSSM, pSasPages->pSASIOUnitPage0, pSasPages->cbSASIOUnitPage0);
+ pHlp->pfnSSMPutMem(pSSM, pSasPages->pSASIOUnitPage1, pSasPages->cbSASIOUnitPage1);
+
+ pHlp->pfnSSMPutMem(pSSM, &pSasPages->SASIOUnitPage2, sizeof(MptConfigurationPageSASIOUnit2));
+ pHlp->pfnSSMPutMem(pSSM, &pSasPages->SASIOUnitPage3, sizeof(MptConfigurationPageSASIOUnit3));
+
+ pHlp->pfnSSMPutU32(pSSM, pSasPages->cPHYs);
+ for (unsigned i = 0; i < pSasPages->cPHYs; i++)
+ {
+ pHlp->pfnSSMPutMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage0, sizeof(MptConfigurationPageSASPHY0));
+ pHlp->pfnSSMPutMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage1, sizeof(MptConfigurationPageSASPHY1));
+ }
+
+ /* The number of devices first. */
+ pHlp->pfnSSMPutU32(pSSM, pSasPages->cDevices);
+
+ for (PMptSASDevice pCurr = pSasPages->pSASDeviceHead; pCurr; pCurr = pCurr->pNext)
+ {
+ pHlp->pfnSSMPutMem(pSSM, &pCurr->SASDevicePage0, sizeof(MptConfigurationPageSASDevice0));
+ pHlp->pfnSSMPutMem(pSSM, &pCurr->SASDevicePage1, sizeof(MptConfigurationPageSASDevice1));
+ pHlp->pfnSSMPutMem(pSSM, &pCurr->SASDevicePage2, sizeof(MptConfigurationPageSASDevice2));
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX);
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE}
+ */
+static DECLCALLBACK(int) lsilogicR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ lsilogicR3Kick(pDevIns, pThis);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) lsilogicR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+
+ if ( uVersion > LSILOGIC_SAVED_STATE_VERSION
+ || 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;
+
+ PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, enmCtrlType, LSILOGICCTRLTYPE);
+ pHlp->pfnSSMGetU32(pSSM, &cDeviceStates);
+ rc = pHlp->pfnSSMGetU32(pSSM, &cPorts);
+ AssertRCReturn(rc, rc);
+
+ if (enmCtrlType != pThis->enmCtrlType)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Target config mismatch (Controller type): config=%d state=%d"),
+ pThis->enmCtrlType, enmCtrlType);
+ if (cDeviceStates != pThis->cDeviceStates)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Target config mismatch (Device states): config=%u state=%u"),
+ pThis->cDeviceStates, cDeviceStates);
+ if (cPorts != pThis->cPorts)
+ return pHlp->pfnSSMSetCfgError(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 = pHlp->pfnSSMGetBool(pSSM, &fPresent);
+ AssertRCReturn(rc, rc);
+ if (fPresent != (pThisCC->paDeviceStates[i].pDrvBase != NULL))
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Target %u config mismatch: config=%RTbool state=%RTbool"),
+ i, pThisCC->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 = &pThisCC->paDeviceStates[i];
+
+ AssertMsg(!pDevice->cOutstandingRequests,
+ ("There are still outstanding requests on this device\n"));
+ pHlp->pfnSSMGetU32V(pSSM, &pDevice->cOutstandingRequests);
+ }
+ /* Now the main device state. */
+ PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, pThis->enmState, LSILOGICSTATE);
+ PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, pThis->enmWhoInit, LSILOGICWHOINIT);
+ if (uVersion <= LSILOGIC_SAVED_STATE_VERSION_BOOL_DOORBELL)
+ {
+ /*
+ * The doorbell status flag distinguishes only between
+ * doorbell not in use or a Function handshake is currently in progress.
+ */
+ bool fDoorbellInProgress = false;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fDoorbellInProgress);
+ AssertRCReturn(rc, rc);
+ if (fDoorbellInProgress)
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_FN_HANDSHAKE;
+ else
+ pThis->enmDoorbellState = LSILOGICDOORBELLSTATE_NOT_IN_USE;
+ }
+ else
+ PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, pThis->enmDoorbellState, LSILOGICDOORBELLSTATE);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fDiagnosticEnabled);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fNotificationSent);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fEventNotificationEnabled);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uInterruptMask);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uInterruptStatus);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aMessage); i++)
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aMessage[i]);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->iMessage);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->cMessage);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->ReplyBuffer, sizeof(pThis->ReplyBuffer));
+ pHlp->pfnSSMGetU32(pSSM, &pThis->uNextReplyEntryRead);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->cReplySize);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->u16IOCFaultCode);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->u32HostMFAHighAddr);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->u32SenseBufferHighAddr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->cMaxDevices);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->cMaxBuses);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->cbReplyFrame);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->iDiagnosticAccess);
+
+ uint32_t cReplyQueueEntries, cRequestQueueEntries;
+ pHlp->pfnSSMGetU32(pSSM, &cReplyQueueEntries);
+ rc = pHlp->pfnSSMGetU32(pSSM, &cRequestQueueEntries);
+ AssertRCReturn(rc, rc);
+
+ if ( cReplyQueueEntries != pThis->cReplyQueueEntries
+ || cRequestQueueEntries != pThis->cRequestQueueEntries)
+ {
+ LogRel(("Changing queue sizes: cReplyQueueEntries=%u cRequestQueuEntries=%u\n", cReplyQueueEntries, cRequestQueueEntries));
+ if ( cReplyQueueEntries > RT_ELEMENTS(pThis->aReplyFreeQueue)
+ || cReplyQueueEntries < LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MIN
+ || cRequestQueueEntries > RT_ELEMENTS(pThis->aRequestQueue)
+ || cRequestQueueEntries < LSILOGICSCSI_REPLY_QUEUE_DEPTH_MIN)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Out of bounds: cReplyQueueEntries=%u cRequestQueueEntries=%u"),
+ cReplyQueueEntries, cRequestQueueEntries);
+ pThis->cReplyQueueEntries = cReplyQueueEntries;
+ pThis->cRequestQueueEntries = cRequestQueueEntries;
+ }
+
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uReplyFreeQueueNextEntryFreeWrite);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uReplyFreeQueueNextAddressRead);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uReplyPostQueueNextEntryFreeWrite);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uReplyPostQueueNextAddressRead);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uRequestQueueNextEntryFreeWrite);
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->uRequestQueueNextAddressRead);
+
+ PMptConfigurationPagesSupported pPages = pThisCC->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 pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: Expected SPI SCSI controller"));
+
+ pHlp->pfnSSMGetMem(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++)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aReplyFreeQueue[i]);
+ for (unsigned i = 0; i < pThis->cReplyQueueEntries; i++)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aReplyPostQueue[i]);
+ for (unsigned i = 0; i < pThis->cRequestQueueEntries; i++)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aRequestQueue[i]);
+
+ pHlp->pfnSSMGetU16(pSSM, &pThis->u16NextHandle);
+
+ if (uVersion > LSILOGIC_SAVED_STATE_VERSION_PRE_DIAG_MEM)
+ {
+
+ /* Save diagnostic memory register and data regions. */
+ pHlp->pfnSSMGetU32(pSSM, &pThis->u32DiagMemAddr);
+ uint32_t cMemRegions = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cMemRegions);
+ AssertLogRelRCReturn(rc, rc);
+
+ while (cMemRegions)
+ {
+ uint32_t u32AddrStart = 0;
+ pHlp->pfnSSMGetU32(pSSM, &u32AddrStart);
+ uint32_t u32AddrEnd = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32AddrEnd);
+ AssertLogRelRCReturn(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;
+ pHlp->pfnSSMGetMem(pSSM, &pRegion->au32Data[0], cRegion * sizeof(uint32_t));
+ lsilogicR3MemRegionInsert(pThisCC, pRegion);
+ pThisCC->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"));
+ pHlp->pfnSSMSkip(pSSM, cRegion * sizeof(uint32_t));
+ }
+ cMemRegions--;
+ }
+ }
+
+ /* Configuration pages */
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage0, sizeof(MptConfigurationPageManufacturing0));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage1, sizeof(MptConfigurationPageManufacturing1));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage2, sizeof(MptConfigurationPageManufacturing2));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage3, sizeof(MptConfigurationPageManufacturing3));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage4, sizeof(MptConfigurationPageManufacturing4));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage5, sizeof(MptConfigurationPageManufacturing5));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage6, sizeof(MptConfigurationPageManufacturing6));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage8, sizeof(MptConfigurationPageManufacturing8));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage9, sizeof(MptConfigurationPageManufacturing9));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->ManufacturingPage10, sizeof(MptConfigurationPageManufacturing10));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOUnitPage0, sizeof(MptConfigurationPageIOUnit0));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOUnitPage1, sizeof(MptConfigurationPageIOUnit1));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOUnitPage2, sizeof(MptConfigurationPageIOUnit2));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOUnitPage3, sizeof(MptConfigurationPageIOUnit3));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOUnitPage4, sizeof(MptConfigurationPageIOUnit4));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage0, sizeof(MptConfigurationPageIOC0));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage1, sizeof(MptConfigurationPageIOC1));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage2, sizeof(MptConfigurationPageIOC2));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage3, sizeof(MptConfigurationPageIOC3));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage4, sizeof(MptConfigurationPageIOC4));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->IOCPage6, sizeof(MptConfigurationPageIOC6));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->BIOSPage1, sizeof(MptConfigurationPageBIOS1));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->BIOSPage2, sizeof(MptConfigurationPageBIOS2));
+ pHlp->pfnSSMGetMem(pSSM, &pPages->BIOSPage4, sizeof(MptConfigurationPageBIOS4));
+
+ /* Device dependent pages */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PMptConfigurationPagesSpi pSpiPages = &pPages->u.SpiPages;
+
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage0, sizeof(MptConfigurationPageSCSISPIPort0));
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage1, sizeof(MptConfigurationPageSCSISPIPort1));
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aPortPages[0].SCSISPIPortPage2, sizeof(MptConfigurationPageSCSISPIPort2));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pSpiPages->aBuses[0].aDevicePages); i++)
+ {
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage0, sizeof(MptConfigurationPageSCSISPIDevice0));
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage1, sizeof(MptConfigurationPageSCSISPIDevice1));
+ pHlp->pfnSSMGetMem(pSSM, &pSpiPages->aBuses[0].aDevicePages[i].SCSISPIDevicePage2, sizeof(MptConfigurationPageSCSISPIDevice2));
+ pHlp->pfnSSMGetMem(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;
+
+ pHlp->pfnSSMGetU32(pSSM, &cbManufacturingPage7);
+ pHlp->pfnSSMGetU32(pSSM, &cbPage0);
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbPage1);
+ AssertRCReturn(rc, rc);
+
+ 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);
+
+ pHlp->pfnSSMGetMem(pSSM, pSasPages->pManufacturingPage7, pSasPages->cbManufacturingPage7);
+ pHlp->pfnSSMGetMem(pSSM, pSasPages->pSASIOUnitPage0, pSasPages->cbSASIOUnitPage0);
+ pHlp->pfnSSMGetMem(pSSM, pSasPages->pSASIOUnitPage1, pSasPages->cbSASIOUnitPage1);
+
+ pHlp->pfnSSMGetMem(pSSM, &pSasPages->SASIOUnitPage2, sizeof(MptConfigurationPageSASIOUnit2));
+ pHlp->pfnSSMGetMem(pSSM, &pSasPages->SASIOUnitPage3, sizeof(MptConfigurationPageSASIOUnit3));
+
+ rc = pHlp->pfnSSMGetU32(pSSM, &cPHYs);
+ AssertRCReturn(rc, rc);
+ if (cPHYs != pSasPages->cPHYs)
+ return VERR_SSM_LOAD_CONFIG_MISMATCH;
+
+ AssertPtr(pSasPages->paPHYs);
+ for (unsigned i = 0; i < pSasPages->cPHYs; i++)
+ {
+ pHlp->pfnSSMGetMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage0, sizeof(MptConfigurationPageSASPHY0));
+ pHlp->pfnSSMGetMem(pSSM, &pSasPages->paPHYs[i].SASPHYPage1, sizeof(MptConfigurationPageSASPHY1));
+ }
+
+ /* The number of devices first. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &pSasPages->cDevices);
+ AssertRCReturn(rc, rc);
+
+ PMptSASDevice pCurr = pSasPages->pSASDeviceHead;
+
+ for (unsigned i = 0; i < pSasPages->cDevices; i++)
+ {
+ AssertReturn(pCurr, VERR_SSM_LOAD_CONFIG_MISMATCH);
+
+ pHlp->pfnSSMGetMem(pSSM, &pCurr->SASDevicePage0, sizeof(MptConfigurationPageSASDevice0));
+ pHlp->pfnSSMGetMem(pSSM, &pCurr->SASDevicePage1, sizeof(MptConfigurationPageSASDevice1));
+ rc = pHlp->pfnSSMGetMem(pSSM, &pCurr->SASDevicePage2, sizeof(MptConfigurationPageSASDevice2));
+ AssertRCReturn(rc, rc);
+
+ pCurr = pCurr->pNext;
+ }
+
+ Assert(!pCurr);
+ }
+ else
+ AssertMsgFailed(("Invalid controller type %d\n", pThis->enmCtrlType));
+ }
+
+ if (uVersion <= LSILOGIC_SAVED_STATE_VERSION_PRE_VBOXSCSI_REMOVAL)
+ vboxscsiR3LoadExecLegacy(pHlp, pSSM);
+
+ uint32_t u32;
+ rc = pHlp->pfnSSMGetU32(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)
+{
+ PLSILOGICSCSICC pThisCC = RT_FROM_MEMBER(pInterface, LSILOGICSCSICC, ILeds);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PLSILOGICSCSI);
+ if (iLUN < pThis->cDeviceStates)
+ {
+ *ppLed = &pThisCC->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)
+{
+ PLSILOGICSCSICC pThisCC = RT_FROM_MEMBER(pInterface, LSILOGICSCSICC, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->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 = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+
+ for (uint32_t i = 0; i < pThis->cDeviceStates; i++)
+ {
+ PLSILOGICDEVICE pThisDevice = &pThisCC->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 = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for ahciR3Suspend and ahciR3PowerOff.
+ */
+static void lsilogicR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+
+ 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 = &pThisCC->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 = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ Log(("lsilogicR3Resume\n"));
+
+ lsilogicR3Kick(pDevIns, 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)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ Log(("%s: iLUN=%#x\n", __FUNCTION__, iLUN));
+ RT_NOREF(fFlags);
+
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG, ("LsiLogic: Device does not support hotplugging\n"));
+
+ if (iLUN >= pThis->cDeviceStates)
+ return;
+
+ /*
+ * Zero some important members.
+ */
+ PLSILOGICDEVICE pDevice = &pThisCC->paDeviceStates[iLUN];
+ 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 = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ PLSILOGICDEVICE pDevice = &pThisCC->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(RT_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(RT_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 = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ int rc;
+
+ rc = lsilogicR3HardReset(pDevIns, pThis, pThisCC);
+ AssertRC(rc);
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by lsilogicR3Reset.}
+ */
+static DECLCALLBACK(bool) lsilogicR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PLSILOGICSCSI pThis = PDMDEVINS_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 = PDMDEVINS_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,pfnPowerOff}
+ */
+static DECLCALLBACK(void) lsilogicR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("lsilogicR3PowerOff\n"));
+ lsilogicR3SuspendOrPowerOff(pDevIns);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) lsilogicR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->ReplyFreeQueueCritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->ReplyPostQueueCritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->RequestQueueCritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->ReplyFreeQueueWriteCritSect);
+
+ if (RTCritSectIsInitialized(&pThisCC->CritSectMemRegns))
+ RTCritSectDelete(&pThisCC->CritSectMemRegns);
+
+ RTMemFree(pThisCC->paDeviceStates);
+ pThisCC->paDeviceStates = NULL;
+
+ if (pThis->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEvtProcess);
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+
+ lsilogicR3ConfigurationPagesFree(pThis, pThisCC);
+ lsilogicR3MemRegionsFree(pThisCC);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) lsilogicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+ PLSILOGICSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PLSILOGICSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Initialize enought of the state to make the destructure not trip up.
+ */
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ RTListInit(&pThisCC->ListMemRegns);
+ pThis->hMmioReg = NIL_IOMMMIOHANDLE;
+ pThis->hMmioDiag = NIL_IOMMMIOHANDLE;
+ pThis->hIoPortsReg = NIL_IOMIOPORTHANDLE;
+ pThis->hIoPortsBios = NIL_IOMIOPORTHANDLE;
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->IBase.pfnQueryInterface = lsilogicR3StatusQueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = lsilogicR3StatusQueryStatusLed;
+
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns,
+ "ReplyQueueDepth|"
+ "RequestQueueDepth|"
+ "ControllerType|"
+ "NumPorts|"
+ "Bootable", /* Keep it for legacy configs, even though it doesn't do anything anymore, see @bugref{4841}. */
+ "");
+
+ rc = pHlp->pfnCFGMQueryU32Def(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"));
+ if ( pThis->cReplyQueueEntries < LSILOGICSCSI_REPLY_QUEUE_DEPTH_MIN
+ || pThis->cReplyQueueEntries > LSILOGICSCSI_REPLY_QUEUE_DEPTH_MAX - 1 /* see +1 later in the function */)
+ return PDMDevHlpVMSetError(pDevIns, VERR_OUT_OF_RANGE, RT_SRC_POS,
+ N_("LsiLogic configuration error: 'ReplyQueueDepth' = %u is out of ranage (%u..%u)"),
+ pThis->cReplyQueueEntries, LSILOGICSCSI_REPLY_QUEUE_DEPTH_MIN,
+ LSILOGICSCSI_REPLY_QUEUE_DEPTH_MAX - 1);
+ Log(("%s: ReplyQueueDepth=%u\n", __FUNCTION__, pThis->cReplyQueueEntries));
+
+ rc = pHlp->pfnCFGMQueryU32Def(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"));
+ if ( pThis->cRequestQueueEntries < LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MIN
+ || pThis->cRequestQueueEntries > LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MAX - 1 /* see +1 later in the function */)
+ return PDMDevHlpVMSetError(pDevIns, VERR_OUT_OF_RANGE, RT_SRC_POS,
+ N_("LsiLogic configuration error: 'RequestQueue' = %u is out of ranage (%u..%u)"),
+ pThis->cRequestQueueEntries, LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MIN,
+ LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MIN - 1);
+ Log(("%s: RequestQueueDepth=%u\n", __FUNCTION__, pThis->cRequestQueueEntries));
+
+ char szCtrlType[64];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "ControllerType", szCtrlType, sizeof(szCtrlType), 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__, szCtrlType));
+ rc = lsilogicR3GetCtrlTypeFromString(pThis, szCtrlType);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic configuration error: failed to determine controller type from string"));
+
+ char szDevTag[20];
+ RTStrPrintf(szDevTag, sizeof(szDevTag), "LSILOGIC%s-%u",
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI ? "SPI" : "SAS",
+ iInstance);
+
+ rc = pHlp->pfnCFGMQueryU8(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"));
+
+ /* Init static parts. */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, LSILOGICSCSI_PCI_VENDOR_ID); /* LsiLogic */
+ if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI)
+ {
+ PDMPciDevSetDeviceId(pPciDev, LSILOGICSCSI_PCI_SPI_DEVICE_ID); /* LSI53C1030 */
+ PDMPciDevSetSubSystemVendorId(pPciDev, LSILOGICSCSI_PCI_SPI_SUBSYSTEM_VENDOR_ID);
+ PDMPciDevSetSubSystemId(pPciDev, LSILOGICSCSI_PCI_SPI_SUBSYSTEM_ID);
+ }
+ else if (pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SAS)
+ {
+ PDMPciDevSetDeviceId(pPciDev, LSILOGICSCSI_PCI_SAS_DEVICE_ID); /* SAS1068 */
+ PDMPciDevSetSubSystemVendorId(pPciDev, LSILOGICSCSI_PCI_SAS_SUBSYSTEM_VENDOR_ID);
+ PDMPciDevSetSubSystemId(pPciDev, LSILOGICSCSI_PCI_SAS_SUBSYSTEM_ID);
+ }
+ else
+ AssertMsgFailed(("Invalid controller type: %d\n", pThis->enmCtrlType));
+
+ PDMPciDevSetClassProg(pPciDev, 0x00); /* SCSI */
+ PDMPciDevSetClassSub(pPciDev, 0x00); /* SCSI */
+ PDMPciDevSetClassBase(pPciDev, 0x01); /* Mass storage */
+ PDMPciDevSetInterruptPin(pPciDev, 0x01); /* Interrupt pin A */
+
+# ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+# endif
+
+ /*
+ * Create critical sections protecting the reply post and free queues.
+ */
+ 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"));
+
+ /*
+ * Critical section protecting the memory regions.
+ */
+ rc = RTCritSectInit(&pThisCC->CritSectMemRegns);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: Failed to initialize critical section protecting the memory regions"));
+
+ /*
+ * Register the PCI device, it's I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ 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;
+ Assert(pDevIns->pReg->cMaxMsixVectors >= MsiReg.cMsixVectors); /* fix device registration when enabling this */
+# 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 */
+ PDMPciDevSetCapabilityList(pPciDev, 0x0);
+ }
+# endif
+
+ /* Region #0: I/O ports. */
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, LSILOGIC_PCI_SPACE_IO_SIZE,
+ lsilogicIOPortWrite, lsilogicIOPortRead, NULL /*pvUser*/,
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI ? "LsiLogic" : "LsiLogicSas",
+ NULL /*paExtDesc*/, &pThis->hIoPortsReg);
+ AssertRCReturn(rc, rc);
+
+ /* Region #1: MMIO.
+ *
+ * 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 = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 1 /*iPciRegion*/, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM,
+ lsilogicMMIOWrite, lsilogicMMIORead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI ? "LsiLogic" : "LsiLogicSas",
+ &pThis->hMmioReg);
+ AssertRCReturn(rc, rc);
+
+ /* Region #2: MMIO - Diag. */
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 2 /*iPciRegion*/, LSILOGIC_PCI_SPACE_MEM_SIZE, PCI_ADDRESS_SPACE_MEM,
+ lsilogicDiagnosticWrite, lsilogicDiagnosticRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ pThis->enmCtrlType == LSILOGICCTRLTYPE_SCSI_SPI ? "LsiLogicDiag" : "LsiLogicSasDiag",
+ &pThis->hMmioDiag);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * We need one entry free in the queue.
+ */
+ pThis->cReplyQueueEntries++;
+ AssertLogRelReturn(pThis->cReplyQueueEntries <= RT_ELEMENTS(pThis->aReplyFreeQueue), VERR_INTERNAL_ERROR_3);
+ AssertLogRelReturn(pThis->cReplyQueueEntries <= RT_ELEMENTS(pThis->aReplyPostQueue), VERR_INTERNAL_ERROR_3);
+
+ pThis->cRequestQueueEntries++;
+ AssertLogRelReturn(pThis->cRequestQueueEntries <= RT_ELEMENTS(pThis->aRequestQueue), VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Device states.
+ */
+ 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
+ AssertLogRelMsgFailedReturn(("Invalid controller type: %d\n", pThis->enmCtrlType), VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Create event semaphore and worker thread.
+ */
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->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 = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("LsiLogic: Failed to create SUP event semaphore"));
+
+ /*
+ * Allocate device states.
+ */
+ pThisCC->paDeviceStates = (PLSILOGICDEVICE)RTMemAllocZ(sizeof(LSILOGICDEVICE) * pThis->cDeviceStates);
+ if (!pThisCC->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 = &pThisCC->paDeviceStates[i];
+
+ /* Initialize static parts of the device. */
+ pDevice->iLUN = i;
+ pDevice->pDevIns = pDevIns;
+ 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;
+ RTStrPrintf(pDevice->szName, sizeof(pDevice->szName), "Device%u", i);
+
+ /* Attach SCSI driver. */
+ rc = PDMDevHlpDriverAttach(pDevIns, pDevice->iLUN, &pDevice->IBase, &pDevice->pDrvBase, pDevice->szName);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query the media interface. */
+ pDevice->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pDevice->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(RT_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(RT_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", pDevice->szName));
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("LsiLogic: Failed to attach %s\n", pDevice->szName));
+ return rc;
+ }
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else
+ AssertMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Failed to attach to status driver. rc=%Rrc\n", rc),
+ PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic cannot attach to status driver")));
+
+ /* 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);
+
+ /* Allocate configuration pages. */
+ rc = lsilogicR3ConfigurationPagesAlloc(pThis, pThisCC);
+ if (RT_FAILURE(rc))
+ PDMDEV_SET_ERROR(pDevIns, rc, N_("LsiLogic: Failed to allocate memory for configuration pages"));
+
+ /* Perform hard reset. */
+ rc = lsilogicR3HardReset(pDevIns, pThis, pThisCC);
+ AssertRC(rc);
+
+ return rc;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) lsilogicRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PLSILOGICSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PLSILOGICSCSI);
+
+ /* Setup callbacks for this context: */
+ int rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsReg, lsilogicIOPortWrite, lsilogicIOPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmioReg, lsilogicMMIOWrite, lsilogicMMIORead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmioDiag, lsilogicDiagnosticWrite, lsilogicDiagnosticRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure - SPI SCSI controller.
+ */
+const PDMDEVREG g_DeviceLsiLogicSCSI =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "lsilogicscsi",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+ /* .fClass = */ PDM_DEVREG_CLASS_STORAGE,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(LSILOGICSCSI),
+ /* .cbInstanceCC = */ sizeof(LSILOGICSCSICC),
+ /* .cbInstanceRC = */ sizeof(LSILOGICSCSIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "LSI Logic 53c1030 SCSI controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ lsilogicR3Construct,
+ /* .pfnDestruct = */ lsilogicR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ lsilogicR3Reset,
+ /* .pfnSuspend = */ lsilogicR3Suspend,
+ /* .pfnResume = */ lsilogicR3Resume,
+ /* .pfnAttach = */ lsilogicR3Attach,
+ /* .pfnDetach = */ lsilogicR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ lsilogicR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ lsilogicRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ lsilogicRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+/**
+ * The device registration structure - SAS controller.
+ */
+const PDMDEVREG g_DeviceLsiLogicSAS =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "lsilogicsas",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | 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,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(LSILOGICSCSI),
+ /* .cbInstanceCC = */ sizeof(LSILOGICSCSICC),
+ /* .cbInstanceRC = */ sizeof(LSILOGICSCSIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "LSI Logic SAS1068 controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ lsilogicR3Construct,
+ /* .pfnDestruct = */ lsilogicR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ lsilogicR3Reset,
+ /* .pfnSuspend = */ lsilogicR3Suspend,
+ /* .pfnResume = */ lsilogicR3Resume,
+ /* .pfnAttach = */ lsilogicR3Attach,
+ /* .pfnDetach = */ lsilogicR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ lsilogicR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ lsilogicRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ lsilogicRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#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..9af14623
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.h
@@ -0,0 +1,3531 @@
+/* $Id: DevLsiLogicSCSI.h $ */
+/** @file
+ * VBox storage devices: LsiLogic LSI53c1030 SCSI controller - Defines and structures.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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_MIN 8 /**< (bird just picked this out thin air) */
+#define LSILOGICSCSI_REQUEST_QUEUE_DEPTH_MAX 1024 /**< (bird just picked this out thin air) */
+#define LSILOGICSCSI_REQUEST_QUEUE_DEPTH_DEFAULT 256
+
+#define LSILOGICSCSI_REPLY_QUEUE_DEPTH_MIN 8 /**< (bird just picked this out thin air) */
+#define LSILOGICSCSI_REPLY_QUEUE_DEPTH_MAX 1024 /**< (bird just picked this out thin air) */
+#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.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptSGEntrySimple64, 12);
+
+/**
+ * A simple SG element for a 32bit address.
+ */
+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;
+AssertCompileSize(MptSGEntrySimple32, 8);
+
+/**
+ * A chain SG element.
+ */
+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;
+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.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptDefaultReplyMessage, 20);
+
+/**
+ * IO controller init request.
+ */
+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;
+AssertCompileSize(MptIOCInitRequest, 24);
+
+/**
+ * IO controller init reply.
+ */
+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;
+AssertCompileSize(MptIOCInitReply, 20);
+
+/**
+ * IO controller facts request.
+ */
+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;
+AssertCompileSize(MptIOCFactsRequest, 12);
+
+/**
+ * IO controller facts reply.
+ */
+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;
+AssertCompileSize(MptIOCFactsReply, 60);
+
+/**
+ * Port facts request
+ */
+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;
+AssertCompileSize(MptPortFactsRequest, 12);
+
+/**
+ * Port facts reply.
+ */
+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;
+AssertCompileSize(MptPortFactsReply, 40);
+
+/**
+ * Port Enable request.
+ */
+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;
+AssertCompileSize(MptPortEnableRequest, 12);
+
+/**
+ * Port enable reply.
+ */
+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;
+AssertCompileSize(MptPortEnableReply, 20);
+
+/**
+ * Event notification request.
+ */
+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;
+AssertCompileSize(MptEventNotificationRequest, 12);
+
+/**
+ * Event notification reply.
+ */
+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;
+AssertCompileSize(MptEventNotificationReply, 32);
+
+#define MPT_EVENT_EVENT_CHANGE (0x0000000a)
+
+/**
+ * FW download request.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptFWDownloadReply, 20);
+
+/**
+ * FW upload request.
+ */
+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;
+AssertCompileSize(MptFWUploadRequest, 12);
+
+/**
+ * FW upload reply.
+ */
+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;
+AssertCompileSize(MptFWUploadReply, 24);
+
+/**
+ * SCSI IO Request
+ */
+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;
+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.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptSCSITaskManagementRequest, 52);
+
+/**
+ * SCSI task management reply.
+ */
+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;
+AssertCompileSize(MptSCSITaskManagementReply, 24);
+
+/**
+ * Page address for SAS expander page types.
+ */
+typedef union MptConfigurationPageAddressSASExpander
+{
+ struct
+ {
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+ } Form0And2;
+ struct
+ {
+ uint16_t u16Handle;
+ uint8_t u8PhyNum;
+ uint8_t u8Reserved;
+ } Form1;
+} MptConfigurationPageAddressSASExpander, *PMptConfigurationPageAddressSASExpander;
+AssertCompileSize(MptConfigurationPageAddressSASExpander, 4);
+
+/**
+ * Page address for SAS device page types.
+ */
+typedef union MptConfigurationPageAddressSASDevice
+{
+ struct
+ {
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+ } Form0And2;
+ struct
+ {
+ uint8_t u8TargetID;
+ uint8_t u8Bus;
+ uint8_t u8Reserved;
+ } Form1; /**< r=bird: only three bytes? */
+} MptConfigurationPageAddressSASDevice, *PMptConfigurationPageAddressSASDevice;
+AssertCompileSize(MptConfigurationPageAddressSASDevice, 4);
+
+/**
+ * Page address for SAS PHY page types.
+ */
+typedef union MptConfigurationPageAddressSASPHY
+{
+ struct
+ {
+ uint8_t u8PhyNumber;
+ uint8_t u8Reserved[3];
+ } Form0;
+ struct
+ {
+ uint16_t u16Index;
+ uint16_t u16Reserved;
+ } Form1;
+} MptConfigurationPageAddressSASPHY, *PMptConfigurationPageAddressSASPHY;
+AssertCompileSize(MptConfigurationPageAddressSASPHY, 4);
+
+/**
+ * Page address for SAS Enclosure page types.
+ */
+typedef struct MptConfigurationPageAddressSASEnclosure
+{
+ uint16_t u16Handle;
+ uint16_t u16Reserved;
+} MptConfigurationPageAddressSASEnclosure, *PMptConfigurationPageAddressSASEnclosure;
+AssertCompileSize(MptConfigurationPageAddressSASEnclosure, 4);
+
+/**
+ * Union of all possible address types.
+ */
+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;
+AssertCompileSize(MptConfigurationPageAddress, 4);
+
+#define MPT_CONFIGURATION_PAGE_ADDRESS_GET_SAS_FORM(x) (((x).u32PageAddress >> 28) & 0x0f)
+
+/**
+ * Configuration request
+ */
+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;
+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.
+ */
+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;
+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.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptConfigurationPageHeader, 4);
+
+/**
+ * Extended configuration page header - Common to all extended pages.
+ */
+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;
+AssertCompileSize(MptExtendedConfigurationPageHeader, 8);
+
+/**
+ * Manufacturing page 0. - Readonly.
+ */
+typedef struct MptConfigurationPageManufacturing0 /**< @todo r=bird: This and a series of other structs could save a lot of 'u.' typing by promoting the inner 'u' union... */
+{
+ /** 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;
+AssertCompileSize(MptConfigurationPageManufacturing0, 76);
+
+/**
+ * Manufacturing page 1. - Readonly Persistent.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing1, 260);
+
+/**
+ * Manufacturing page 2. - Readonly.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing2, 8);
+
+/**
+ * Manufacturing page 3. - Readonly.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing3, 8);
+
+/**
+ * Manufacturing page 4. - Readonly.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing4, 84);
+
+/**
+ * Manufacturing page 5 - Readonly.
+ */
+#pragma pack(1) /* u64BaseWWID is at offset 4, which isn't natural for uint64_t. */
+typedef struct MptConfigurationPageManufacturing5
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[88];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Base WWID.
+ * @note Not aligned on 8-byte boundrary */
+ 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.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing6, 4);
+
+/**
+ * Manufacutring page 7 - PHY element.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing7PHY, 24);
+
+/**
+ * Manufacturing page 7 - Readonly.
+ */
+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;
+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.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing8, 4);
+
+/**
+ * Manufacturing page 9 - Readonly.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing9, 4);
+
+/**
+ * Manufacturing page 10 - Readonly.
+ */
+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;
+AssertCompileSize(MptConfigurationPageManufacturing10, 4);
+
+/**
+ * IO Unit page 0. - Readonly.
+ */
+#pragma pack(1) /* u64UniqueIdentifier is at offset 4, which isn't natural for uint64_t. */
+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.
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOUnit1, 8);
+
+/**
+ * Adapter Ordering.
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOUnit2AdapterOrdering, 4);
+
+/**
+ * IO Unit page 2. - Read/Write.
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOUnit2, 28);
+
+/*
+ * IO Unit page 3. - Read/Write.
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOUnit3, 8);
+
+/*
+ * IO Unit page 4. - Readonly for everyone except the BIOS.
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOUnit4, 20);
+
+/**
+ * IOC page 0. - Readonly
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC0, 28);
+
+/**
+ * IOC page 1. - Read/Write
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC1, 16);
+
+/**
+ * IOC page 2. - Readonly
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC2, 12);
+
+/**
+ * IOC page 3. - Readonly
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC3, 8);
+
+/**
+ * IOC page 4. - Read/Write
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC4, 8);
+
+/**
+ * IOC page 6. - Read/Write
+ */
+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;
+AssertCompileSize(MptConfigurationPageIOC6, 60);
+
+/**
+ * BIOS page 1 - Read/write.
+ */
+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;
+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.
+ */
+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;
+#if 0 /** @todo r=bird: The u16PCISlotNo member looks like it has the wrong type, but I cannot immediately locate specs and check. */
+ /** 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;
+#endif
+ /** 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;
+AssertCompileMemberAlignment(MptConfigurationPageBIOS2, u.fields, 8);
+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) /* u64ReassignmentBaseWWID starts at offset 4, which isn't normally natural for uint64_t. */
+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
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIPort0, 12);
+
+/**
+ * SCSI-SPI port page 1. - Read/Write
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIPort1, 12);
+
+/**
+ * Device settings for one device.
+ */
+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;
+AssertCompileSize(MptDeviceSettings, 4);
+
+/**
+ * SCSI-SPI port page 2. - Read/Write for the BIOS
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIPort2, 76);
+
+/**
+ * SCSI-SPI device page 0. - Readonly
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIDevice0, 12);
+
+/**
+ * SCSI-SPI device page 1. - Read/Write
+ */
+typedef struct MptConfigurationPageSCSISPIDevice1
+{
+ /** Union. */
+ union
+ {
+ /** Byte view. */
+ uint8_t abPageData[16];
+ /** Field view. */
+ struct
+ {
+ /** The omnipresent header. */
+ MptConfigurationPageHeader Header;
+ /** Requested Parameters. */
+ /** Information Units enable. */
+ unsigned fInformationUnitsEnable: 1;
+ /** Dual Transfers Enable. */
+ unsigned fDTEnable: 1;
+ /** QAS enable. */
+ unsigned 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. */
+ unsigned fWide: 1;
+ /** Reserved. */
+ unsigned fReserved1: 1;
+ /** AIP enable. */
+ unsigned fAIPEnable: 1;
+ /** Reserved. */
+ unsigned fReserved2: 1;
+ /** WDTR disallowed. */
+ unsigned fWDTRDisallowed: 1;
+ /** SDTR disallowed. */
+ unsigned fSDTRDisallowed: 1;
+ /** Reserved. */
+ unsigned u29Reserved: 29;
+ } fields;
+ } u;
+} MptConfigurationPageSCSISPIDevice1, *PMptConfigurationPageSCSISPIDevice1;
+AssertCompileSize(MptConfigurationPageSCSISPIDevice1, 16);
+
+/**
+ * SCSI-SPI device page 2. - Read/Write
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIDevice2, 16);
+
+/**
+ * SCSI-SPI device page 3 (Revision G). - Readonly
+ */
+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;
+AssertCompileSize(MptConfigurationPageSCSISPIDevice3, 12);
+
+/**
+ * PHY entry for the SAS I/O unit page 0
+ */
+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;
+AssertCompileSize(MptConfigurationPageSASIOUnit0PHY, 16);
+
+/**
+ * SAS I/O Unit page 0 - Readonly
+ */
+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;
+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
+ */
+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;
+AssertCompileSize(MptConfigurationPageSASIOUnit1PHY, 12);
+
+/**
+ * SAS I/O Unit page 1 - Read/Write
+ */
+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;
+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
+ */
+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;
+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
+ */
+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;
+AssertCompileSize(MptConfigurationPageSASIOUnit3, 44);
+
+/**
+ * SAS PHY page 0 - Readonly
+ */
+#pragma pack(1) /* SASAddress starts at offset 12, which isn't typically natural for uint64_t (inside it). */
+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
+ */
+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;
+AssertCompileSize(MptConfigurationPageSASPHY1, 28);
+
+/**
+ * SAS Device page 0 - Readonly
+ */
+#pragma pack(1) /* SASAddress starts at offset 12, which isn't typically natural for uint64_t (inside it). */
+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) /* SASAddress starts at offset 12, which isn't typically natural for uint64_t (inside it). */
+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) /* Because of a uint64_t inside SASAddress, the struct size would be 24 without packing. */
+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) /* SASAddress starts at offset 12, which isn't typically natural for uint64_t (inside it). */
+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
+ */
+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;
+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;
+
+typedef struct MptConfigurationPagesSas
+{
+ /** Pointer to the manufacturing page 7 */
+ PMptConfigurationPageManufacturing7 pManufacturingPage7;
+ /** Size of the manufacturing page 7 */
+ uint32_t cbManufacturingPage7;
+ /** Size of the I/O unit page 0 */
+ uint32_t cbSASIOUnitPage0;
+ /** Pointer to the I/O unit page 0 */
+ PMptConfigurationPageSASIOUnit0 pSASIOUnitPage0;
+ /** Pointer to the I/O unit page 1 */
+ PMptConfigurationPageSASIOUnit1 pSASIOUnitPage1;
+ /** Size of the I/O unit page 1 */
+ uint32_t cbSASIOUnitPage1;
+ /** 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;
+ uint32_t u32Padding;
+ /** Pointer to the first SAS device. */
+ R3PTRTYPE(PMptSASDevice) pSASDeviceHead;
+ /** Pointer to the last SAS device. */
+ R3PTRTYPE(PMptSASDevice) pSASDeviceTail;
+} MptConfigurationPagesSas, *PMptConfigurationPagesSas;
+AssertCompile(RTASSERT_OFFSET_OF(MptConfigurationPagesSas,cbSASIOUnitPage0) + 4 == RTASSERT_OFFSET_OF(MptConfigurationPagesSas, pSASIOUnitPage0));
+AssertCompile(RTASSERT_OFFSET_OF(MptConfigurationPagesSas,cPHYs) + 4 == RTASSERT_OFFSET_OF(MptConfigurationPagesSas, paPHYs));
+AssertCompile(RTASSERT_OFFSET_OF(MptConfigurationPagesSas,cDevices) + 8 == RTASSERT_OFFSET_OF(MptConfigurationPagesSas, pSASDeviceHead));
+
+
+/**
+ * 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) \
+ RT_ZERO(*pg); \
+ 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) \
+ RT_ZERO(*pg); \
+ 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) \
+ RT_ZERO(*pg); \
+ 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) \
+ RT_ZERO(*pg); \
+ 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) \
+ RT_BZERO(pg, cb); \
+ (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/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp
new file mode 100644
index 00000000..4190ee00
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp
@@ -0,0 +1,2772 @@
+/* $Id: DevVirtioSCSI.cpp $ */
+/** @file
+ * VBox storage devices - Virtio SCSI Driver
+ *
+ * Log-levels used:
+ * - Level 1: The most important (but usually rare) things to note
+ * - Level 2: SCSI command logging
+ * - Level 3: Vector and I/O transfer summary (shows what client sent an expects and fulfillment)
+ * - Level 6: Device <-> Guest Driver negotation, traffic, notifications and state handling
+ * - Level 12: Brief formatted hex dumps of I/O data
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+//#define LOG_GROUP LOG_GROUP_DRV_SCSI
+#define LOG_GROUP LOG_GROUP_DEV_VIRTIO
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/pdmcritsect.h>
+#include <VBox/AssertGuest.h>
+#include <VBox/msi.h>
+#include <VBox/version.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <VBox/sup.h>
+#include "../build/VBoxDD.h"
+#include <VBox/scsi.h>
+#ifdef IN_RING3
+# include <iprt/alloc.h>
+# include <iprt/memcache.h>
+# include <iprt/semaphore.h>
+# include <iprt/sg.h>
+# include <iprt/param.h>
+# include <iprt/uuid.h>
+#endif
+#include "../VirtIO/VirtioCore.h"
+
+#include "VBoxSCSI.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The current saved state version. */
+#define VIRTIOSCSI_SAVED_STATE_VERSION UINT32_C(1)
+
+
+#define LUN0 0
+/** @name VirtIO 1.0 SCSI Host feature bits (See VirtIO 1.0 specification, Section 5.6.3)
+ * @{ */
+#define VIRTIO_SCSI_F_INOUT RT_BIT_64(0) /** Request is device readable AND writeable */
+#define VIRTIO_SCSI_F_HOTPLUG RT_BIT_64(1) /** Host allows hotplugging SCSI LUNs & targets */
+#define VIRTIO_SCSI_F_CHANGE RT_BIT_64(2) /** Host LUNs chgs via VIRTIOSCSI_T_PARAM_CHANGE evt */
+#define VIRTIO_SCSI_F_T10_PI RT_BIT_64(3) /** Add T10 port info (DIF/DIX) in SCSI req hdr */
+/** @} */
+
+
+#define VIRTIOSCSI_HOST_SCSI_FEATURES_ALL \
+ (VIRTIO_SCSI_F_INOUT | VIRTIO_SCSI_F_HOTPLUG | VIRTIO_SCSI_F_CHANGE | VIRTIO_SCSI_F_T10_PI)
+
+#define VIRTIOSCSI_HOST_SCSI_FEATURES_NONE 0
+
+#define VIRTIOSCSI_HOST_SCSI_FEATURES_OFFERED VIRTIOSCSI_HOST_SCSI_FEATURES_NONE
+
+#define VIRTIOSCSI_REQ_VIRTQ_CNT 4 /**< T.B.D. Consider increasing */
+#define VIRTIOSCSI_VIRTQ_CNT (VIRTIOSCSI_REQ_VIRTQ_CNT + 2)
+#define VIRTIOSCSI_MAX_TARGETS 256 /**< T.B.D. Figure out a a good value for this. */
+#define VIRTIOSCSI_MAX_LUN 1 /**< VirtIO specification, section 5.6.4 */
+#define VIRTIOSCSI_MAX_COMMANDS_PER_LUN 128 /**< T.B.D. What is a good value for this? */
+#define VIRTIOSCSI_MAX_SEG_COUNT 126 /**< T.B.D. What is a good value for this? */
+#define VIRTIOSCSI_MAX_SECTORS_HINT 0x10000 /**< VirtIO specification, section 5.6.4 */
+#define VIRTIOSCSI_MAX_CHANNEL_HINT 0 /**< VirtIO specification, section 5.6.4 should be 0 */
+
+#define PCI_DEVICE_ID_VIRTIOSCSI_HOST 0x1048 /**< Informs guest driver of type of VirtIO device */
+#define PCI_CLASS_BASE_MASS_STORAGE 0x01 /**< PCI Mass Storage device class */
+#define PCI_CLASS_SUB_SCSI_STORAGE_CONTROLLER 0x00 /**< PCI SCSI Controller subclass */
+#define PCI_CLASS_PROG_UNSPECIFIED 0x00 /**< Programming interface. N/A. */
+#define VIRTIOSCSI_PCI_CLASS 0x01 /**< Base class Mass Storage? */
+
+#define VIRTIOSCSI_SENSE_SIZE_DEFAULT 96 /**< VirtIO 1.0: 96 on reset, guest can change */
+#define VIRTIOSCSI_SENSE_SIZE_MAX 4096 /**< Picked out of thin air by bird. */
+#define VIRTIOSCSI_CDB_SIZE_DEFAULT 32 /**< VirtIO 1.0: 32 on reset, guest can change */
+#define VIRTIOSCSI_CDB_SIZE_MAX 255 /**< Picked out of thin air by bird. */
+#define VIRTIOSCSI_PI_BYTES_IN 1 /**< Value TBD (see section 5.6.6.1) */
+#define VIRTIOSCSI_PI_BYTES_OUT 1 /**< Value TBD (see section 5.6.6.1) */
+#define VIRTIOSCSI_DATA_OUT 512 /**< Value TBD (see section 5.6.6.1) */
+
+/**
+ * VirtIO SCSI Host Device device-specific queue indicies.
+ * (Note: # of request queues is determined by virtio_scsi_config.num_queues. VirtIO 1.0, 5.6.4)
+ */
+#define CONTROLQ_IDX 0 /**< VirtIO Spec-defined Index of control queue */
+#define EVENTQ_IDX 1 /**< VirtIO Spec-defined Index of event queue */
+#define VIRTQ_REQ_BASE 2 /**< VirtIO Spec-defined base index of req. queues */
+
+#define VIRTQNAME(uVirtqNbr) (pThis->aszVirtqNames[uVirtqNbr]) /**< Macro to get queue name from its index */
+#define CBVIRTQNAME(uVirtqNbr) RTStrNLen(VIRTQNAME(uVirtqNbr), sizeof(VIRTQNAME(uVirtqNbr)))
+
+#define IS_REQ_VIRTQ(uVirtqNbr) (uVirtqNbr >= VIRTQ_REQ_BASE && uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT)
+
+#define VIRTIO_IS_IN_DIRECTION(pMediaExTxDirEnumValue) \
+ ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE)
+
+#define VIRTIO_IS_OUT_DIRECTION(pMediaExTxDirEnumValue) \
+ ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE)
+
+#define IS_VIRTQ_EMPTY(pDevIns, pVirtio, uVirtqNbr) \
+ (virtioCoreVirtqAvailBufCount(pDevIns, pVirtio, uVirtqNbr) == 0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * VirtIO SCSI Host Device device-specific configuration (see VirtIO 1.0, section 5.6.4)
+ * VBox VirtIO core issues callback to this VirtIO device-specific implementation to handle
+ * MMIO accesses to device-specific configuration parameters.
+ */
+typedef struct virtio_scsi_config
+{
+ uint32_t uNumVirtqs; /**< num_queues \# of req q's exposed by dev */
+ uint32_t uSegMax; /**< seg_max Max \# of segs allowed in cmd */
+ uint32_t uMaxSectors; /**< max_sectors Hint to guest max xfer to use */
+ uint32_t uCmdPerLun; /**< cmd_per_lun Max \# of link cmd sent per lun */
+ uint32_t uEventInfoSize; /**< event_info_size Fill max, evtq bufs */
+ uint32_t uSenseSize; /**< sense_size Max sense data size dev writes */
+ uint32_t uCdbSize; /**< cdb_size Max CDB size driver writes */
+ uint16_t uMaxChannel; /**< max_channel Hint to guest driver */
+ uint16_t uMaxTarget; /**< max_target Hint to guest driver */
+ uint32_t uMaxLun; /**< max_lun Hint to guest driver */
+} VIRTIOSCSI_CONFIG_T, PVIRTIOSCSI_CONFIG_T;
+
+/** @name VirtIO 1.0 SCSI Host Device device specific control types
+ * @{ */
+#define VIRTIOSCSI_T_NO_EVENT 0
+#define VIRTIOSCSI_T_TRANSPORT_RESET 1
+#define VIRTIOSCSI_T_ASYNC_NOTIFY 2 /**< Asynchronous notification */
+#define VIRTIOSCSI_T_PARAM_CHANGE 3
+/** @} */
+
+/**
+ * Device operation: eventq
+ */
+#define VIRTIOSCSI_T_EVENTS_MISSED UINT32_C(0x80000000)
+typedef struct virtio_scsi_event
+{
+ // Device-writable part
+ uint32_t uEvent; /**< event */
+ uint8_t abVirtioLun[8]; /**< lun */
+ uint32_t uReason; /**< reason */
+} VIRTIOSCSI_EVENT_T, *PVIRTIOSCSI_EVENT_T;
+
+/** @name VirtIO 1.0 SCSI Host Device device specific event types
+ * @{ */
+#define VIRTIOSCSI_EVT_RESET_HARD 0 /**< */
+#define VIRTIOSCSI_EVT_RESET_RESCAN 1 /**< */
+#define VIRTIOSCSI_EVT_RESET_REMOVED 2 /**< */
+/** @} */
+
+/**
+ * Device operation: reqestq
+ */
+#pragma pack(1)
+typedef struct REQ_CMD_HDR_T
+{
+ uint8_t abVirtioLun[8]; /**< lun */
+ uint64_t uId; /**< id */
+ uint8_t uTaskAttr; /**< task_attr */
+ uint8_t uPrio; /**< prio */
+ uint8_t uCrn; /**< crn */
+} REQ_CMD_HDR_T;
+#pragma pack()
+AssertCompileSize(REQ_CMD_HDR_T, 19);
+
+typedef struct REQ_CMD_PI_T
+{
+ uint32_t uPiBytesOut; /**< pi_bytesout */
+ uint32_t uPiBytesIn; /**< pi_bytesin */
+} REQ_CMD_PI_T;
+AssertCompileSize(REQ_CMD_PI_T, 8);
+
+typedef struct REQ_RESP_HDR_T
+{
+ uint32_t cbSenseLen; /**< sense_len */
+ uint32_t uResidual; /**< residual */
+ uint16_t uStatusQualifier; /**< status_qualifier */
+ uint8_t uStatus; /**< status SCSI status code */
+ uint8_t uResponse; /**< response */
+} REQ_RESP_HDR_T;
+AssertCompileSize(REQ_RESP_HDR_T, 12);
+
+#pragma pack(1)
+typedef struct VIRTIOSCSI_REQ_CMD_T
+{
+ /** Device-readable section
+ * @{ */
+ REQ_CMD_HDR_T ReqHdr;
+ uint8_t uCdb[1]; /**< cdb */
+
+ REQ_CMD_PI_T piHdr; /**< T10 Pi block integrity (optional feature) */
+ uint8_t uPiOut[1]; /**< pi_out[] T10 pi block integrity */
+ uint8_t uDataOut[1]; /**< dataout */
+ /** @} */
+
+ /** @name Device writable section
+ * @{ */
+ REQ_RESP_HDR_T respHdr;
+ uint8_t uSense[1]; /**< sense */
+ uint8_t uPiIn[1]; /**< pi_in[] T10 Pi block integrity */
+ uint8_t uDataIn[1]; /**< detain; */
+ /** @} */
+} VIRTIOSCSI_REQ_CMD_T, *PVIRTIOSCSI_REQ_CMD_T;
+#pragma pack()
+AssertCompileSize(VIRTIOSCSI_REQ_CMD_T, 19+8+12+6);
+
+/** @name VirtIO 1.0 SCSI Host Device Req command-specific response values
+ * @{ */
+#define VIRTIOSCSI_S_OK 0 /**< control, command */
+#define VIRTIOSCSI_S_OVERRUN 1 /**< control */
+#define VIRTIOSCSI_S_ABORTED 2 /**< control */
+#define VIRTIOSCSI_S_BAD_TARGET 3 /**< control, command */
+#define VIRTIOSCSI_S_RESET 4 /**< control */
+#define VIRTIOSCSI_S_BUSY 5 /**< control, command */
+#define VIRTIOSCSI_S_TRANSPORT_FAILURE 6 /**< control, command */
+#define VIRTIOSCSI_S_TARGET_FAILURE 7 /**< control, command */
+#define VIRTIOSCSI_S_NEXUS_FAILURE 8 /**< control, command */
+#define VIRTIOSCSI_S_FAILURE 9 /**< control, command */
+#define VIRTIOSCSI_S_INCORRECT_LUN 12 /**< command */
+/** @} */
+
+/** @name VirtIO 1.0 SCSI Host Device command-specific task_attr values
+ * @{ */
+#define VIRTIOSCSI_S_SIMPLE 0 /**< */
+#define VIRTIOSCSI_S_ORDERED 1 /**< */
+#define VIRTIOSCSI_S_HEAD 2 /**< */
+#define VIRTIOSCSI_S_ACA 3 /**< */
+/** @} */
+
+/**
+ * VirtIO 1.0 SCSI Host Device Control command before we know type (5.6.6.2)
+ */
+typedef struct VIRTIOSCSI_CTRL_T
+{
+ uint32_t uType;
+} VIRTIOSCSI_CTRL_T, *PVIRTIOSCSI_CTRL_T;
+
+/** @name VirtIO 1.0 SCSI Host Device command-specific TMF values
+ * @{ */
+#define VIRTIOSCSI_T_TMF 0 /**< */
+#define VIRTIOSCSI_T_TMF_ABORT_TASK 0 /**< */
+#define VIRTIOSCSI_T_TMF_ABORT_TASK_SET 1 /**< */
+#define VIRTIOSCSI_T_TMF_CLEAR_ACA 2 /**< */
+#define VIRTIOSCSI_T_TMF_CLEAR_TASK_SET 3 /**< */
+#define VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET 4 /**< */
+#define VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET 5 /**< */
+#define VIRTIOSCSI_T_TMF_QUERY_TASK 6 /**< */
+#define VIRTIOSCSI_T_TMF_QUERY_TASK_SET 7 /**< */
+/** @} */
+
+#pragma pack(1)
+typedef struct VIRTIOSCSI_CTRL_TMF_T
+{
+ uint32_t uType; /**< type */
+ uint32_t uSubtype; /**< subtype */
+ uint8_t abScsiLun[8]; /**< lun */
+ uint64_t uId; /**< id */
+} VIRTIOSCSI_CTRL_TMF_T, *PVIRTIOSCSI_CTRL_TMF_T;
+#pragma pack()
+AssertCompileSize(VIRTIOSCSI_CTRL_TMF_T, 24);
+
+/** VirtIO 1.0 section 5.6.6.2, CTRL TMF response is an 8-bit status */
+
+/** @name VirtIO 1.0 SCSI Host Device device specific tmf control response values
+ * @{ */
+#define VIRTIOSCSI_S_FUNCTION_COMPLETE 0 /**< */
+#define VIRTIOSCSI_S_FUNCTION_SUCCEEDED 10 /**< */
+#define VIRTIOSCSI_S_FUNCTION_REJECTED 11 /**< */
+/** @} */
+
+#define VIRTIOSCSI_T_AN_QUERY 1 /**< Asynchronous notification query */
+#define VIRTIOSCSI_T_AN_SUBSCRIBE 2 /**< Asynchronous notification subscription */
+
+#pragma pack(1)
+typedef struct VIRTIOSCSI_CTRL_AN_T
+{
+ uint32_t uType; /**< type */
+ uint8_t abScsiLun[8]; /**< lun */
+ uint32_t fEventsRequested; /**< event_requested */
+} VIRTIOSCSI_CTRL_AN_T, *PVIRTIOSCSI_CTRL_AN_T;
+#pragma pack()
+AssertCompileSize(VIRTIOSCSI_CTRL_AN_T, 16);
+
+/** VirtIO 1.0, Section 5.6.6.2, CTRL AN response is 4-byte evt mask + 8-bit status */
+
+typedef union VIRTIO_SCSI_CTRL_UNION_T
+{
+ VIRTIOSCSI_CTRL_T Type;
+ VIRTIOSCSI_CTRL_TMF_T Tmf;
+ VIRTIOSCSI_CTRL_AN_T AsyncNotify;
+ uint8_t ab[24];
+} VIRTIO_SCSI_CTRL_UNION_T, *PVIRTIO_SCSI_CTRL_UNION_T;
+AssertCompile(sizeof(VIRTIO_SCSI_CTRL_UNION_T) == 24); /* VIRTIOSCSI_CTRL_T forces 4 byte alignment, the other two are byte packed. */
+
+/** @name VirtIO 1.0 SCSI Host Device device specific tmf control response values
+ * @{ */
+#define VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2 /**< */
+#define VIRTIOSCSI_EVT_ASYNC_POWER_MGMT 4 /**< */
+#define VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST 8 /**< */
+#define VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE 16 /**< */
+#define VIRTIOSCSI_EVT_ASYNC_MULTI_HOST 32 /**< */
+#define VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY 64 /**< */
+/** @} */
+
+#define SUBSCRIBABLE_EVENTS \
+ ( VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE \
+ | VIRTIOSCSI_EVT_ASYNC_POWER_MGMT \
+ | VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST \
+ | VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE \
+ | VIRTIOSCSI_EVT_ASYNC_MULTI_HOST \
+ | VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY )
+
+#define SUPPORTED_EVENTS 0 /* TBD */
+
+/**
+ * Worker thread context, shared state.
+ */
+typedef struct VIRTIOSCSIWORKER
+{
+ SUPSEMEVENT hEvtProcess; /**< handle of associated sleep/wake-up semaphore */
+ bool volatile fSleeping; /**< Flags whether worker thread is sleeping or not */
+ bool volatile fNotified; /**< Flags whether worker thread notified */
+} VIRTIOSCSIWORKER;
+/** Pointer to a VirtIO SCSI worker. */
+typedef VIRTIOSCSIWORKER *PVIRTIOSCSIWORKER;
+
+/**
+ * Worker thread context, ring-3 state.
+ */
+typedef struct VIRTIOSCSIWORKERR3
+{
+ R3PTRTYPE(PPDMTHREAD) pThread; /**< pointer to worker thread's handle */
+ uint16_t auRedoDescs[VIRTQ_SIZE];/**< List of previously suspended reqs to re-submit */
+ uint16_t cRedoDescs; /**< Number of redo desc chain head desc idxes in list */
+} VIRTIOSCSIWORKERR3;
+/** Pointer to a VirtIO SCSI worker. */
+typedef VIRTIOSCSIWORKERR3 *PVIRTIOSCSIWORKERR3;
+
+/**
+ * State of a target attached to the VirtIO SCSI Host
+ */
+typedef struct VIRTIOSCSITARGET
+{
+ /** The ring-3 device instance so we can easily get our bearings. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** Pointer to attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+
+ /** Target number (PDM LUN) */
+ uint32_t uTarget;
+
+ /** Target Description */
+ R3PTRTYPE(char *) pszTargetName;
+
+ /** Target base interface. */
+ PDMIBASE IBase;
+
+ /** Flag whether device is present. */
+ bool fPresent;
+
+ /** Media port interface. */
+ PDMIMEDIAPORT IMediaPort;
+
+ /** Pointer to the attached driver's media interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+
+ /** Extended media port interface. */
+ PDMIMEDIAEXPORT IMediaExPort;
+
+ /** Pointer to the attached driver's extended media interface. */
+ R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx;
+
+ /** Status LED interface */
+ PDMILEDPORTS ILed;
+
+ /** The status LED state for this device. */
+ PDMLED led;
+
+} VIRTIOSCSITARGET, *PVIRTIOSCSITARGET;
+
+/**
+ * VirtIO Host SCSI device state, shared edition.
+ *
+ * @extends VIRTIOCORE
+ */
+typedef struct VIRTIOSCSI
+{
+ /** The core virtio state. */
+ VIRTIOCORE Virtio;
+
+ /** VirtIO Host SCSI device runtime configuration parameters */
+ VIRTIOSCSI_CONFIG_T virtioScsiConfig;
+
+ bool fBootable;
+ bool afPadding0[3];
+
+ /** Number of targets in paTargetInstances. */
+ uint32_t cTargets;
+
+ /** Per device-bound virtq worker-thread contexts (eventq slot unused) */
+ VIRTIOSCSIWORKER aWorkers[VIRTIOSCSI_VIRTQ_CNT];
+
+ /** Instance name */
+ char szInstance[16];
+
+ /** Device-specific spec-based VirtIO VIRTQNAMEs */
+ char aszVirtqNames[VIRTIOSCSI_VIRTQ_CNT][VIRTIO_MAX_VIRTQ_NAME_SIZE];
+
+ /** Track which VirtIO queues we've attached to */
+ bool afVirtqAttached[VIRTIOSCSI_VIRTQ_CNT];
+
+ /** Set if events missed due to lack of bufs avail on eventq */
+ bool fEventsMissed;
+
+ /** Explicit alignment padding. */
+ bool afPadding1[2];
+
+ /** Mask of VirtIO Async Event types this device will deliver */
+ uint32_t fAsyncEvtsEnabled;
+
+ /** Total number of requests active across all targets */
+ volatile uint32_t cActiveReqs;
+
+
+ /** True if the guest/driver and VirtIO framework are in the ready state */
+ uint32_t fVirtioReady;
+
+ /** True if VIRTIO_SCSI_F_T10_PI was negotiated */
+ uint32_t fHasT10pi;
+
+ /** True if VIRTIO_SCSI_F_HOTPLUG was negotiated */
+ uint32_t fHasHotplug;
+
+ /** True if VIRTIO_SCSI_F_INOUT was negotiated */
+ uint32_t fHasInOutBufs;
+
+ /** True if VIRTIO_SCSI_F_CHANGE was negotiated */
+ uint32_t fHasLunChange;
+
+ /** True if in the process of resetting */
+ uint32_t fResetting;
+
+} VIRTIOSCSI;
+/** Pointer to the shared state of the VirtIO Host SCSI device. */
+typedef VIRTIOSCSI *PVIRTIOSCSI;
+
+
+/**
+ * VirtIO Host SCSI device state, ring-3 edition.
+ *
+ * @extends VIRTIOCORER3
+ */
+typedef struct VIRTIOSCSIR3
+{
+ /** The core virtio ring-3 state. */
+ VIRTIOCORER3 Virtio;
+
+ /** Array of per-target data. */
+ R3PTRTYPE(PVIRTIOSCSITARGET) paTargetInstances;
+
+ /** Per device-bound virtq worker-thread contexts (eventq slot unused) */
+ VIRTIOSCSIWORKERR3 aWorkers[VIRTIOSCSI_VIRTQ_CNT];
+
+ /** Device base interface. */
+ PDMIBASE IBase;
+
+ /** Pointer to the device instance.
+ * @note Only used in interface callbacks. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** Status Target: LEDs port interface. */
+ PDMILEDPORTS ILeds;
+
+ /** IMediaExPort: Media ejection notification */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+
+ /** Virtq to send tasks to R3. - HC ptr */
+ R3PTRTYPE(PPDMQUEUE) pNotifierVirtqR3;
+
+ /** True if in the process of quiescing I/O */
+ uint32_t fQuiescing;
+
+ /** For which purpose we're quiescing. */
+ VIRTIOVMSTATECHANGED enmQuiescingFor;
+
+} VIRTIOSCSIR3;
+/** Pointer to the ring-3 state of the VirtIO Host SCSI device. */
+typedef VIRTIOSCSIR3 *PVIRTIOSCSIR3;
+
+
+/**
+ * VirtIO Host SCSI device state, ring-0 edition.
+ */
+typedef struct VIRTIOSCSIR0
+{
+ /** The core virtio ring-0 state. */
+ VIRTIOCORER0 Virtio;
+} VIRTIOSCSIR0;
+/** Pointer to the ring-0 state of the VirtIO Host SCSI device. */
+typedef VIRTIOSCSIR0 *PVIRTIOSCSIR0;
+
+
+/**
+ * VirtIO Host SCSI device state, raw-mode edition.
+ */
+typedef struct VIRTIOSCSIRC
+{
+ /** The core virtio raw-mode state. */
+ VIRTIOCORERC Virtio;
+} VIRTIOSCSIRC;
+/** Pointer to the ring-0 state of the VirtIO Host SCSI device. */
+typedef VIRTIOSCSIRC *PVIRTIOSCSIRC;
+
+
+/** @typedef VIRTIOSCSICC
+ * The instance data for the current context. */
+typedef CTX_SUFF(VIRTIOSCSI) VIRTIOSCSICC;
+/** @typedef PVIRTIOSCSICC
+ * Pointer to the instance data for the current context. */
+typedef CTX_SUFF(PVIRTIOSCSI) PVIRTIOSCSICC;
+
+
+/**
+ * Request structure for IMediaEx (Associated Interfaces implemented by DrvSCSI)
+ * @note cbIn, cbOUt, cbDataOut mostly for debugging
+ */
+typedef struct VIRTIOSCSIREQ
+{
+ PDMMEDIAEXIOREQ hIoReq; /**< Handle of I/O request */
+ PVIRTIOSCSITARGET pTarget; /**< Target */
+ uint16_t uVirtqNbr; /**< Index of queue this request arrived on */
+ PVIRTQBUF pVirtqBuf; /**< Prepared desc chain pulled from virtq avail ring */
+ size_t cbDataIn; /**< size of datain buffer */
+ size_t cbDataOut; /**< size of dataout buffer */
+ uint16_t uDataInOff; /**< Fixed size of respHdr + sense (precede datain) */
+ uint16_t uDataOutOff; /**< Fixed size of reqhdr + cdb (precede dataout) */
+ uint32_t cbSenseAlloc; /**< Size of sense buffer */
+ size_t cbSenseLen; /**< Receives \# bytes written into sense buffer */
+ uint8_t *pbSense; /**< Pointer to R3 sense buffer */
+ PDMMEDIAEXIOREQSCSITXDIR enmTxDir; /**< Receives transfer direction of I/O req */
+ uint8_t uStatus; /**< SCSI status code */
+} VIRTIOSCSIREQ;
+typedef VIRTIOSCSIREQ *PVIRTIOSCSIREQ;
+
+
+/**
+ * callback_method_impl{VIRTIOCORER0,pfnVirtqNotified}
+ * @todo this causes burn if I prefix with at-sign. This callback is in VIRTIOCORER0 and VIRTIOCORER3
+ */
+static DECLCALLBACK(void) virtioScsiNotified(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr)
+{
+
+ RT_NOREF(pVirtio);
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+
+ AssertReturnVoid(uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT);
+ PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr];
+
+#if defined (IN_RING3) && defined (LOG_ENABLED)
+ RTLogFlush(NULL);
+#endif
+
+ if (uVirtqNbr == CONTROLQ_IDX || IS_REQ_VIRTQ(uVirtqNbr))
+ {
+ Log6Func(("%s has available data\n", VIRTQNAME(uVirtqNbr)));
+ /* Wake queue's worker thread up if sleeping */
+ if (!ASMAtomicXchgBool(&pWorker->fNotified, true))
+ {
+ if (ASMAtomicReadBool(&pWorker->fSleeping))
+ {
+ Log6Func(("waking %s worker.\n", VIRTQNAME(uVirtqNbr)));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pWorker->hEvtProcess);
+ AssertRC(rc);
+ }
+ }
+ }
+ else if (uVirtqNbr == EVENTQ_IDX)
+ {
+ Log3Func(("Driver queued buffer(s) to %s\n", VIRTQNAME(uVirtqNbr)));
+// if (ASMAtomicXchgBool(&pThis->fEventsMissed, false))
+// virtioScsiR3ReportEventsMissed(pDevIns, pThis, 0);
+ }
+ else
+ LogFunc(("Unexpected queue idx (ignoring): %d\n", uVirtqNbr));
+}
+
+
+#ifdef IN_RING3 /* spans most of the file, at the moment. */
+
+
+DECLINLINE(void) virtioScsiSetVirtqNames(PVIRTIOSCSI pThis)
+{
+ RTStrCopy(pThis->aszVirtqNames[CONTROLQ_IDX], VIRTIO_MAX_VIRTQ_NAME_SIZE, "controlq");
+ RTStrCopy(pThis->aszVirtqNames[EVENTQ_IDX], VIRTIO_MAX_VIRTQ_NAME_SIZE, "eventq");
+ for (uint16_t uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTQ_REQ_BASE + VIRTIOSCSI_REQ_VIRTQ_CNT; uVirtqNbr++)
+ RTStrPrintf(pThis->aszVirtqNames[uVirtqNbr], VIRTIO_MAX_VIRTQ_NAME_SIZE,
+ "requestq<%d>", uVirtqNbr - VIRTQ_REQ_BASE);
+}
+
+#ifdef LOG_ENABLED
+
+
+DECLINLINE(const char *) virtioGetTxDirText(uint32_t enmTxDir)
+{
+ switch (enmTxDir)
+ {
+ case PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN: return "<UNKNOWN>";
+ case PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE: return "<DEV-TO-GUEST>";
+ case PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE: return "<GUEST-TO-DEV>";
+ case PDMMEDIAEXIOREQSCSITXDIR_NONE: return "<NONE>";
+ default: return "<BAD ENUM>";
+ }
+}
+
+DECLINLINE(const char *) virtioGetTMFTypeText(uint32_t uSubType)
+{
+ switch (uSubType)
+ {
+ case VIRTIOSCSI_T_TMF_ABORT_TASK: return "ABORT TASK";
+ case VIRTIOSCSI_T_TMF_ABORT_TASK_SET: return "ABORT TASK SET";
+ case VIRTIOSCSI_T_TMF_CLEAR_ACA: return "CLEAR ACA";
+ case VIRTIOSCSI_T_TMF_CLEAR_TASK_SET: return "CLEAR TASK SET";
+ case VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET: return "I T NEXUS RESET";
+ case VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET: return "LOGICAL UNIT RESET";
+ case VIRTIOSCSI_T_TMF_QUERY_TASK: return "QUERY TASK";
+ case VIRTIOSCSI_T_TMF_QUERY_TASK_SET: return "QUERY TASK SET";
+ default: return "<unknown>";
+ }
+}
+
+DECLINLINE(const char *) virtioGetReqRespText(uint32_t vboxRc)
+{
+ switch (vboxRc)
+ {
+ case VIRTIOSCSI_S_OK: return "OK/COMPLETE";
+ case VIRTIOSCSI_S_OVERRUN: return "OVERRRUN";
+ case VIRTIOSCSI_S_ABORTED: return "ABORTED";
+ case VIRTIOSCSI_S_BAD_TARGET: return "BAD TARGET";
+ case VIRTIOSCSI_S_RESET: return "RESET";
+ case VIRTIOSCSI_S_TRANSPORT_FAILURE: return "TRANSPORT FAILURE";
+ case VIRTIOSCSI_S_TARGET_FAILURE: return "TARGET FAILURE";
+ case VIRTIOSCSI_S_NEXUS_FAILURE: return "NEXUS FAILURE";
+ case VIRTIOSCSI_S_BUSY: return "BUSY";
+ case VIRTIOSCSI_S_FAILURE: return "FAILURE";
+ case VIRTIOSCSI_S_INCORRECT_LUN: return "INCORRECT LUN";
+ case VIRTIOSCSI_S_FUNCTION_SUCCEEDED: return "FUNCTION SUCCEEDED";
+ case VIRTIOSCSI_S_FUNCTION_REJECTED: return "FUNCTION REJECTED";
+ default: return "<unknown>";
+ }
+}
+
+DECLINLINE(void) virtioGetControlAsyncMaskText(char *pszOutput, uint32_t cbOutput, uint32_t fAsyncTypesMask)
+{
+ RTStrPrintf(pszOutput, cbOutput, "%s%s%s%s%s%s",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE ? "CHANGE_OPERATION " : "",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_POWER_MGMT ? "POWER_MGMT " : "",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST ? "EXTERNAL_REQ " : "",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE ? "MEDIA_CHANGE " : "",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_MULTI_HOST ? "MULTI_HOST " : "",
+ fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY ? "DEVICE_BUSY " : "");
+}
+
+static uint8_t virtioScsiEstimateCdbLen(uint8_t uCmd, uint8_t cbMax)
+{
+ if (uCmd < 0x1f)
+ return RT_MIN(6, cbMax);
+ if (uCmd >= 0x20 && uCmd < 0x60)
+ return RT_MIN(10, cbMax);
+ if (uCmd >= 0x60 && uCmd < 0x80)
+ return cbMax;
+ if (uCmd >= 0x80 && uCmd < 0xa0)
+ return RT_MIN(16, cbMax);
+ if (uCmd >= 0xa0 && uCmd < 0xc0)
+ return RT_MIN(12, cbMax);
+ return cbMax;
+}
+
+#endif /* LOG_ENABLED */
+
+
+/*
+ * @todo Figure out how to implement this with R0 changes. Not used by current linux driver
+ */
+
+#if 0
+static int virtioScsiR3SendEvent(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget, uint32_t uEventType, uint32_t uReason)
+{
+ switch (uEventType)
+ {
+ case VIRTIOSCSI_T_NO_EVENT:
+ Log6Func(("(target=%d, LUN=%d): Warning event info guest queued is shorter than configured\n", uTarget, LUN0));
+ break;
+ case VIRTIOSCSI_T_NO_EVENT | VIRTIOSCSI_T_EVENTS_MISSED:
+ Log6Func(("(target=%d, LUN=%d): Warning driver that events were missed\n", uTarget, LUN0));
+ break;
+ case VIRTIOSCSI_T_TRANSPORT_RESET:
+ switch (uReason)
+ {
+ case VIRTIOSCSI_EVT_RESET_REMOVED:
+ Log6Func(("(target=%d, LUN=%d): Target or LUN removed\n", uTarget, LUN0));
+ break;
+ case VIRTIOSCSI_EVT_RESET_RESCAN:
+ Log6Func(("(target=%d, LUN=%d): Target or LUN added\n", uTarget, LUN0));
+ break;
+ case VIRTIOSCSI_EVT_RESET_HARD:
+ Log6Func(("(target=%d, LUN=%d): Target was reset\n", uTarget, LUN0));
+ break;
+ }
+ break;
+ case VIRTIOSCSI_T_ASYNC_NOTIFY:
+ {
+#ifdef LOG_ENABLED
+ char szTypeText[128];
+ virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), uReason);
+ Log6Func(("(target=%d, LUN=%d): Delivering subscribed async notification %s\n", uTarget, LUN0, szTypeText));
+#endif
+ break;
+ }
+ case VIRTIOSCSI_T_PARAM_CHANGE:
+ LogFunc(("(target=%d, LUN=%d): PARAM_CHANGE sense code: 0x%x sense qualifier: 0x%x\n",
+ uTarget, LUN0, uReason & 0xff, (uReason >> 8) & 0xff));
+ break;
+ default:
+ Log6Func(("(target=%d, LUN=%d): Unknown event type: %d, ignoring\n", uTarget, LUN0, uEventType));
+ return VINF_SUCCESS;
+ }
+
+ PVIRTQBUF pVirtqBuf = NULL;
+ int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, EVENTQ_IDX, &pVirtqBuf, true);
+ if (rc == VERR_NOT_AVAILABLE)
+ {
+ LogFunc(("eventq is empty, events missed (driver didn't preload queue)!\n"));
+ ASMAtomicWriteBool(&pThis->fEventsMissed, true);
+ return VINF_SUCCESS;
+ }
+ AssertRCReturn(rc, rc);
+
+ VIRTIOSCSI_EVENT_T Event;
+ Event.uEvent = uEventType;
+ Event.uReason = uReason;
+ Event.abVirtioLun[0] = 1;
+ Event.abVirtioLun[1] = uTarget;
+ Event.abVirtioLun[2] = (LUN0 >> 8) & 0x40;
+ Event.abVirtioLun[3] = LUN0 & 0xff;
+ Event.abVirtioLun[4] = 0;
+ Event.abVirtioLun[5] = 0;
+ Event.abVirtioLun[6] = 0;
+ Event.abVirtioLun[7] = 0;
+
+ RTSGSEG aReqSegs[1];
+ aReqSegs[0].pvSeg = &Event;
+ aReqSegs[0].cbSeg = sizeof(Event);
+
+ RTSGBUF ReqSgBuf;
+ RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs));
+
+ rc = virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, EVENTQ_IDX, &ReqSgBuf, pVirtqBuf, true /*fFence*/);
+ if (rc == VINF_SUCCESS)
+ virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, EVENTQ_IDX, false);
+ else
+ LogRel(("Error writing control message to guest\n"));
+ virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf);
+
+ return rc;
+}
+#endif
+
+/**
+ * Releases one reference from the given controller instances active request counter.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis VirtIO SCSI shared instance data.
+ * @param pThisCC VirtIO SCSI ring-3 instance data.
+ */
+DECLINLINE(void) virtioScsiR3Release(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC)
+{
+ Assert(pThis->cActiveReqs);
+
+ if (!ASMAtomicDecU32(&pThis->cActiveReqs) && pThisCC->fQuiescing)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+}
+
+/**
+ * Retains one reference for the given controller instances active request counter.
+ *
+ * @param pThis VirtIO SCSI shared instance data.
+ */
+DECLINLINE(void) virtioScsiR3Retain(PVIRTIOSCSI pThis)
+{
+ ASMAtomicIncU32(&pThis->cActiveReqs);
+}
+
+/** Internal worker. */
+static void virtioScsiR3FreeReq(PVIRTIOSCSITARGET pTarget, PVIRTIOSCSIREQ pReq)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pTarget->pDevIns, PVIRTIOSCSI);
+ RTMemFree(pReq->pbSense);
+ pReq->pbSense = NULL;
+ virtioCoreR3VirtqBufRelease(&pThis->Virtio, pReq->pVirtqBuf);
+ pReq->pVirtqBuf = NULL;
+ pTarget->pDrvMediaEx->pfnIoReqFree(pTarget->pDrvMediaEx, pReq->hIoReq);
+}
+
+/**
+ * This is called to complete a request immediately
+ *
+ * @param pDevIns The device instance.
+ * @param pThis VirtIO SCSI shared instance data.
+ * @param uVirtqNbr Virtq index
+ * @param pVirtqBuf Pointer to pre-processed descriptor chain pulled from virtq
+ * @param pRespHdr Response header
+ * @param pbSense Pointer to sense buffer or NULL if none.
+ * @param cbSenseCfg The configured sense buffer size.
+ *
+ * @returns VINF_SUCCESS
+ */
+static int virtioScsiR3ReqErr(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uVirtqNbr,
+ PVIRTQBUF pVirtqBuf, REQ_RESP_HDR_T *pRespHdr, uint8_t *pbSense,
+ size_t cbSenseCfg)
+{
+ Log2Func((" status: %s response: %s\n",
+ SCSIStatusText(pRespHdr->uStatus), virtioGetReqRespText(pRespHdr->uResponse)));
+
+ RTSGSEG aReqSegs[2];
+
+ /* Segment #1: Response header*/
+ aReqSegs[0].pvSeg = pRespHdr;
+ aReqSegs[0].cbSeg = sizeof(*pRespHdr);
+
+ /* Segment #2: Sense data. */
+ uint8_t abSenseBuf[VIRTIOSCSI_SENSE_SIZE_MAX];
+ AssertCompile(VIRTIOSCSI_SENSE_SIZE_MAX <= 4096);
+ Assert(cbSenseCfg <= sizeof(abSenseBuf));
+
+ RT_ZERO(abSenseBuf);
+ if (pbSense && pRespHdr->cbSenseLen)
+ memcpy(abSenseBuf, pbSense, RT_MIN(pRespHdr->cbSenseLen, sizeof(abSenseBuf)));
+ else
+ pRespHdr->cbSenseLen = 0;
+
+ aReqSegs[1].pvSeg = abSenseBuf;
+ aReqSegs[1].cbSeg = cbSenseCfg;
+
+ /* Init S/G buffer. */
+ RTSGBUF ReqSgBuf;
+ RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs));
+
+ if (pThis->fResetting)
+ pRespHdr->uResponse = VIRTIOSCSI_S_RESET;
+
+ virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, uVirtqNbr, &ReqSgBuf, pVirtqBuf, true /* fFence */);
+ virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, uVirtqNbr);
+
+ Log2(("---------------------------------------------------------------------------------\n"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Variant of virtioScsiR3ReqErr that takes four (4) REQ_RESP_HDR_T member
+ * fields rather than a pointer to an initialized structure.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis VirtIO SCSI shared instance data.
+ * @param uVirtqNbr Virtq index
+ * @param pVirtqBuf Pointer to pre-processed descriptor chain pulled from virtq
+ * @param cbResidual The number of residual bytes or something like that.
+ * @param bStatus The SCSI status code.
+ * @param bResponse The virtio SCSI response code.
+ * @param pbSense Pointer to sense buffer or NULL if none.
+ * @param cbSense The number of bytes of sense data. Zero if none.
+ * @param cbSenseCfg The configured sense buffer size.
+ *
+ * @returns VINF_SUCCESS
+ */
+static int virtioScsiR3ReqErr4(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uVirtqNbr,
+ PVIRTQBUF pVirtqBuf, size_t cbResidual, uint8_t bStatus, uint8_t bResponse,
+ uint8_t *pbSense, size_t cbSense, size_t cbSenseCfg)
+{
+ REQ_RESP_HDR_T RespHdr;
+ RespHdr.cbSenseLen = cbSense & UINT32_MAX;
+ RespHdr.uResidual = cbResidual & UINT32_MAX;
+ RespHdr.uStatusQualifier = 0;
+ RespHdr.uStatus = bStatus;
+ RespHdr.uResponse = bResponse;
+
+ return virtioScsiR3ReqErr(pDevIns, pThis, uVirtqNbr, pVirtqBuf, &RespHdr, pbSense, cbSenseCfg);
+}
+
+static void virtioScsiR3SenseKeyToVirtioResp(REQ_RESP_HDR_T *respHdr, uint8_t uSenseKey)
+{
+ switch (uSenseKey)
+ {
+ case SCSI_SENSE_ABORTED_COMMAND:
+ respHdr->uResponse = VIRTIOSCSI_S_ABORTED;
+ break;
+ case SCSI_SENSE_COPY_ABORTED:
+ respHdr->uResponse = VIRTIOSCSI_S_ABORTED;
+ break;
+ case SCSI_SENSE_UNIT_ATTENTION:
+ respHdr->uResponse = VIRTIOSCSI_S_TARGET_FAILURE;
+ break;
+ case SCSI_SENSE_HARDWARE_ERROR:
+ respHdr->uResponse = VIRTIOSCSI_S_TARGET_FAILURE;
+ break;
+ case SCSI_SENSE_NOT_READY:
+ /* Not sure what to return for this. See choices at VirtIO 1.0, 5.6.6.1.1 */
+ respHdr->uResponse = VIRTIOSCSI_S_FAILURE;
+ /* respHdr->uResponse = VIRTIOSCSI_S_BUSY; */ /* BUSY is VirtIO's 'retryable' response */
+ break;
+ default:
+ respHdr->uResponse = VIRTIOSCSI_S_FAILURE;
+ break;
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) virtioScsiR3IoReqFinish(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ PPDMIMEDIAEX pIMediaEx = pTarget->pDrvMediaEx;
+ PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc;
+
+ size_t cbResidual = 0;
+ int rc = pIMediaEx->pfnIoReqQueryResidual(pIMediaEx, hIoReq, &cbResidual);
+ AssertRC(rc);
+
+ size_t cbXfer = 0;
+ rc = pIMediaEx->pfnIoReqQueryXferSize(pIMediaEx, hIoReq, &cbXfer);
+ AssertRC(rc);
+
+ /* Masking deals with data type size discrepancies between
+ * The APIs (virtio and VBox). Windows C-compiler complains otherwise */
+ Assert(!(cbXfer & 0xffffffff00000000));
+ uint32_t cbXfer32 = cbXfer & 0xffffffff;
+ REQ_RESP_HDR_T respHdr = { 0 };
+ respHdr.cbSenseLen = pReq->pbSense[2] == SCSI_SENSE_NONE ? 0 : (uint32_t)pReq->cbSenseLen;
+ AssertMsg(!(cbResidual & 0xffffffff00000000),
+ ("WARNING: Residual size larger than sizeof(uint32_t), truncating"));
+ respHdr.uResidual = (uint32_t)(cbResidual & 0xffffffff);
+ respHdr.uStatus = pReq->uStatus;
+
+ /* VirtIO 1.0 spec 5.6.6.1.1 says device MUST return a VirtIO response byte value.
+ * Some are returned during the submit phase, and a few are not mapped at all,
+ * wherein anything that can't map specifically gets mapped to VIRTIOSCSI_S_FAILURE
+ */
+ if (pThis->fResetting)
+ respHdr.uResponse = VIRTIOSCSI_S_RESET;
+ else
+ {
+ switch (rcReq)
+ {
+ case SCSI_STATUS_OK:
+ {
+ if (pReq->uStatus != SCSI_STATUS_CHECK_CONDITION)
+ respHdr.uResponse = VIRTIOSCSI_S_OK;
+ else
+ virtioScsiR3SenseKeyToVirtioResp(&respHdr, pReq->pbSense[2]);
+ break;
+ }
+ case SCSI_STATUS_CHECK_CONDITION:
+ virtioScsiR3SenseKeyToVirtioResp(&respHdr, pReq->pbSense[2]);
+ break;
+
+ default:
+ respHdr.uResponse = VIRTIOSCSI_S_FAILURE;
+ break;
+ }
+ }
+
+ Log2Func(("status: (%d) %s, response: (%d) %s\n", pReq->uStatus, SCSIStatusText(pReq->uStatus),
+ respHdr.uResponse, virtioGetReqRespText(respHdr.uResponse)));
+
+ if (RT_FAILURE(rcReq))
+ Log2Func(("rcReq: %Rrc\n", rcReq));
+
+ if (LogIs3Enabled())
+ {
+ LogFunc(("cbDataIn = %u, cbDataOut = %u (cbIn = %u, cbOut = %u)\n",
+ pReq->cbDataIn, pReq->cbDataOut, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysSend));
+ LogFunc(("xfer = %lu, residual = %u\n", cbXfer, cbResidual));
+ LogFunc(("xfer direction: %s, sense written = %d, sense size = %d\n",
+ virtioGetTxDirText(pReq->enmTxDir), respHdr.cbSenseLen, pThis->virtioScsiConfig.uSenseSize));
+ }
+
+ if (respHdr.cbSenseLen && LogIs2Enabled())
+ {
+ LogFunc(("Sense: %s\n", SCSISenseText(pReq->pbSense[2])));
+ LogFunc(("Sense Ext3: %s\n", SCSISenseExtText(pReq->pbSense[12], pReq->pbSense[13])));
+ }
+
+ if ( (VIRTIO_IS_IN_DIRECTION(pReq->enmTxDir) && cbXfer32 > pReq->cbDataIn)
+ || (VIRTIO_IS_OUT_DIRECTION(pReq->enmTxDir) && cbXfer32 > pReq->cbDataOut))
+ {
+ Log2Func((" * * * * Data overrun, returning sense\n"));
+ uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED,
+ 0, SCSI_SENSE_ILLEGAL_REQUEST, 0, 0, 0, 0, 10, 0, 0, 0 };
+ respHdr.cbSenseLen = sizeof(abSense);
+ respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION;
+ respHdr.uResponse = VIRTIOSCSI_S_OVERRUN;
+ respHdr.uResidual = pReq->cbDataIn & UINT32_MAX;
+
+ virtioScsiR3ReqErr(pDevIns, pThis, pReq->uVirtqNbr, pReq->pVirtqBuf, &respHdr, abSense,
+ RT_MIN(pThis->virtioScsiConfig.uSenseSize, VIRTIOSCSI_SENSE_SIZE_MAX));
+ }
+ else
+ {
+ Assert(pReq->pbSense != NULL);
+
+ /* req datain bytes already in guest phys mem. via virtioScsiIoReqCopyFromBuf() */
+ RTSGSEG aReqSegs[2];
+
+ aReqSegs[0].pvSeg = &respHdr;
+ aReqSegs[0].cbSeg = sizeof(respHdr);
+
+ aReqSegs[1].pvSeg = pReq->pbSense;
+ aReqSegs[1].cbSeg = pReq->cbSenseAlloc; /* VirtIO 1.0 spec 5.6.4/5.6.6.1 */
+
+ RTSGBUF ReqSgBuf;
+ RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs));
+
+ size_t cbReqSgBuf = RTSgBufCalcTotalLength(&ReqSgBuf);
+ /** @todo r=bird: Returning here looks a little bogus... */
+ AssertMsgReturn(cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn,
+ ("Guest expected less req data (space needed: %zu, avail: %u)\n",
+ cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn),
+ VERR_BUFFER_OVERFLOW);
+
+ virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf, true /* fFence TBD */);
+ virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr);
+
+ Log2(("-----------------------------------------------------------------------------------------\n"));
+ }
+
+ virtioScsiR3FreeReq(pTarget, pReq);
+ virtioScsiR3Release(pDevIns, pThis, pThisCC);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+
+ * Copy virtual memory from VSCSI layer to guest physical memory
+ */
+static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf, size_t cbCopy)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+ PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq, cbCopy);
+
+ if (!pReq->cbDataIn)
+ return VINF_SUCCESS;
+
+ AssertReturn(pReq->pVirtqBuf, VERR_INVALID_PARAMETER);
+
+ PVIRTIOSGBUF pSgPhysReturn = pReq->pVirtqBuf->pSgPhysReturn;
+ virtioCoreGCPhysChainAdvance(pSgPhysReturn, offDst);
+
+ size_t cbCopied = 0;
+ size_t cbRemain = pReq->cbDataIn;
+
+ /* Skip past the REQ_RESP_HDR_T and sense code if we're at the start of the buffer. */
+ if (!pSgPhysReturn->idxSeg && pSgPhysReturn->cbSegLeft == pSgPhysReturn->paSegs[0].cbSeg)
+ virtioCoreGCPhysChainAdvance(pSgPhysReturn, pReq->uDataInOff);
+
+ while (cbRemain)
+ {
+ cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysReturn->cbSegLeft);
+ Assert(cbCopied > 0);
+ PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pSgBuf->pvSegCur, cbCopied);
+ RTSgBufAdvance(pSgBuf, cbCopied);
+ virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbCopied);
+ cbRemain -= cbCopied;
+ }
+ RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); /* needed? */
+
+ Log3Func((".... Copied %lu bytes from %lu byte guest buffer, residual=%lu\n",
+ cbCopy, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysReturn - cbCopy));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ *
+ * Copy guest physical memory to VSCSI layer virtual memory
+ */
+static DECLCALLBACK(int) virtioScsiR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf, size_t cbCopy)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+ PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc;
+ RT_NOREF(hIoReq, cbCopy);
+
+ if (!pReq->cbDataOut)
+ return VINF_SUCCESS;
+
+ PVIRTIOSGBUF pSgPhysSend = pReq->pVirtqBuf->pSgPhysSend;
+ virtioCoreGCPhysChainAdvance(pSgPhysSend, offSrc);
+
+ size_t cbCopied = 0;
+ size_t cbRemain = pReq->cbDataOut;
+ while (cbRemain)
+ {
+ cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysSend->cbSegLeft);
+ Assert(cbCopied > 0);
+ PDMDevHlpPCIPhysReadUser(pDevIns, pSgPhysSend->GCPhysCur, pSgBuf->pvSegCur, cbCopied);
+ RTSgBufAdvance(pSgBuf, cbCopied);
+ virtioCoreGCPhysChainAdvance(pSgPhysSend, cbCopied);
+ cbRemain -= cbCopied;
+ }
+
+ Log2Func((".... Copied %lu bytes to %lu byte guest buffer, residual=%lu\n",
+ cbCopy, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysReturn - cbCopy));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles request queues for/on a worker thread.
+ *
+ * @returns VBox status code (logged by caller).
+ */
+static int virtioScsiR3ReqSubmit(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC,
+ uint16_t uVirtqNbr, PVIRTQBUF pVirtqBuf)
+{
+ /*
+ * Validate configuration values we use here before we start.
+ */
+ uint32_t const cbCdb = pThis->virtioScsiConfig.uCdbSize;
+ uint32_t const cbSenseCfg = pThis->virtioScsiConfig.uSenseSize;
+ /** @todo Report these as errors to the guest or does the caller do that? */
+ ASSERT_GUEST_LOGREL_MSG_RETURN(cbCdb <= VIRTIOSCSI_CDB_SIZE_MAX, ("cbCdb=%#x\n", cbCdb), VERR_OUT_OF_RANGE);
+ ASSERT_GUEST_LOGREL_MSG_RETURN(cbSenseCfg <= VIRTIOSCSI_SENSE_SIZE_MAX, ("cbSenseCfg=%#x\n", cbSenseCfg), VERR_OUT_OF_RANGE);
+
+ /*
+ * Extract command header and CDB from guest physical memory
+ * The max size is rather small here (19 + 255 = 274), so put
+ * it on the stack.
+ */
+ size_t const cbReqHdr = sizeof(REQ_CMD_HDR_T) + cbCdb;
+ AssertReturn(pVirtqBuf && pVirtqBuf->cbPhysSend >= cbReqHdr, VERR_INVALID_PARAMETER);
+
+ AssertCompile(VIRTIOSCSI_CDB_SIZE_MAX < 4096);
+ union
+ {
+ RT_GCC_EXTENSION struct
+ {
+ REQ_CMD_HDR_T ReqHdr;
+ uint8_t abCdb[VIRTIOSCSI_CDB_SIZE_MAX];
+ } ;
+ uint8_t ab[sizeof(REQ_CMD_HDR_T) + VIRTIOSCSI_CDB_SIZE_MAX];
+ uint64_t au64Align[(sizeof(REQ_CMD_HDR_T) + VIRTIOSCSI_CDB_SIZE_MAX) / sizeof(uint64_t)];
+ } VirtqReq;
+ RT_ZERO(VirtqReq);
+
+ for (size_t offReq = 0; offReq < cbReqHdr; )
+ {
+ size_t cbSeg = cbReqHdr - offReq;
+ RTGCPHYS GCPhys = virtioCoreGCPhysChainGetNextSeg(pVirtqBuf->pSgPhysSend, &cbSeg);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhys, &VirtqReq.ab[offReq], cbSeg);
+ offReq += cbSeg;
+ }
+
+ uint8_t const uType = VirtqReq.ReqHdr.abVirtioLun[0];
+ uint8_t const uTarget = VirtqReq.ReqHdr.abVirtioLun[1];
+ uint32_t uScsiLun = RT_MAKE_U16(VirtqReq.ReqHdr.abVirtioLun[3], VirtqReq.ReqHdr.abVirtioLun[2]) & 0x3fff;
+
+ bool fBadLUNFormat = false;
+ if (uType == 0xc1 && uTarget == 0x01)
+ {
+ LogRel(("* * * WARNING: REPORT LUNS LU ACCESSED. FEATURE NOT IMPLEMENTED SEE DevVirtioScsi.cpp * * * "));
+ /* Force rejection. */ /** @todo figure out right way to handle. Note this is a very
+ * vague and confusing part of the VirtIO spec (which deviates from the SCSI standard).
+ * I have not been able to determine how to implement this properly. I've checked the
+ * source code of Guest drivers, and so far none seem to use it. If this message is logged,
+ * meaning a guest expects this feature, implementing it can be re-visited */
+ uScsiLun = 0xff;
+ }
+ else
+ if (uType != 1)
+ fBadLUNFormat = true;
+
+ LogFunc(("[%s] (Target: %d LUN: %d) CDB: %.*Rhxs\n",
+ SCSICmdText(VirtqReq.abCdb[0]), uTarget, uScsiLun,
+ virtioScsiEstimateCdbLen(VirtqReq.abCdb[0], cbCdb), &VirtqReq.abCdb[0]));
+
+ Log3Func(("cmd id: %RX64, attr: %x, prio: %d, crn: %x\n",
+ VirtqReq.ReqHdr.uId, VirtqReq.ReqHdr.uTaskAttr, VirtqReq.ReqHdr.uPrio, VirtqReq.ReqHdr.uCrn));
+
+ /*
+ * Calculate request offsets and data sizes.
+ */
+ uint32_t const offDataOut = sizeof(REQ_CMD_HDR_T) + cbCdb;
+ uint32_t const offDataIn = sizeof(REQ_RESP_HDR_T) + cbSenseCfg;
+ size_t const cbDataOut = pVirtqBuf->cbPhysSend - offDataOut;
+ /** @todo r=bird: Validate cbPhysReturn properly? I've just RT_MAX'ed it for now. */
+ size_t const cbDataIn = RT_MAX(pVirtqBuf->cbPhysReturn, offDataIn) - offDataIn;
+ Assert(offDataOut <= UINT16_MAX);
+ Assert(offDataIn <= UINT16_MAX);
+
+ /*
+ * Handle submission errors
+ */
+ if (RT_LIKELY(!fBadLUNFormat))
+ { /* likely */ }
+ else
+ {
+ Log2Func(("Error submitting request, bad LUN format\n"));
+ return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, 0 /*bStatus*/,
+ VIRTIOSCSI_S_FAILURE, NULL /*pbSense*/, 0 /*cbSense*/, cbSenseCfg);
+ }
+
+ PVIRTIOSCSITARGET const pTarget = &pThisCC->paTargetInstances[uTarget];
+ if (RT_LIKELY( uTarget < pThis->cTargets
+ && pTarget->fPresent
+ && pTarget->pDrvMediaEx))
+ { /* likely */ }
+ else
+ {
+ Log2Func(("Error submitting request to bad target (%d) or bad LUN (%d)\n", uTarget, uScsiLun));
+ uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED,
+ 0, SCSI_SENSE_ILLEGAL_REQUEST,
+ 0, 0, 0, 0, 10, SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED, 0, 0 };
+ return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION,
+ VIRTIOSCSI_S_BAD_TARGET, abSense, sizeof(abSense), cbSenseCfg);
+ }
+ if (RT_LIKELY(uScsiLun == 0))
+ { /* likely */ }
+ else
+ {
+ Log2Func(("Error submitting request to bad target (%d) or bad LUN (%d)\n", uTarget, uScsiLun));
+ uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED,
+ 0, SCSI_SENSE_ILLEGAL_REQUEST,
+ 0, 0, 0, 0, 10, SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED, 0, 0 };
+ return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION,
+ VIRTIOSCSI_S_OK, abSense, sizeof(abSense), cbSenseCfg);
+ }
+ if (RT_LIKELY(!pThis->fResetting))
+ { /* likely */ }
+ else
+ {
+ Log2Func(("Aborting req submission because reset is in progress\n"));
+ return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_OK,
+ VIRTIOSCSI_S_RESET, NULL /*pbSense*/, 0 /*cbSense*/, cbSenseCfg);
+ }
+
+#if 0
+ if (RT_LIKELY(!cbDataIn || !cbDataOut || pThis->fHasInOutBufs)) /* VirtIO 1.0, 5.6.6.1.1 */
+ { /* likely */ }
+ else
+ {
+ Log2Func(("Error submitting request, got datain & dataout bufs w/o INOUT feature negotated\n"));
+ uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED,
+ 0, SCSI_SENSE_ILLEGAL_REQUEST, 0, 0, 0, 0, 10, 0, 0, 0 };
+ return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION,
+ VIRTIOSCSI_S_FAILURE, abSense, sizeof(abSense), cbSenseCfg);
+ }
+#endif
+ /*
+ * Have underlying driver allocate a req of size set during initialization of this device.
+ */
+ virtioScsiR3Retain(pThis);
+
+ PDMMEDIAEXIOREQ hIoReq = NULL;
+ PVIRTIOSCSIREQ pReq = NULL;
+ PPDMIMEDIAEX pIMediaEx = pTarget->pDrvMediaEx;
+
+ int rc = pIMediaEx->pfnIoReqAlloc(pIMediaEx, &hIoReq, (void **)&pReq, 0 /* uIoReqId */,
+ PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR);
+
+ if (RT_FAILURE(rc))
+ {
+ virtioScsiR3Release(pDevIns, pThis, pThisCC);
+ return rc;
+ }
+
+ pReq->hIoReq = hIoReq;
+ pReq->pTarget = pTarget;
+ pReq->uVirtqNbr = uVirtqNbr;
+ pReq->cbDataIn = cbDataIn;
+ pReq->cbDataOut = cbDataOut;
+ pReq->pVirtqBuf = pVirtqBuf;
+ virtioCoreR3VirtqBufRetain(pVirtqBuf); /* (For pReq->pVirtqBuf. Released by virtioScsiR3FreeReq.) */
+ pReq->uDataInOff = offDataIn;
+ pReq->uDataOutOff = offDataOut;
+
+ pReq->cbSenseAlloc = cbSenseCfg;
+ pReq->pbSense = (uint8_t *)RTMemAllocZ(pReq->cbSenseAlloc);
+ AssertMsgReturnStmt(pReq->pbSense, ("Out of memory allocating sense buffer"),
+ virtioScsiR3FreeReq(pTarget, pReq);, VERR_NO_MEMORY);
+
+ /* Note: DrvSCSI allocates one virtual memory buffer for input and output phases of the request */
+ rc = pIMediaEx->pfnIoReqSendScsiCmd(pIMediaEx, pReq->hIoReq, uScsiLun,
+ &VirtqReq.abCdb[0], cbCdb,
+ PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN, &pReq->enmTxDir,
+ RT_MAX(cbDataIn, cbDataOut),
+ pReq->pbSense, pReq->cbSenseAlloc, &pReq->cbSenseLen,
+ &pReq->uStatus, RT_MS_30SEC);
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ {
+ /*
+ * Getting here means the request failed in early in the submission to the lower level driver,
+ * and there will be no callback to the finished/completion function for this request
+ */
+ Assert(RT_FAILURE_NP(rc));
+ Log2Func(("Request-submission error from lower-level driver\n"));
+ uint8_t uASC, uASCQ = 0;
+ switch (rc)
+ {
+ case VERR_NO_MEMORY:
+ uASC = SCSI_ASC_SYSTEM_RESOURCE_FAILURE;
+ break;
+ default:
+ uASC = SCSI_ASC_INTERNAL_TARGET_FAILURE;
+ break;
+ }
+ uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED,
+ 0, SCSI_SENSE_VENDOR_SPECIFIC,
+ 0, 0, 0, 0, 10, uASC, uASCQ, 0 };
+ REQ_RESP_HDR_T respHdr = { 0 };
+ respHdr.cbSenseLen = sizeof(abSense);
+ respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION;
+ respHdr.uResponse = VIRTIOSCSI_S_FAILURE;
+ respHdr.uResidual = (cbDataIn + cbDataOut) & UINT32_MAX;
+ virtioScsiR3ReqErr(pDevIns, pThis, uVirtqNbr, pVirtqBuf, &respHdr, abSense, cbSenseCfg);
+ virtioScsiR3FreeReq(pTarget, pReq);
+ virtioScsiR3Release(pDevIns, pThis, pThisCC);
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles control transfers for/on a worker thread.
+ *
+ * @returns VBox status code (ignored by the caller).
+ * @param pDevIns The device instance.
+ * @param pThis VirtIO SCSI shared instance data.
+ * @param pThisCC VirtIO SCSI ring-3 instance data.
+ * @param uVirtqNbr CONTROLQ_IDX
+ * @param pVirtqBuf Descriptor chain to process.
+ */
+static int virtioScsiR3Ctrl(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC,
+ uint16_t uVirtqNbr, PVIRTQBUF pVirtqBuf)
+{
+ AssertReturn(pVirtqBuf->cbPhysSend >= RT_MIN(sizeof(VIRTIOSCSI_CTRL_AN_T),
+ sizeof(VIRTIOSCSI_CTRL_TMF_T)), 0);
+
+ /*
+ * Allocate buffer and read in the control command
+ */
+ VIRTIO_SCSI_CTRL_UNION_T ScsiCtrlUnion;
+ RT_ZERO(ScsiCtrlUnion);
+
+ size_t const cb = RT_MIN(pVirtqBuf->cbPhysSend, sizeof(VIRTIO_SCSI_CTRL_UNION_T));
+ for (size_t uOffset = 0; uOffset < cb; )
+ {
+ size_t cbSeg = cb - uOffset;
+ RTGCPHYS GCPhys = virtioCoreGCPhysChainGetNextSeg(pVirtqBuf->pSgPhysSend, &cbSeg);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhys, &ScsiCtrlUnion.ab[uOffset], cbSeg);
+ uOffset += cbSeg;
+ }
+
+ AssertReturn( (ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_TMF
+ && pVirtqBuf->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_TMF_T))
+ || ( ( ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_AN_QUERY
+ || ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_AN_SUBSCRIBE)
+ && pVirtqBuf->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_AN_T)),
+ 0 /** @todo r=bird: what kind of status is '0' here? */);
+
+ union
+ {
+ uint32_t fSupportedEvents;
+ } uData;
+ uint8_t bResponse = VIRTIOSCSI_S_OK;
+ uint8_t cSegs;
+ RTSGSEG aReqSegs[2];
+ switch (ScsiCtrlUnion.Type.uType)
+ {
+ case VIRTIOSCSI_T_TMF: /* Task Management Functions */
+ {
+ uint8_t uTarget = ScsiCtrlUnion.Tmf.abScsiLun[1];
+ uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.Tmf.abScsiLun[3], ScsiCtrlUnion.Tmf.abScsiLun[2]) & 0x3fff;
+ Log2Func(("[%s] (Target: %d LUN: %d) Task Mgt Function: %s\n",
+ VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, virtioGetTMFTypeText(ScsiCtrlUnion.Tmf.uSubtype)));
+
+ if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent)
+ bResponse = VIRTIOSCSI_S_BAD_TARGET;
+ else
+ if (uScsiLun != 0)
+ bResponse = VIRTIOSCSI_S_INCORRECT_LUN;
+ else
+ switch (ScsiCtrlUnion.Tmf.uSubtype)
+ {
+ case VIRTIOSCSI_T_TMF_ABORT_TASK:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_ABORT_TASK_SET:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_CLEAR_ACA:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_CLEAR_TASK_SET:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET:
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ case VIRTIOSCSI_T_TMF_QUERY_TASK:
+ bResponse = VIRTIOSCSI_S_FUNCTION_REJECTED;
+ break;
+ case VIRTIOSCSI_T_TMF_QUERY_TASK_SET:
+ bResponse = VIRTIOSCSI_S_FUNCTION_REJECTED;
+ break;
+ default:
+ LogFunc(("Unknown TMF type\n"));
+ bResponse = VIRTIOSCSI_S_FAILURE;
+ }
+ cSegs = 0; /* only bResponse */
+ break;
+ }
+ case VIRTIOSCSI_T_AN_QUERY: /* Guest SCSI driver is querying supported async event notifications */
+ {
+ uint8_t uTarget = ScsiCtrlUnion.AsyncNotify.abScsiLun[1];
+ uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.AsyncNotify.abScsiLun[3],
+ ScsiCtrlUnion.AsyncNotify.abScsiLun[2]) & 0x3fff;
+
+ if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent)
+ bResponse = VIRTIOSCSI_S_BAD_TARGET;
+ else
+ if (uScsiLun != 0)
+ bResponse = VIRTIOSCSI_S_INCORRECT_LUN;
+ else
+ bResponse = VIRTIOSCSI_S_FUNCTION_COMPLETE;
+
+#ifdef LOG_ENABLED
+ if (LogIs2Enabled())
+ {
+ char szTypeText[128];
+ virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), ScsiCtrlUnion.AsyncNotify.fEventsRequested);
+ Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Query: %s\n",
+ VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, szTypeText));
+ }
+#endif
+ uData.fSupportedEvents = SUPPORTED_EVENTS;
+ aReqSegs[0].pvSeg = &uData.fSupportedEvents;
+ aReqSegs[0].cbSeg = sizeof(uData.fSupportedEvents);
+ cSegs = 1;
+ break;
+ }
+ case VIRTIOSCSI_T_AN_SUBSCRIBE: /* Guest SCSI driver is subscribing to async event notification(s) */
+ {
+ if (ScsiCtrlUnion.AsyncNotify.fEventsRequested & ~SUBSCRIBABLE_EVENTS)
+ LogFunc(("Unsupported bits in event subscription event mask: %#x\n",
+ ScsiCtrlUnion.AsyncNotify.fEventsRequested));
+
+ uint8_t uTarget = ScsiCtrlUnion.AsyncNotify.abScsiLun[1];
+ uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.AsyncNotify.abScsiLun[3],
+ ScsiCtrlUnion.AsyncNotify.abScsiLun[2]) & 0x3fff;
+
+#ifdef LOG_ENABLED
+ if (LogIs2Enabled())
+ {
+ char szTypeText[128];
+ virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), ScsiCtrlUnion.AsyncNotify.fEventsRequested);
+ Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Subscribe: %s\n",
+ VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, szTypeText));
+ }
+#endif
+ if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent)
+ bResponse = VIRTIOSCSI_S_BAD_TARGET;
+ else
+ if (uScsiLun != 0)
+ bResponse = VIRTIOSCSI_S_INCORRECT_LUN;
+ else
+ {
+ bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; /* or VIRTIOSCSI_S_FUNCTION_COMPLETE? */
+ pThis->fAsyncEvtsEnabled = SUPPORTED_EVENTS & ScsiCtrlUnion.AsyncNotify.fEventsRequested;
+ }
+
+ aReqSegs[0].pvSeg = &pThis->fAsyncEvtsEnabled;
+ aReqSegs[0].cbSeg = sizeof(pThis->fAsyncEvtsEnabled);
+ cSegs = 1;
+ break;
+ }
+ default:
+ {
+ LogFunc(("Unknown control type extracted from %s: %u\n", VIRTQNAME(uVirtqNbr), ScsiCtrlUnion.Type.uType));
+
+ bResponse = VIRTIOSCSI_S_FAILURE;
+ cSegs = 0; /* only bResponse */
+ break;
+ }
+ }
+
+ /* Add the response code: */
+ aReqSegs[cSegs].pvSeg = &bResponse;
+ aReqSegs[cSegs].cbSeg = sizeof(bResponse);
+ cSegs++;
+ Assert(cSegs <= RT_ELEMENTS(aReqSegs));
+
+ LogFunc(("Response code: %s\n", virtioGetReqRespText(bResponse)));
+
+ RTSGBUF ReqSgBuf;
+ RTSgBufInit(&ReqSgBuf, aReqSegs, cSegs);
+
+ virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, uVirtqNbr, &ReqSgBuf, pVirtqBuf, true /*fFence*/);
+ virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, uVirtqNbr);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNPDMTHREADWAKEUPDEV}
+ */
+static DECLCALLBACK(int) virtioScsiR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[(uintptr_t)pThread->pvUser].hEvtProcess);
+}
+
+/**
+ * @callback_method_impl{FNPDMTHREADDEV}
+ */
+static DECLCALLBACK(int) virtioScsiR3WorkerThread(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ uint16_t const uVirtqNbr = (uint16_t)(uintptr_t)pThread->pvUser;
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr];
+ PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr];
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ Log6Func(("[Re]starting %s worker\n", VIRTQNAME(uVirtqNbr)));
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ if ( !pWorkerR3->cRedoDescs
+ && IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, uVirtqNbr))
+ {
+ /* Atomic interlocks avoid missing alarm while going to sleep & notifier waking the awoken */
+ ASMAtomicWriteBool(&pWorker->fSleeping, true);
+ bool fNotificationSent = ASMAtomicXchgBool(&pWorker->fNotified, false);
+ if (!fNotificationSent)
+ {
+ Log6Func(("%s worker sleeping...\n", VIRTQNAME(uVirtqNbr)));
+ Assert(ASMAtomicReadBool(&pWorker->fSleeping));
+ int rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pWorker->hEvtProcess, RT_INDEFINITE_WAIT);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ {
+ Log6Func(("%s worker thread not running, exiting\n", VIRTQNAME(uVirtqNbr)));
+ return VINF_SUCCESS;
+ }
+ if (rc == VERR_INTERRUPTED)
+ {
+ Log6Func(("%s worker interrupted ... continuing\n", VIRTQNAME(uVirtqNbr)));
+ continue;
+ }
+ Log6Func(("%s worker woken\n", VIRTQNAME(uVirtqNbr)));
+ ASMAtomicWriteBool(&pWorker->fNotified, false);
+ }
+ ASMAtomicWriteBool(&pWorker->fSleeping, false);
+ }
+ if (!virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr))
+ {
+ LogFunc(("%s queue not enabled, worker aborting...\n", VIRTQNAME(uVirtqNbr)));
+ break;
+ }
+
+ if (!pThis->afVirtqAttached[uVirtqNbr])
+ {
+ LogFunc(("%s queue not attached, worker aborting...\n", VIRTQNAME(uVirtqNbr)));
+ break;
+ }
+ if (!pThisCC->fQuiescing)
+ {
+ /* Process any reqs that were suspended saved to the redo queue in save exec. */
+ for (int i = 0; i < pWorkerR3->cRedoDescs; i++)
+ {
+#ifdef VIRTIO_VBUF_ON_STACK
+ PVIRTQBUF pVirtqBuf = virtioCoreR3VirtqBufAlloc();
+ if (!pVirtqBuf)
+ {
+ LogRel(("Failed to allocate memory for VIRTQBUF\n"));
+ break; /* No point in trying to allocate memory for other descriptor chains */
+ }
+ int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr,
+ pWorkerR3->auRedoDescs[i], pVirtqBuf);
+#else /* !VIRTIO_VBUF_ON_STACK */
+ PVIRTQBUF pVirtqBuf;
+ int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr,
+ pWorkerR3->auRedoDescs[i], &pVirtqBuf);
+#endif /* !VIRTIO_VBUF_ON_STACK */
+ if (RT_FAILURE(rc))
+ LogRel(("Error fetching desc chain to redo, %Rrc", rc));
+
+ rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf);
+ if (RT_FAILURE(rc))
+ LogRel(("Error submitting req packet, resetting %Rrc", rc));
+
+ virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf);
+ }
+ pWorkerR3->cRedoDescs = 0;
+
+ Log6Func(("fetching next descriptor chain from %s\n", VIRTQNAME(uVirtqNbr)));
+#ifdef VIRTIO_VBUF_ON_STACK
+ PVIRTQBUF pVirtqBuf = virtioCoreR3VirtqBufAlloc();
+ if (!pVirtqBuf)
+ LogRel(("Failed to allocate memory for VIRTQBUF\n"));
+ else
+ {
+ int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, pVirtqBuf, true);
+#else /* !VIRTIO_VBUF_ON_STACK */
+ PVIRTQBUF pVirtqBuf = NULL;
+ int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, &pVirtqBuf, true);
+#endif /* !VIRTIO_VBUF_ON_STACK */
+ if (rc == VERR_NOT_AVAILABLE)
+ {
+ Log6Func(("Nothing found in %s\n", VIRTQNAME(uVirtqNbr)));
+ continue;
+ }
+
+ AssertRC(rc);
+ if (uVirtqNbr == CONTROLQ_IDX)
+ virtioScsiR3Ctrl(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf);
+ else /* request queue index */
+ {
+ rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf);
+ if (RT_FAILURE(rc))
+ LogRel(("Error submitting req packet, resetting %Rrc", rc));
+ }
+
+ virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf);
+#ifdef VIRTIO_VBUF_ON_STACK
+ }
+#endif /* VIRTIO_VBUF_ON_STACK */
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Sending evnets
+*********************************************************************************************************************************/
+
+/*
+ * @todo Figure out how to implement this with R0 changes. Not used by current linux driver
+ */
+
+#if 0
+DECLINLINE(void) virtioScsiR3ReportEventsMissed(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget)
+{
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_NO_EVENT | VIRTIOSCSI_T_EVENTS_MISSED, 0);
+}
+#endif
+
+#if 0
+/* SUBSCRIBABLE EVENT - not sure when to call this or how to detect when media is added or removed
+ * via the VBox GUI */
+DECLINLINE(void) virtioScsiR3ReportMediaChange(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget)
+{
+ if (pThis->fAsyncEvtsEnabled & VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE)
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_ASYNC_NOTIFY, VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE);
+}
+
+/* ESSENTIAL (NON-SUBSCRIBABLE) EVENT TYPES (most guest virtio-scsi drivers ignore?) */
+
+DECLINLINE(void) virtioScsiR3ReportTransportReset(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget)
+{
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_HARD);
+}
+
+DECLINLINE(void) virtioScsiR3ReportParamChange(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget,
+ uint32_t uSenseCode, uint32_t uSenseQualifier)
+{
+ uint32_t uReason = uSenseQualifier << 8 | uSenseCode;
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_PARAM_CHANGE, uReason);
+
+}
+
+DECLINLINE(void) virtioScsiR3ReportTargetRemoved(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget)
+{
+ if (pThis->fHasHotplug)
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_REMOVED);
+}
+
+DECLINLINE(void) virtioScsiR3ReportTargetAdded(PDMDEVINS pDevInsPVIRTIOSCSI pThis, uint16_t uTarget)
+{
+ if (pThis->fHasHotplug)
+ virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_RESCAN);
+}
+
+#endif
+
+/**
+ * @callback_method_impl{VIRTIOCORER3,pfnStatusChanged}
+ */
+static DECLCALLBACK(void) virtioScsiR3StatusChanged(PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC, uint32_t fVirtioReady)
+{
+ PVIRTIOSCSI pThis = RT_FROM_MEMBER(pVirtio, VIRTIOSCSI, Virtio);
+ PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pVirtioCC, VIRTIOSCSICC, Virtio);
+
+ pThis->fVirtioReady = fVirtioReady;
+
+ if (fVirtioReady)
+ {
+ LogFunc(("VirtIO ready\n-----------------------------------------------------------------------------------------\n"));
+ uint64_t fFeatures = virtioCoreGetNegotiatedFeatures(&pThis->Virtio);
+ pThis->fHasT10pi = fFeatures & VIRTIO_SCSI_F_T10_PI;
+ pThis->fHasHotplug = fFeatures & VIRTIO_SCSI_F_HOTPLUG;
+ pThis->fHasInOutBufs = fFeatures & VIRTIO_SCSI_F_INOUT;
+ pThis->fHasLunChange = fFeatures & VIRTIO_SCSI_F_CHANGE;
+ pThis->fResetting = false;
+ pThisCC->fQuiescing = false;
+
+ for (unsigned i = 0; i < VIRTIOSCSI_VIRTQ_CNT; i++)
+ pThis->afVirtqAttached[i] = true;
+ }
+ else
+ {
+ LogFunc(("VirtIO is resetting\n"));
+ for (unsigned i = 0; i < VIRTIOSCSI_VIRTQ_CNT; i++)
+ pThis->afVirtqAttached[i] = false;
+
+ /*
+ * BIOS may change these values. When the OS comes up, and KVM driver accessed
+ * through Windows, it assumes they are the default size. So as per the VirtIO 1.0 spec,
+ * 5.6.4, these device configuration values must be set to default upon device reset.
+ */
+ pThis->virtioScsiConfig.uSenseSize = VIRTIOSCSI_SENSE_SIZE_DEFAULT;
+ pThis->virtioScsiConfig.uCdbSize = VIRTIOSCSI_CDB_SIZE_DEFAULT;
+ }
+
+
+}
+
+
+/*********************************************************************************************************************************
+* LEDs *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed, Target level.}
+ */
+static DECLCALLBACK(int) virtioScsiR3TargetQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, ILed);
+ if (iLUN == 0)
+ {
+ *ppLed = &pTarget->led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+/**
+ * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed, Device level.}
+ */
+static DECLCALLBACK(int) virtioScsiR3DeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pInterface, VIRTIOSCSICC, ILeds);
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PVIRTIOSCSI);
+ if (iLUN < pThis->cTargets)
+ {
+ *ppLed = &pThisCC->paTargetInstances[iLUN].led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIMEDIAPORT (target) *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation, Target level.}
+ */
+static DECLCALLBACK(int) virtioScsiR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pTarget->uTarget;
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Virtio config. *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for virtioScsiR3DevCapWrite and virtioScsiR3DevCapRead.
+ */
+static int virtioScsiR3CfgAccessed(PVIRTIOSCSI pThis, uint32_t uOffsetOfAccess, void *pv, uint32_t cb, bool fWrite)
+{
+ AssertReturn(pv && cb <= sizeof(uint32_t), fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00);
+
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uNumVirtqs, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uNumVirtqs, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uSegMax, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uSegMax, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxSectors, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxSectors, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uCmdPerLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uCmdPerLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uEventInfoSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uEventInfoSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uSenseSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS( uSenseSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uCdbSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS( uCdbSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxChannel, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxChannel, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxTarget, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxTarget, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess))
+ VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig);
+ else
+ {
+ LogFunc(("Bad access by guest to virtio_scsi_config: off=%u (%#x), cb=%u\n", uOffsetOfAccess, uOffsetOfAccess, cb));
+ return fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{VIRTIOCORER3,pfnDevCapRead}
+ */
+static DECLCALLBACK(int) virtioScsiR3DevCapRead(PPDMDEVINS pDevIns, uint32_t uOffset, void *pv, uint32_t cb)
+{
+ return virtioScsiR3CfgAccessed(PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI), uOffset, pv, cb, false /*fRead*/);
+}
+
+/**
+ * @callback_method_impl{VIRTIOCORER3,pfnDevCapWrite}
+ */
+static DECLCALLBACK(int) virtioScsiR3DevCapWrite(PPDMDEVINS pDevIns, uint32_t uOffset, const void *pv, uint32_t cb)
+{
+ return virtioScsiR3CfgAccessed(PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI), uOffset, (void *)pv, cb, true /*fWrite*/);
+}
+
+
+/*********************************************************************************************************************************
+* IBase for device and targets *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface, Target level.}
+ */
+static DECLCALLBACK(void *) virtioScsiR3TargetQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pTarget->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pTarget->IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pTarget->IMediaExPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pTarget->ILed);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface, Device level.}
+ */
+static DECLCALLBACK(void *) virtioScsiR3DeviceQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pInterface, VIRTIOSCSICC, IBase);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* Misc *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, virtio-scsi debugger info callback.}
+ */
+static DECLCALLBACK(void) virtioScsiR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+
+ /* Parse arguments. */
+ RT_NOREF(pszArgs); //bool fVerbose = pszArgs && strstr(pszArgs, "verbose") != NULL;
+
+ /* Show basic information. */
+ pHlp->pfnPrintf(pHlp, "%s#%d: virtio-scsci ",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance);
+ pHlp->pfnPrintf(pHlp, "numTargets=%lu", pThis->cTargets);
+}
+
+
+/*********************************************************************************************************************************
+* Saved state *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) virtioScsiR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ LogFunc(("LOAD EXEC!!\n"));
+
+ AssertReturn(uPass == SSM_PASS_FINAL, VERR_SSM_UNEXPECTED_PASS);
+ AssertLogRelMsgReturn(uVersion == VIRTIOSCSI_SAVED_STATE_VERSION,
+ ("uVersion=%u\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+
+ virtioScsiSetVirtqNames(pThis);
+ for (int uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ pHlp->pfnSSMGetBool(pSSM, &pThis->afVirtqAttached[uVirtqNbr]);
+
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uNumVirtqs);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uSegMax);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uMaxSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uCmdPerLun);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uEventInfoSize);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uSenseSize);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uCdbSize);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->virtioScsiConfig.uMaxChannel);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->virtioScsiConfig.uMaxTarget);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uMaxLun);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fAsyncEvtsEnabled);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fEventsMissed);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fVirtioReady);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fHasT10pi);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fHasHotplug);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fHasInOutBufs);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fHasLunChange);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->fResetting);
+
+ uint32_t cTargets;
+ int rc = pHlp->pfnSSMGetU32(pSSM, &cTargets);
+ AssertRCReturn(rc, rc);
+ AssertReturn(cTargets == pThis->cTargets,
+ pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_LOAD_CONFIG_MISMATCH, RT_SRC_POS,
+ N_("target count has changed: %u saved, %u configured now"),
+ cTargets, pThis->cTargets));
+
+ for (uint16_t uTarget = 0; uTarget < pThis->cTargets; uTarget++)
+ {
+ uint16_t cReqsRedo;
+ rc = pHlp->pfnSSMGetU16(pSSM, &cReqsRedo);
+ AssertRCReturn(rc, rc);
+ AssertReturn(cReqsRedo < VIRTQ_SIZE,
+ pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
+ N_("Bad count of I/O transactions to re-do in saved state (%#x, max %#x - 1)"),
+ cReqsRedo, VIRTQ_SIZE));
+
+ for (uint16_t uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ {
+ PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr];
+ pWorkerR3->cRedoDescs = 0;
+ }
+
+ for (int i = 0; i < cReqsRedo; i++)
+ {
+ uint16_t uVirtqNbr;
+ rc = pHlp->pfnSSMGetU16(pSSM, &uVirtqNbr);
+ AssertRCReturn(rc, rc);
+ AssertReturn(uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT,
+ pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
+ N_("Bad queue index for re-do in saved state (%#x, max %#x)"),
+ uVirtqNbr, VIRTIOSCSI_VIRTQ_CNT - 1));
+
+ uint16_t idxHead;
+ rc = pHlp->pfnSSMGetU16(pSSM, &idxHead);
+ AssertRCReturn(rc, rc);
+ AssertReturn(idxHead < VIRTQ_SIZE,
+ pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
+ N_("Bad queue element index for re-do in saved state (%#x, max %#x)"),
+ idxHead, VIRTQ_SIZE - 1));
+
+ PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr];
+ pWorkerR3->auRedoDescs[pWorkerR3->cRedoDescs++] = idxHead;
+ pWorkerR3->cRedoDescs %= VIRTQ_SIZE;
+ }
+ }
+
+ /*
+ * Call the virtio core to let it load its state.
+ */
+ rc = virtioCoreR3ModernDeviceLoadExec(&pThis->Virtio, pDevIns->pHlpR3, pSSM,
+ uVersion, VIRTIOSCSI_SAVED_STATE_VERSION, pThis->virtioScsiConfig.uNumVirtqs);
+
+ /*
+ * Nudge request queue workers
+ */
+ for (int uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ {
+ if (pThis->afVirtqAttached[uVirtqNbr])
+ {
+ LogFunc(("Waking %s worker.\n", VIRTQNAME(uVirtqNbr)));
+ int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess);
+ AssertRCReturn(rc, rc2);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) virtioScsiR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ LogFunc(("SAVE EXEC!!\n"));
+
+ for (int uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ pHlp->pfnSSMPutBool(pSSM, pThis->afVirtqAttached[uVirtqNbr]);
+
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uNumVirtqs);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uSegMax);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uMaxSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uCmdPerLun);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uEventInfoSize);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uSenseSize);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uCdbSize);
+ pHlp->pfnSSMPutU16(pSSM, pThis->virtioScsiConfig.uMaxChannel);
+ pHlp->pfnSSMPutU16(pSSM, pThis->virtioScsiConfig.uMaxTarget);
+ pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uMaxLun);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fAsyncEvtsEnabled);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fEventsMissed);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fVirtioReady);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fHasT10pi);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fHasHotplug);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fHasInOutBufs);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fHasLunChange);
+ pHlp->pfnSSMPutU32(pSSM, pThis->fResetting);
+
+ AssertMsg(!pThis->cActiveReqs, ("There are still outstanding requests on this device\n"));
+
+ pHlp->pfnSSMPutU32(pSSM, pThis->cTargets);
+
+ for (uint16_t uTarget = 0; uTarget < pThis->cTargets; uTarget++)
+ {
+ PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget];
+
+ /* Query all suspended requests and store them in the request queue. */
+ if (pTarget->pDrvMediaEx)
+ {
+ uint32_t cReqsRedo = pTarget->pDrvMediaEx->pfnIoReqGetSuspendedCount(pTarget->pDrvMediaEx);
+
+ pHlp->pfnSSMPutU16(pSSM, cReqsRedo);
+
+ if (cReqsRedo)
+ {
+ PDMMEDIAEXIOREQ hIoReq;
+ PVIRTIOSCSIREQ pReq;
+
+ int rc = pTarget->pDrvMediaEx->pfnIoReqQuerySuspendedStart(pTarget->pDrvMediaEx, &hIoReq,
+ (void **)&pReq);
+ AssertRCBreak(rc);
+
+ while(--cReqsRedo)
+ {
+ pHlp->pfnSSMPutU16(pSSM, pReq->uVirtqNbr);
+ pHlp->pfnSSMPutU16(pSSM, pReq->pVirtqBuf->uHeadIdx);
+
+ rc = pTarget->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pTarget->pDrvMediaEx, hIoReq,
+ &hIoReq, (void **)&pReq);
+ AssertRCBreak(rc);
+ }
+ }
+ }
+ }
+
+ /*
+ * Call the virtio core to let it save its state.
+ */
+ return virtioCoreR3SaveExec(&pThis->Virtio, pDevIns->pHlpR3, pSSM, VIRTIOSCSI_SAVED_STATE_VERSION, VIRTIOSCSI_VIRTQ_CNT);
+}
+
+
+/*********************************************************************************************************************************
+* Device interface. *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnDetach}
+ *
+ * One harddisk at one port has been unplugged.
+ * The VM is suspended at this point.
+ */
+static DECLCALLBACK(void) virtioScsiR3Detach(PPDMDEVINS pDevIns, unsigned uTarget, uint32_t fFlags)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ AssertReturnVoid(uTarget < pThis->cTargets);
+ PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget];
+
+ LogFunc((""));
+
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("virtio-scsi: Device does not support hotplugging\n"));
+ RT_NOREF(fFlags);
+
+ /*
+ * Zero all important members.
+ */
+ pTarget->fPresent = false;
+ pTarget->pDrvBase = NULL;
+ pTarget->pDrvMedia = NULL;
+ pTarget->pDrvMediaEx = NULL;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnAttach}
+ *
+ * This is called when we change block driver.
+ */
+static DECLCALLBACK(int) virtioScsiR3Attach(PPDMDEVINS pDevIns, unsigned uTarget, uint32_t fFlags)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ AssertReturn(uTarget < pThis->cTargets, VERR_PDM_LUN_NOT_FOUND);
+ PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget];
+
+ Assert(pTarget->pDevIns == pDevIns);
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("virtio-scsi: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ AssertRelease(!pTarget->pDrvBase);
+ Assert(pTarget->uTarget == uTarget);
+
+ /*
+ * Try attach the SCSI driver and get the interfaces, required as well as optional.
+ */
+ int rc = PDMDevHlpDriverAttach(pDevIns, pTarget->uTarget, &pDevIns->IBase, &pTarget->pDrvBase, pTarget->pszTargetName);
+ if (RT_SUCCESS(rc))
+ {
+ pTarget->fPresent = true;
+ pTarget->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMedia),
+ ("virtio-scsi configuration error: LUN#%d missing basic media interface!\n", uTarget),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Get the extended media interface. */
+ pTarget->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx),
+ ("virtio-scsi configuration error: LUN#%d missing extended media interface!\n", uTarget),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pTarget->pDrvMediaEx->pfnIoReqAllocSizeSet(pTarget->pDrvMediaEx, sizeof(VIRTIOSCSIREQ));
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx),
+ ("virtio-scsi configuration error: LUN#%u: Failed to set I/O request size!\n", uTarget),
+ rc);
+ }
+ else
+ AssertMsgFailed(("Failed to attach %s. rc=%Rrc\n", pTarget->pszTargetName, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pTarget->fPresent = false;
+ pTarget->pDrvBase = NULL;
+ pTarget->pDrvMedia = NULL;
+ pTarget->pDrvMediaEx = NULL;
+ pThisCC->pMediaNotify = NULL;
+ }
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY}
+ */
+static DECLCALLBACK(bool) virtioScsiR3DeviceQuiesced(PPDMDEVINS pDevIns)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+ if (ASMAtomicReadU32(&pThis->cActiveReqs))
+ return false;
+
+ LogFunc(("Device I/O activity quiesced: %s\n",
+ virtioCoreGetStateChangeText(pThisCC->enmQuiescingFor)));
+
+ virtioCoreR3VmStateChanged(&pThis->Virtio, pThisCC->enmQuiescingFor);
+
+ pThis->fResetting = false;
+ pThisCC->fQuiescing = false;
+
+ return true;
+}
+
+/**
+ * Worker for virtioScsiR3Reset() and virtioScsiR3SuspendOrPowerOff().
+ */
+static void virtioScsiR3QuiesceDevice(PPDMDEVINS pDevIns, VIRTIOVMSTATECHANGED enmQuiscingFor)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+ /* Prevent worker threads from removing/processing elements from virtq's */
+ pThisCC->fQuiescing = true;
+ pThisCC->enmQuiescingFor = enmQuiscingFor;
+
+ PDMDevHlpSetAsyncNotification(pDevIns, virtioScsiR3DeviceQuiesced);
+
+ /* If already quiesced invoke async callback. */
+ if (!ASMAtomicReadU32(&pThis->cActiveReqs))
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnReset}
+ */
+static DECLCALLBACK(void) virtioScsiR3Reset(PPDMDEVINS pDevIns)
+{
+ LogFunc(("\n"));
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ pThis->fResetting = true;
+ virtioScsiR3QuiesceDevice(pDevIns, kvirtIoVmStateChangedReset);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnPowerOff}
+ */
+static DECLCALLBACK(void) virtioScsiR3SuspendOrPowerOff(PPDMDEVINS pDevIns, VIRTIOVMSTATECHANGED enmType)
+{
+ LogFunc(("\n"));
+
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+ /* VM is halted, thus no new I/O being dumped into queues by the guest.
+ * Workers have been flagged to stop pulling stuff already queued-up by the guest.
+ * Now tell lower-level to to suspend reqs (for example, DrvVD suspends all reqs
+ * on its wait queue, and we will get a callback as the state changes to
+ * suspended (and later, resumed) for each).
+ */
+ for (uint32_t i = 0; i < pThis->cTargets; i++)
+ {
+ PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[i];
+ if (pTarget->pDrvMediaEx)
+ pTarget->pDrvMediaEx->pfnNotifySuspend(pTarget->pDrvMediaEx);
+ }
+
+ virtioScsiR3QuiesceDevice(pDevIns, enmType);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnSuspend}
+ */
+static DECLCALLBACK(void) virtioScsiR3PowerOff(PPDMDEVINS pDevIns)
+{
+ LogFunc(("\n"));
+ virtioScsiR3SuspendOrPowerOff(pDevIns, kvirtIoVmStateChangedPowerOff);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnSuspend}
+ */
+static DECLCALLBACK(void) virtioScsiR3Suspend(PPDMDEVINS pDevIns)
+{
+ LogFunc(("\n"));
+ virtioScsiR3SuspendOrPowerOff(pDevIns, kvirtIoVmStateChangedSuspend);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnResume}
+ */
+static DECLCALLBACK(void) virtioScsiR3Resume(PPDMDEVINS pDevIns)
+{
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ LogFunc(("\n"));
+
+ pThisCC->fQuiescing = false;
+
+ /* Wake worker threads flagged to skip pulling queue entries during quiesce
+ * to ensure they re-check their queues. Active request queues may already
+ * be awake due to new reqs coming in.
+ */
+ for (uint16_t uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_REQ_VIRTQ_CNT; uVirtqNbr++)
+ {
+ if ( virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr)
+ && ASMAtomicReadBool(&pThis->aWorkers[uVirtqNbr].fSleeping))
+ {
+ Log6Func(("waking %s worker.\n", VIRTQNAME(uVirtqNbr)));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess);
+ AssertRC(rc);
+ }
+ }
+ /* Ensure guest is working the queues too. */
+ virtioCoreR3VmStateChanged(&pThis->Virtio, kvirtIoVmStateChangedResume);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) virtioScsiR3MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+#if 0 /* need more info about how to use this event. The VirtIO 1.0 specification
+ * lists several SCSI related event types but presumes the reader knows
+ * how to use them without providing references. */
+ virtioScsiR3ReportMediaChange(pDevIns, pThis, pTarget->uTarget);
+#endif
+
+ if (pThisCC->pMediaNotify)
+ {
+ int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->pMediaNotify, pTarget->uTarget);
+ AssertRC(rc);
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) virtioScsiR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort);
+ PPDMDEVINS pDevIns = pTarget->pDevIns;
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ RT_NOREF(hIoReq, pvIoReqAlloc);
+
+ switch (enmState)
+ {
+ case PDMMEDIAEXIOREQSTATE_SUSPENDED:
+ {
+ /* Stop considering this request active */
+ virtioScsiR3Release(pDevIns, pThis, pThisCC);
+ break;
+ }
+ case PDMMEDIAEXIOREQSTATE_ACTIVE:
+ virtioScsiR3Retain(pThis);
+ break;
+ default:
+ AssertMsgFailed(("Invalid request state given %u\n", enmState));
+ }
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnDestruct}
+ */
+static DECLCALLBACK(int) virtioScsiR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+ RTMemFree(pThisCC->paTargetInstances);
+ pThisCC->paTargetInstances = NULL;
+ pThisCC->pMediaNotify = NULL;
+
+ for (unsigned uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ {
+ PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr];
+ if (pWorker->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pWorker->hEvtProcess);
+ pWorker->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+
+ if (pThisCC->aWorkers[uVirtqNbr].pThread)
+ {
+ /* Destroy the thread. */
+ int rcThread;
+ int rc = PDMDevHlpThreadDestroy(pDevIns, pThisCC->aWorkers[uVirtqNbr].pThread, &rcThread);
+ if (RT_FAILURE(rc) || RT_FAILURE(rcThread))
+ AssertMsgFailed(("%s Failed to destroythread rc=%Rrc rcThread=%Rrc\n",
+ __FUNCTION__, rc, rcThread));
+ pThisCC->aWorkers[uVirtqNbr].pThread = NULL;
+ }
+ }
+
+ virtioCoreR3Term(pDevIns, &pThis->Virtio, &pThisCC->Virtio);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnConstruct}
+ */
+static DECLCALLBACK(int) virtioScsiR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /*
+ * Quick initialization of the state data, making sure that the destructor always works.
+ */
+ pThisCC->pDevIns = pDevIns;
+
+ LogFunc(("PDM device instance: %d\n", iInstance));
+ RTStrPrintf(pThis->szInstance, sizeof(pThis->szInstance), "VIRTIOSCSI%d", iInstance);
+
+ pThisCC->IBase.pfnQueryInterface = virtioScsiR3DeviceQueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = virtioScsiR3DeviceQueryStatusLed;
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "NumTargets|Bootable", "");
+
+ int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "NumTargets", &pThis->cTargets, 1);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi configuration error: failed to read NumTargets as integer"));
+ if (pThis->cTargets < 1 || pThis->cTargets > VIRTIOSCSI_MAX_TARGETS)
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("virtio-scsi configuration error: NumTargets=%u is out of range (1..%u)"),
+ pThis->cTargets, VIRTIOSCSI_MAX_TARGETS);
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "Bootable", &pThis->fBootable, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi configuration error: failed to read Bootable as boolean"));
+
+ LogRel(("%s: Targets=%u Bootable=%RTbool (unimplemented) R0Enabled=%RTbool RCEnabled=%RTbool\n",
+ pThis->szInstance, pThis->cTargets, pThis->fBootable, pDevIns->fR0Enabled, pDevIns->fRCEnabled));
+
+
+ /*
+ * Do core virtio initialization.
+ */
+
+ /* Configure virtio_scsi_config that transacts via VirtIO implementation's Dev. Specific Cap callbacks */
+ pThis->virtioScsiConfig.uNumVirtqs = VIRTIOSCSI_REQ_VIRTQ_CNT;
+ pThis->virtioScsiConfig.uSegMax = VIRTIOSCSI_MAX_SEG_COUNT;
+ pThis->virtioScsiConfig.uMaxSectors = VIRTIOSCSI_MAX_SECTORS_HINT;
+ pThis->virtioScsiConfig.uCmdPerLun = VIRTIOSCSI_MAX_COMMANDS_PER_LUN;
+ pThis->virtioScsiConfig.uEventInfoSize = sizeof(VIRTIOSCSI_EVENT_T); /*VirtIO 1.0 Spec says at least this size! */
+ pThis->virtioScsiConfig.uSenseSize = VIRTIOSCSI_SENSE_SIZE_DEFAULT;
+ pThis->virtioScsiConfig.uCdbSize = VIRTIOSCSI_CDB_SIZE_DEFAULT;
+ pThis->virtioScsiConfig.uMaxChannel = VIRTIOSCSI_MAX_CHANNEL_HINT;
+ pThis->virtioScsiConfig.uMaxTarget = pThis->cTargets;
+ pThis->virtioScsiConfig.uMaxLun = VIRTIOSCSI_MAX_LUN;
+
+ /* Initialize the generic Virtio core: */
+ pThisCC->Virtio.pfnVirtqNotified = virtioScsiNotified;
+ pThisCC->Virtio.pfnStatusChanged = virtioScsiR3StatusChanged;
+ pThisCC->Virtio.pfnDevCapRead = virtioScsiR3DevCapRead;
+ pThisCC->Virtio.pfnDevCapWrite = virtioScsiR3DevCapWrite;
+
+ VIRTIOPCIPARAMS VirtioPciParams;
+ VirtioPciParams.uDeviceId = PCI_DEVICE_ID_VIRTIOSCSI_HOST;
+ VirtioPciParams.uClassBase = PCI_CLASS_BASE_MASS_STORAGE;
+ VirtioPciParams.uClassSub = PCI_CLASS_SUB_SCSI_STORAGE_CONTROLLER;
+ VirtioPciParams.uClassProg = PCI_CLASS_PROG_UNSPECIFIED;
+ VirtioPciParams.uSubsystemId = PCI_DEVICE_ID_VIRTIOSCSI_HOST; /* VirtIO 1.0 spec allows PCI Device ID here */
+ VirtioPciParams.uInterruptLine = 0x00;
+ VirtioPciParams.uInterruptPin = 0x01;
+
+ rc = virtioCoreR3Init(pDevIns, &pThis->Virtio, &pThisCC->Virtio, &VirtioPciParams, pThis->szInstance,
+ VIRTIOSCSI_HOST_SCSI_FEATURES_OFFERED, 0 /*fOfferLegacy*/,
+ &pThis->virtioScsiConfig /*pvDevSpecificCap*/, sizeof(pThis->virtioScsiConfig));
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi: failed to initialize VirtIO"));
+
+ /*
+ * Initialize queues.
+ */
+
+ virtioScsiSetVirtqNames(pThis);
+
+ /* Attach the queues and create worker threads for them: */
+ for (uint16_t uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++)
+ {
+ rc = virtioCoreR3VirtqAttach(&pThis->Virtio, uVirtqNbr, VIRTQNAME(uVirtqNbr));
+ if (RT_FAILURE(rc))
+ continue;
+ if (uVirtqNbr == CONTROLQ_IDX || IS_REQ_VIRTQ(uVirtqNbr))
+ {
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->aWorkers[uVirtqNbr].pThread,
+ (void *)(uintptr_t)uVirtqNbr, virtioScsiR3WorkerThread,
+ virtioScsiR3WorkerWakeUp, 0, RTTHREADTYPE_IO, VIRTQNAME(uVirtqNbr));
+ if (rc != VINF_SUCCESS)
+ {
+ LogRel(("Error creating thread for Virtual Virtq %s: %Rrc\n", VIRTQNAME(uVirtqNbr), rc));
+ return rc;
+ }
+
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->aWorkers[uVirtqNbr].hEvtProcess);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("DevVirtioSCSI: Failed to create SUP event semaphore"));
+ }
+ pThis->afVirtqAttached[uVirtqNbr] = true;
+ }
+
+ /*
+ * Initialize per device instances (targets).
+ */
+ Log2Func(("Probing %d targets ...\n", pThis->cTargets));
+
+ pThisCC->paTargetInstances = (PVIRTIOSCSITARGET)RTMemAllocZ(sizeof(VIRTIOSCSITARGET) * pThis->cTargets);
+ if (!pThisCC->paTargetInstances)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to allocate memory for target states"));
+
+ for (uint32_t uTarget = 0; uTarget < pThis->cTargets; uTarget++)
+ {
+ PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget];
+
+ if (RTStrAPrintf(&pTarget->pszTargetName, "VSCSI%u", uTarget) < 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ /* Initialize static parts of the device. */
+ pTarget->pDevIns = pDevIns;
+ pTarget->uTarget = uTarget;
+
+ pTarget->IBase.pfnQueryInterface = virtioScsiR3TargetQueryInterface;
+
+ /* IMediaPort and IMediaExPort interfaces provide callbacks for VD media and downstream driver access */
+ pTarget->IMediaPort.pfnQueryDeviceLocation = virtioScsiR3QueryDeviceLocation;
+ pTarget->IMediaPort.pfnQueryScsiInqStrings = NULL;
+ pTarget->IMediaExPort.pfnIoReqCompleteNotify = virtioScsiR3IoReqFinish;
+ pTarget->IMediaExPort.pfnIoReqCopyFromBuf = virtioScsiR3IoReqCopyFromBuf;
+ pTarget->IMediaExPort.pfnIoReqCopyToBuf = virtioScsiR3IoReqCopyToBuf;
+ pTarget->IMediaExPort.pfnIoReqStateChanged = virtioScsiR3IoReqStateChanged;
+ pTarget->IMediaExPort.pfnMediumEjected = virtioScsiR3MediumEjected;
+ pTarget->IMediaExPort.pfnIoReqQueryBuf = NULL; /* When used avoids copyFromBuf CopyToBuf*/
+ pTarget->IMediaExPort.pfnIoReqQueryDiscardRanges = NULL;
+
+ pTarget->IBase.pfnQueryInterface = virtioScsiR3TargetQueryInterface;
+ pTarget->ILed.pfnQueryStatusLed = virtioScsiR3TargetQueryStatusLed;
+ pTarget->led.u32Magic = PDMLED_MAGIC;
+
+ LogFunc(("Attaching LUN: %s\n", pTarget->pszTargetName));
+
+ AssertReturn(uTarget < pThis->cTargets, VERR_PDM_NO_SUCH_LUN);
+ rc = PDMDevHlpDriverAttach(pDevIns, uTarget, &pTarget->IBase, &pTarget->pDrvBase, pTarget->pszTargetName);
+ if (RT_SUCCESS(rc))
+ {
+ pTarget->fPresent = true;
+
+ pTarget->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIA);
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMedia),
+ ("virtio-scsi configuration error: LUN#%d missing basic media interface!\n", uTarget),
+ VERR_PDM_MISSING_INTERFACE);
+ /* Get the extended media interface. */
+ pTarget->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIAEX);
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx),
+ ("virtio-scsi configuration error: LUN#%d missing extended media interface!\n", uTarget),
+ VERR_PDM_MISSING_INTERFACE);
+
+ rc = pTarget->pDrvMediaEx->pfnIoReqAllocSizeSet(pTarget->pDrvMediaEx, sizeof(VIRTIOSCSIREQ));
+ AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx),
+ ("virtio-scsi configuration error: LUN#%u: Failed to set I/O request size!\n", uTarget),
+ rc);
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pTarget->fPresent = false;
+ pTarget->pDrvBase = NULL;
+ Log(("virtio-scsi: no driver attached to device %s\n", pTarget->pszTargetName));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("virtio-scsi: Failed to attach %s: %Rrc\n", pTarget->pszTargetName, rc));
+ return rc;
+ }
+ }
+
+ /*
+ * Status driver (optional).
+ */
+ PPDMIBASE pUpBase = NULL;
+ AssertCompile(PDM_STATUS_LUN >= VIRTIOSCSI_MAX_TARGETS);
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pUpBase, "Status Port");
+ if (RT_FAILURE(rc) && rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to attach the status LUN"));
+ if (RT_SUCCESS(rc) && pUpBase)
+ pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pUpBase, PDMIMEDIANOTIFY);
+
+
+ /*
+ * Register saved state.
+ */
+ rc = PDMDevHlpSSMRegister(pDevIns, VIRTIOSCSI_SAVED_STATE_VERSION, sizeof(*pThis),
+ virtioScsiR3SaveExec, virtioScsiR3LoadExec);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the debugger info callback (ignore errors).
+ */
+ char szTmp[128];
+ RTStrPrintf(szTmp, sizeof(szTmp), "%s%u", pDevIns->pReg->szName, pDevIns->iInstance);
+ PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "virtio-scsi info", virtioScsiR3Info);
+
+ return rc;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) virtioScsiRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI);
+ PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC);
+
+
+ pThisCC->Virtio.pfnVirtqNotified = virtioScsiNotified;
+ return virtioCoreRZInit(pDevIns, &pThis->Virtio);
+}
+
+#endif /* !IN_RING3 */
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceVirtioSCSI =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "virtio-scsi",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION
+ | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION,
+ /* .fClass = */ PDM_DEVREG_CLASS_STORAGE,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(VIRTIOSCSI),
+ /* .cbInstanceCC = */ sizeof(VIRTIOSCSICC),
+ /* .cbInstanceRC = */ sizeof(VIRTIOSCSIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ VBOX_MSIX_MAX_ENTRIES,
+ /* .pszDescription = */ "Virtio Host SCSI.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ virtioScsiR3Construct,
+ /* .pfnDestruct = */ virtioScsiR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ virtioScsiR3Reset,
+ /* .pfnSuspend = */ virtioScsiR3Suspend,
+ /* .pfnResume = */ virtioScsiR3Resume,
+ /* .pfnAttach = */ virtioScsiR3Attach,
+ /* .pfnDetach = */ virtioScsiR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ virtioScsiR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ virtioScsiRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ virtioScsiRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp b/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp
new file mode 100644
index 00000000..fbe92dcd
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvDiskIntegrity.cpp
@@ -0,0 +1,2160 @@
+/* $Id: DrvDiskIntegrity.cpp $ */
+/** @file
+ * VBox storage devices: Disk integrity check.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_DISK_INTEGRITY
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/avl.h>
+#include <iprt/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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnGetRegionCount} */
+static DECLCALLBACK(uint32_t) drvdiskintGetRegionCount(PPDMIMEDIA pInterface)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnGetRegionCount(pThis->pDrvMedia);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionProperties} */
+static DECLCALLBACK(int) drvdiskintQueryRegionProperties(PPDMIMEDIA pInterface, uint32_t uRegion, uint64_t *pu64LbaStart,
+ uint64_t *pcBlocks, uint64_t *pcbBlock,
+ PVDREGIONDATAFORM penmDataForm)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnQueryRegionProperties(pThis->pDrvMedia, uRegion, pu64LbaStart, pcBlocks, pcbBlock, penmDataForm);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnQueryRegionPropertiesForLba} */
+static DECLCALLBACK(int) drvdiskintQueryRegionPropertiesForLba(PPDMIMEDIA pInterface, uint64_t u64LbaStart,
+ uint32_t *puRegion, uint64_t *pcBlocks,
+ uint64_t *pcbBlock, PVDREGIONDATAFORM penmDataForm)
+{
+ PDRVDISKINTEGRITY pThis = PDMIMEDIA_2_DRVDISKINTEGRITY(pInterface);
+ return pThis->pDrvMedia->pfnQueryRegionPropertiesForLba(pThis->pDrvMedia, u64LbaStart, puRegion, pcBlocks, pcbBlock, penmDataForm);
+}
+
+/* -=-=-=-=- 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,pfnNotifySuspend}
+ */
+static DECLCALLBACK(void) drvdiskintNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ PDRVDISKINTEGRITY pThis = RT_FROM_MEMBER(pInterface, DRVDISKINTEGRITY, IMediaEx);
+ return pThis->pDrvMediaEx->pfnNotifySuspend(pThis->pDrvMediaEx);
+}
+
+/**
+ * @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)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVDISKINTEGRITY pThis = PDMINS_2_DATA(pDrvIns, PDRVDISKINTEGRITY);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvdiskintConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "CheckConsistency"
+ "|TraceRequests"
+ "|CheckIntervalMs"
+ "|ExpireIntervalMs"
+ "|CheckDoubleCompletions"
+ "|HistorySize"
+ "|IoLogType"
+ "|IoLogFile"
+ "|IoLogAddress"
+ "|IoLogPort"
+ "|IoLogData"
+ "|PrepopulateRamDisk"
+ "|ReadAfterWrite"
+ "|RecordWriteBeforeCompletion"
+ "|ValidateMemoryBuffers",
+ "");
+
+ int rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "CheckConsistency", &pThis->fCheckConsistency, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "TraceRequests", &pThis->fTraceRequests, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "CheckIntervalMs", &pThis->uCheckIntervalMs, 5000); /* 5 seconds */
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "ExpireIntervalMs", &pThis->uExpireIntervalMs, 20000); /* 20 seconds */
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "CheckDoubleCompletions", &pThis->fCheckDoubleCompletion, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "HistorySize", &pThis->cEntries, 512);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "PrepopulateRamDisk", &pThis->fPrepopulateRamDisk, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "ReadAfterWrite", &pThis->fReadAfterWrite, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "RecordWriteBeforeCompletion", &pThis->fRecordWriteBeforeCompletion, false);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "ValidateMemoryBuffers", &pThis->fValidateMemBufs, false);
+ AssertRC(rc);
+
+ bool fIoLogData = false;
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "IoLogData", &fIoLogData, false);
+ AssertRC(rc);
+
+ char *pszIoLogType = NULL;
+ char *pszIoLogFilename = NULL;
+ char *pszAddress = NULL;
+ uint32_t uPort = 0;
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "IoLogType", &pszIoLogType);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTStrICmp(pszIoLogType, "File"))
+ {
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "IoLogFile", &pszIoLogFilename);
+ AssertRC(rc);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Server"))
+ {
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "IoLogAddress", &pszAddress, NULL);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "IoLogPort", &uPort, 4000);
+ AssertRC(rc);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Client"))
+ {
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "IoLogAddress", &pszAddress);
+ AssertRC(rc);
+ rc = pHlp->pfnCFGMQueryU32Def(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;
+ pThis->IMedia.pfnSendCmd = NULL;
+ pThis->IMedia.pfnGetRegionCount = drvdiskintGetRegionCount;
+ pThis->IMedia.pfnQueryRegionProperties = drvdiskintQueryRegionProperties;
+ pThis->IMedia.pfnQueryRegionPropertiesForLba = drvdiskintQueryRegionPropertiesForLba;
+
+
+ /* IMediaEx. */
+ pThis->IMediaEx.pfnQueryFeatures = drvdiskintQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvdiskintNotifySuspend;
+ 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);
+ PDMDrvHlpMMHeapFree(pDrvIns, pszIoLogFilename);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Server"))
+ {
+ rc = RTTraceLogWrCreateTcpServer(&pThis->hIoLogger, NULL, pszAddress, uPort);
+ if (pszAddress)
+ PDMDrvHlpMMHeapFree(pDrvIns, pszAddress);
+ }
+ else if (!RTStrICmp(pszIoLogType, "Client"))
+ {
+ rc = RTTraceLogWrCreateTcpClient(&pThis->hIoLogger, NULL, pszAddress, uPort);
+ PDMDrvHlpMMHeapFree(pDrvIns, pszAddress);
+ }
+ else
+ AssertMsgFailed(("Invalid I/O log type given: %s\n", pszIoLogType));
+
+ PDMDrvHlpMMHeapFree(pDrvIns, 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..669989a5
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-darwin.cpp
@@ -0,0 +1,768 @@
+/* $Id: DrvHostBase-darwin.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, OS X specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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..8aaaaa12
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-freebsd.cpp
@@ -0,0 +1,448 @@
+/* $Id: DrvHostBase-freebsd.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, FreeBSD specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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/file.h>
+#include <iprt/log.h>
+#include <iprt/string.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);
+ }
+
+ return rc;
+}
+
+
+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..1154f11b
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-linux.cpp
@@ -0,0 +1,399 @@
+/* $Id: DrvHostBase-linux.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Linux specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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..c9b9205a
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-solaris.cpp
@@ -0,0 +1,431 @@
+/* $Id: DrvHostBase-solaris.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Solaris specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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..4d2ac9cd
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase-win.cpp
@@ -0,0 +1,568 @@
+/* $Id: DrvHostBase-win.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver, Windows specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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..6d516b08
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.cpp
@@ -0,0 +1,1584 @@
+/* $Id: DrvHostBase.cpp $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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.
+ *
+ * @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)
+ {
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ 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 (!pHlp->pfnCFGMAreValuesValid(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 = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(pCfg, "Locked", &pThis->fLocked, false);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: Query \"Locked\" resulted in %Rrc.\n", rc));
+ return rc;
+ }
+
+ /* BIOS visible */
+ rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryStringAlloc(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));
+ PDMDrvHlpMMHeapFree(pDrvIns, psz);
+ return rc;
+ }
+ PDMDrvHlpMMHeapFree(pDrvIns, 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 = pHlp->pfnCFGMQueryBoolDef(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..3af1da0a
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostBase.h
@@ -0,0 +1,202 @@
+/* $Id: DrvHostBase.h $ */
+/** @file
+ * DrvHostBase - Host base drive access driver.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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..a9a58725
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostDVD.cpp
@@ -0,0 +1,581 @@
+/* $Id: DrvHostDVD.cpp $ */
+/** @file
+ * DrvHostDVD - Host DVD block driver.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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 driver 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, PDMMEDIAEXIOREQSCSITXDIR *penmTxDirRet,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, size_t *pcbSenseRet,
+ 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, cbXfer);
+
+ 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
+ && RT_VALID_PTR(pabSense)
+ && cbSense > 0)
+ {
+ size_t cbSenseCpy = RT_MIN(cbSense, sizeof(pThis->abATAPISense));
+
+ memcpy(pabSense, &pThis->abATAPISense[0], cbSenseCpy);
+ if (pcbSenseRet)
+ *pcbSenseRet = cbSenseCpy;
+ }
+
+ if (penmTxDirRet)
+ {
+ switch (enmXferDir)
+ {
+ case PDMMEDIATXDIR_NONE:
+ *penmTxDirRet = PDMMEDIAEXIOREQSCSITXDIR_NONE;
+ break;
+ case PDMMEDIATXDIR_FROM_DEVICE:
+ *penmTxDirRet = PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+ break;
+ case PDMMEDIATXDIR_TO_DEVICE:
+ *penmTxDirRet = PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE;
+ break;
+ default:
+ *penmTxDirRet = PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN;
+ }
+ }
+
+ 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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ int rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBool(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;
+}
+
+/**
+ * Reset a host dvd drive driver instance.
+ *
+ * @copydoc FNPDMDRVRESET
+ */
+static DECLCALLBACK(void) drvHostDvdReset(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
+
+ if (pThis->pTrackList)
+ {
+ ATAPIPassthroughTrackListDestroy(pThis->pTrackList);
+ pThis->pTrackList = NULL;
+ }
+
+ int rc = drvHostBaseDoLockOs(&pThis->Core, false);
+ RT_NOREF(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 */
+ drvHostDvdReset,
+ /* 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..b36288c8
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvHostFloppy.cpp
@@ -0,0 +1,120 @@
+/* $Id: DrvHostFloppy.cpp $ */
+/** @file
+ *
+ * VBox storage devices:
+ * Host floppy block driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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\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..bf43c511
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvRamDisk.cpp
@@ -0,0 +1,1863 @@
+/* $Id: DrvRamDisk.cpp $ */
+/** @file
+ * VBox storage devices: RAM disk driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_DISK_INTEGRITY
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/avl.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/memcache.h>
+#include <iprt/message.h>
+#include <iprt/sg.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/asm.h>
+#include <iprt/req.h>
+#include <iprt/thread.h>
+
+#include "VBoxDD.h"
+#include "IOBufMgmt.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Pointer to a ramdisk driver instance. */
+typedef struct DRVRAMDISK *PDRVRAMDISK;
+
+/**
+ * Disk segment.
+ */
+typedef struct DRVDISKSEGMENT
+{
+ /** AVL core. */
+ AVLRFOFFNODECORE Core;
+ /** Size of the segment */
+ size_t cbSeg;
+ /** Data for this segment */
+ uint8_t *pbSeg;
+} DRVDISKSEGMENT, *PDRVDISKSEGMENT;
+
+/**
+ * VD I/O request state.
+ */
+typedef enum VDIOREQSTATE
+{
+ /** Invalid. */
+ VDIOREQSTATE_INVALID = 0,
+ /** The request is not in use and resides on the free list. */
+ VDIOREQSTATE_FREE,
+ /** The request was just allocated and is not active. */
+ VDIOREQSTATE_ALLOCATED,
+ /** The request was allocated and is in use. */
+ VDIOREQSTATE_ACTIVE,
+ /** The request was suspended and is not actively processed. */
+ VDIOREQSTATE_SUSPENDED,
+ /** The request is in the last step of completion and syncs memory. */
+ VDIOREQSTATE_COMPLETING,
+ /** The request completed. */
+ VDIOREQSTATE_COMPLETED,
+ /** The request was aborted but wasn't returned as complete from the storage
+ * layer below us. */
+ VDIOREQSTATE_CANCELED,
+ /** 32bit hack. */
+ VDIOREQSTATE_32BIT_HACK = 0x7fffffff
+} VDIOREQSTATE;
+
+/**
+ * VD I/O Request.
+ */
+typedef struct PDMMEDIAEXIOREQINT
+{
+ /** List node for the list of allocated requests. */
+ RTLISTNODE NdAllocatedList;
+ /** List for requests waiting for I/O memory or on the redo list. */
+ RTLISTNODE NdLstWait;
+ /** I/O request type. */
+ PDMMEDIAEXIOREQTYPE enmType;
+ /** Request state. */
+ volatile VDIOREQSTATE enmState;
+ /** I/O request ID. */
+ PDMMEDIAEXIOREQID uIoReqId;
+ /** Pointer to the disk container. */
+ PDRVRAMDISK pDisk;
+ /** Flags. */
+ uint32_t fFlags;
+ /** Timestamp when the request was submitted. */
+ uint64_t tsSubmit;
+ /** Type dependent data. */
+ union
+ {
+ /** Read/Write request sepcific data. */
+ struct
+ {
+ /** Start offset of the request. */
+ uint64_t offStart;
+ /** Size of the request. */
+ size_t cbReq;
+ /** Size left for this request. */
+ size_t cbReqLeft;
+ /** Size of the allocated I/O buffer. */
+ size_t cbIoBuf;
+ /** I/O buffer descriptor. */
+ IOBUFDESC IoBuf;
+ } ReadWrite;
+ /** Discard specific data. */
+ struct
+ {
+ /** Pointer to array of ranges to discard. */
+ PRTRANGE paRanges;
+ /** Number of ranges to discard. */
+ unsigned cRanges;
+ } Discard;
+ };
+ /** Allocator specific memory - variable size. */
+ uint8_t abAlloc[1];
+} PDMMEDIAEXIOREQINT;
+/** Pointer to a VD I/O request. */
+typedef PDMMEDIAEXIOREQINT *PPDMMEDIAEXIOREQINT;
+
+/**
+ * Structure for holding a list of allocated requests.
+ */
+typedef struct VDLSTIOREQALLOC
+{
+ /** Mutex protecting the table of allocated requests. */
+ RTSEMFASTMUTEX hMtxLstIoReqAlloc;
+ /** List anchor. */
+ RTLISTANCHOR LstIoReqAlloc;
+} VDLSTIOREQALLOC;
+typedef VDLSTIOREQALLOC *PVDLSTIOREQALLOC;
+
+/** Number of bins for allocated requests. */
+#define DRVVD_VDIOREQ_ALLOC_BINS 8
+
+/**
+ * Disk integrity driver instance data.
+ *
+ * @implements PDMIMEDIA
+ */
+typedef struct DRVRAMDISK
+{
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the media driver below us.
+ * This is NULL if the media is not mounted. */
+ PPDMIMEDIA pDrvMedia;
+ /** Our media interface */
+ PDMIMEDIA IMedia;
+
+ /** The media port interface above. */
+ PPDMIMEDIAPORT pDrvMediaPort;
+ /** Media port interface */
+ PDMIMEDIAPORT IMediaPort;
+
+ /** Flag whether the RAM disk was pre allocated. */
+ bool fPreallocRamDisk;
+ /** Flag whether to report a non totating medium. */
+ bool fNonRotational;
+ /** AVL tree containing the disk blocks to check. */
+ PAVLRFOFFTREE pTreeSegments;
+ /** Size of the disk. */
+ uint64_t cbDisk;
+ /** Size of one sector. */
+ uint32_t cbSector;
+
+ /** Worker request queue. */
+ RTREQQUEUE hReqQ;
+ /** Worker thread for async requests. */
+ RTTHREAD hThrdWrk;
+
+ /** @name IMEDIAEX interface support specific members.
+ * @{ */
+ /** Pointer to the IMEDIAEXPORT interface above us. */
+ PPDMIMEDIAEXPORT pDrvMediaExPort;
+ /** Our extended media interface. */
+ PDMIMEDIAEX IMediaEx;
+ /** Memory cache for the I/O requests. */
+ RTMEMCACHE hIoReqCache;
+ /** I/O buffer manager. */
+ IOBUFMGR hIoBufMgr;
+ /** Active request counter. */
+ volatile uint32_t cIoReqsActive;
+ /** Bins for allocated requests. */
+ VDLSTIOREQALLOC aIoReqAllocBins[DRVVD_VDIOREQ_ALLOC_BINS];
+ /** List of requests for I/O memory to be available - VDIOREQ::NdLstWait. */
+ RTLISTANCHOR LstIoReqIoBufWait;
+ /** Critical section protecting the list of requests waiting for I/O memory. */
+ RTCRITSECT CritSectIoReqsIoBufWait;
+ /** Number of requests waiting for a I/O buffer. */
+ volatile uint32_t cIoReqsWaiting;
+ /** Flag whether we have to resubmit requests on resume because the
+ * VM was suspended due to a recoverable I/O error.
+ */
+ volatile bool fRedo;
+ /** List of requests we have to redo. */
+ RTLISTANCHOR LstIoReqRedo;
+ /** Criticial section protecting the list of waiting requests. */
+ RTCRITSECT CritSectIoReqRedo;
+ /** Number of errors logged so far. */
+ unsigned cErrors;
+ /** @} */
+
+} DRVRAMDISK;
+
+
+static void drvramdiskMediaExIoReqComplete(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq,
+ int rcReq);
+
+/**
+ * Record a successful write to the virtual disk.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param pSgBuf The S/G buffer holding the data to write.
+ * @param off Start offset.
+ * @param cbWrite Number of bytes to record.
+ */
+static int drvramdiskWriteWorker(PDRVRAMDISK pThis, PRTSGBUF pSgBuf,
+ uint64_t off, size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pSgBuf=%#p off=%llx cbWrite=%u\n",
+ pThis, pSgBuf, off, cbWrite));
+
+ /* Update the segments */
+ size_t cbLeft = cbWrite;
+ RTFOFF offCurr = (RTFOFF)off;
+
+ while (cbLeft)
+ {
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fSet = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ Assert(cbRange % 512 == 0);
+
+ /* Create new segment */
+ pSeg = (PDRVDISKSEGMENT)RTMemAllocZ(sizeof(DRVDISKSEGMENT));
+ if (pSeg)
+ {
+ pSeg->Core.Key = offCurr;
+ pSeg->Core.KeyLast = offCurr + (RTFOFF)cbRange - 1;
+ pSeg->cbSeg = cbRange;
+ pSeg->pbSeg = (uint8_t *)RTMemAllocZ(cbRange);
+ if (!pSeg->pbSeg)
+ RTMemFree(pSeg);
+ else
+ {
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); RT_NOREF(fInserted);
+ fSet = true;
+ }
+ }
+ }
+ else
+ {
+ fSet = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (fSet)
+ {
+ AssertPtr(pSeg);
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, pSeg->pbSeg + offSeg, cbRange);
+ Assert(cbCopied == cbRange); RT_NOREF(cbCopied);
+ }
+ else
+ RTSgBufAdvance(pSgBuf, cbRange);
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return rc;
+}
+
+/**
+ * Read data from the ram disk.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk driver instance data.
+ * @param pSgBuf The S/G buffer to store the data.
+ * @param off Start offset.
+ * @param cbRead Number of bytes to read.
+ */
+static int drvramdiskReadWorker(PDRVRAMDISK pThis, PRTSGBUF pSgBuf,
+ uint64_t off, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pSgBuf=%#p off=%llx cbRead=%u\n",
+ pThis, pSgBuf, off, cbRead));
+
+ Assert(off % 512 == 0);
+ Assert(cbRead % 512 == 0);
+
+ /* Compare read data */
+ size_t cbLeft = cbRead;
+ RTFOFF offCurr = (RTFOFF)off;
+
+ while (cbLeft)
+ {
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fCmp = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ /* No segment means everything should be 0 for this part. */
+ RTSgBufSet(pSgBuf, 0, cbRange);
+ }
+ else
+ {
+ fCmp = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+
+ RTSGSEG Seg;
+ RTSGBUF SgBufSrc;
+
+ Seg.cbSeg = cbRange;
+ Seg.pvSeg = pSeg->pbSeg + offSeg;
+
+ RTSgBufInit(&SgBufSrc, &Seg, 1);
+ RTSgBufCopy(pSgBuf, &SgBufSrc, cbRange);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return rc;
+}
+
+/**
+ * Discards the given ranges from the disk.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param paRanges Array of ranges to discard.
+ * @param cRanges Number of ranges in the array.
+ */
+static int drvramdiskDiscardRecords(PDRVRAMDISK pThis, PCRTRANGE paRanges, unsigned cRanges)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p paRanges=%#p cRanges=%u\n", pThis, paRanges, cRanges));
+
+ for (unsigned i = 0; i < cRanges; i++)
+ {
+ uint64_t offStart = paRanges[i].offStart;
+ size_t cbLeft = paRanges[i].cbRange;
+
+ LogFlowFunc(("Discarding off=%llu cbRange=%zu\n", offStart, cbLeft));
+
+ while (cbLeft)
+ {
+ size_t cbRange;
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offStart);
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offStart, true);
+ if ( !pSeg
+ || (RTFOFF)offStart + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offStart;
+
+ Assert(!(cbRange % 512));
+ }
+ else
+ {
+ size_t cbPreLeft, cbPostLeft;
+
+ cbRange = RT_MIN(cbLeft, pSeg->Core.KeyLast - offStart + 1);
+ cbPreLeft = offStart - pSeg->Core.Key;
+ cbPostLeft = pSeg->cbSeg - cbRange - cbPreLeft;
+
+ Assert(!(cbRange % 512));
+ Assert(!(cbPreLeft % 512));
+ Assert(!(cbPostLeft % 512));
+
+ LogFlowFunc(("cbRange=%zu cbPreLeft=%zu cbPostLeft=%zu\n",
+ cbRange, cbPreLeft, cbPostLeft));
+
+ RTAvlrFileOffsetRemove(pThis->pTreeSegments, pSeg->Core.Key);
+
+ if (!cbPreLeft && !cbPostLeft)
+ {
+ /* Just free the whole segment. */
+ LogFlowFunc(("Freeing whole segment pSeg=%#p\n", pSeg));
+ RTMemFree(pSeg->pbSeg);
+ RTMemFree(pSeg);
+ }
+ else if (cbPreLeft && !cbPostLeft)
+ {
+ /* Realloc to new size and insert. */
+ LogFlowFunc(("Realloc segment pSeg=%#p\n", pSeg));
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPreLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ else if (!cbPreLeft && cbPostLeft)
+ {
+ /* Move data to the front and realloc. */
+ LogFlowFunc(("Move data and realloc segment pSeg=%#p\n", pSeg));
+ memmove(pSeg->pbSeg, pSeg->pbSeg + cbRange, cbPostLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPostLeft);
+ pSeg->Core.Key += cbRange;
+ pSeg->cbSeg = cbPostLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ else
+ {
+ /* Split the segment into 2 new segments. */
+ LogFlowFunc(("Split segment pSeg=%#p\n", pSeg));
+ PDRVDISKSEGMENT pSegPost = (PDRVDISKSEGMENT)RTMemAllocZ(sizeof(DRVDISKSEGMENT));
+ if (pSegPost)
+ {
+ pSegPost->Core.Key = pSeg->Core.Key + cbPreLeft + cbRange;
+ pSegPost->Core.KeyLast = pSeg->Core.KeyLast;
+ pSegPost->cbSeg = cbPostLeft;
+ pSegPost->pbSeg = (uint8_t *)RTMemAllocZ(cbPostLeft);
+ if (!pSegPost->pbSeg)
+ RTMemFree(pSegPost);
+ else
+ {
+ memcpy(pSegPost->pbSeg, pSeg->pbSeg + cbPreLeft + cbRange, cbPostLeft);
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSegPost->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ }
+
+ /* Shrink the current segment. */
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPreLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ } /* if (cbPreLeft && cbPostLeft) */
+ }
+
+ offStart += cbRange;
+ cbLeft -= cbRange;
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/* -=-=-=-=- IMedia -=-=-=-=- */
+
+
+/*********************************************************************************************************************************
+* Media interface methods *
+*********************************************************************************************************************************/
+
+/** @copydoc PDMIMEDIA::pfnRead */
+static DECLCALLBACK(int) drvramdiskRead(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskReadWorker(pThis, &SgBuf, off, cbRead);
+}
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) drvramdiskWrite(PPDMIMEDIA pInterface,
+ uint64_t off, const void *pvBuf,
+ size_t cbWrite)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbWrite;
+ Seg.pvSeg = (void *)pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskWriteWorker(pThis, &SgBuf, off, cbWrite);
+}
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) drvramdiskFlush(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ /* Nothing to do here. */
+ return VINF_SUCCESS;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvramdiskGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->cbDisk;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvramdiskBiosIsVisible(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return false;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetType */
+static DECLCALLBACK(PDMMEDIATYPE) drvramdiskGetType(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return PDMMEDIATYPE_HARD_DISK;
+}
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvramdiskIsReadOnly(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return false; /** @todo */
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF2(pInterface, pPCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF2(pInterface, pPCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF2(pInterface, pLCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF2(pInterface, pLCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) drvramdiskGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ RT_NOREF1(pInterface);
+ return RTUuidClear(pUuid);
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSectorSize */
+static DECLCALLBACK(uint32_t) drvramdiskGetSectorSize(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->cbSector;
+}
+
+/** @copydoc PDMIMEDIA::pfnDiscard */
+static DECLCALLBACK(int) drvramdiskDiscard(PPDMIMEDIA pInterface, PCRTRANGE paRanges, unsigned cRanges)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return drvramdiskDiscardRecords(pThis, paRanges, cRanges);
+}
+
+/** @copydoc PDMIMEDIA::pfnReadPcBios */
+static DECLCALLBACK(int) drvramdiskReadPcBios(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskReadWorker(pThis, &SgBuf, off, cbRead);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvramdiskIsNonRotational(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->fNonRotational;
+}
+
+
+/*********************************************************************************************************************************
+* Extended media interface methods *
+*********************************************************************************************************************************/
+
+static void drvramdiskMediaExIoReqWarningOutOfMemory(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("RamDisk#%u: Out of memory\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvRamDisk_OOM",
+ N_("There is not enough free memory for the ramdisk"));
+ AssertRC(rc);
+}
+
+/**
+ * Checks whether a given status code indicates a recoverable error
+ * suspending the VM if it is.
+ *
+ * @returns Flag indicating whether the status code is a recoverable error
+ * (full disk, broken network connection).
+ * @param pThis VBox disk container instance data.
+ * @param rc Status code to check.
+ */
+bool drvramdiskMediaExIoReqIsRedoSetWarning(PDRVRAMDISK pThis, int rc)
+{
+ if (rc == VERR_NO_MEMORY)
+ {
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvramdiskMediaExIoReqWarningOutOfMemory(pThis->pDrvIns);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Syncs the memory buffers between the I/O request allocator and the internal buffer.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to sync.
+ * @param fToIoBuf Flag indicating the sync direction.
+ * true to copy data from the allocators buffer to our internal buffer.
+ * false for the other direction.
+ */
+DECLINLINE(int) drvramdiskMediaExIoReqBufSync(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fToIoBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+
+ /* Make sure the buffer is reset. */
+ RTSgBufReset(&pIoReq->ReadWrite.IoBuf.SgBuf);
+
+ if (fToIoBuf)
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ (uint32_t)(pIoReq->ReadWrite.cbReq - pIoReq->ReadWrite.cbReqLeft),
+ &pIoReq->ReadWrite.IoBuf.SgBuf,
+ RT_MIN(pIoReq->ReadWrite.cbIoBuf, pIoReq->ReadWrite.cbReqLeft));
+ else
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyFromBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ (uint32_t)(pIoReq->ReadWrite.cbReq - pIoReq->ReadWrite.cbReqLeft),
+ &pIoReq->ReadWrite.IoBuf.SgBuf,
+ RT_MIN(pIoReq->ReadWrite.cbIoBuf, pIoReq->ReadWrite.cbReqLeft));
+
+ RTSgBufReset(&pIoReq->ReadWrite.IoBuf.SgBuf);
+ return rc;
+}
+
+/**
+ * Hashes the I/O request ID to an index for the allocated I/O request bin.
+ */
+DECLINLINE(unsigned) drvramdiskMediaExIoReqIdHash(PDMMEDIAEXIOREQID uIoReqId)
+{
+ return uIoReqId % DRVVD_VDIOREQ_ALLOC_BINS; /** @todo Find something better? */
+}
+
+/**
+ * Inserts the given I/O request in to the list of allocated I/O requests.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to insert.
+ */
+static int drvramdiskMediaExIoReqInsert(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(pIoReq->uIoReqId);
+
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for conflicting I/O request ID. */
+ PPDMMEDIAEXIOREQINT pIt;
+ RTListForEach(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, pIt, PDMMEDIAEXIOREQINT, NdAllocatedList)
+ {
+ if (RT_UNLIKELY(pIt->uIoReqId == pIoReq->uIoReqId))
+ {
+ rc = VERR_PDM_MEDIAEX_IOREQID_CONFLICT;
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, &pIoReq->NdAllocatedList);
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * Removes the given I/O request from the list of allocated I/O requests.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to insert.
+ */
+static int drvramdiskMediaExIoReqRemove(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(pIoReq->uIoReqId);
+
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pIoReq->NdAllocatedList);
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * I/O request completion worker.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to complete.
+ * @param rcReq The status code the request completed with.
+ * @param fUpNotify Flag whether to notify the driver/device above us about the completion.
+ */
+static int drvramdiskMediaExIoReqCompleteWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, int rcReq, bool fUpNotify)
+{
+ int rc;
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETING, VDIOREQSTATE_ACTIVE);
+ if (fXchg)
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ else
+ {
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ rcReq = VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETED);
+
+ /*
+ * Leave a release log entry if the request was active for more than 25 seconds
+ * (30 seconds is the timeout of the guest).
+ */
+ uint64_t tsNow = RTTimeMilliTS();
+ if (tsNow - pIoReq->tsSubmit >= 25 * 1000)
+ {
+ const char *pcszReq = NULL;
+
+ switch (pIoReq->enmType)
+ {
+ case PDMMEDIAEXIOREQTYPE_READ:
+ pcszReq = "Read";
+ break;
+ case PDMMEDIAEXIOREQTYPE_WRITE:
+ pcszReq = "Write";
+ break;
+ case PDMMEDIAEXIOREQTYPE_FLUSH:
+ pcszReq = "Flush";
+ break;
+ case PDMMEDIAEXIOREQTYPE_DISCARD:
+ pcszReq = "Discard";
+ break;
+ default:
+ pcszReq = "<Invalid>";
+ }
+
+ LogRel(("RamDisk#%u: %s request was active for %llu seconds\n",
+ pThis->pDrvIns->iInstance, pcszReq, (tsNow - pIoReq->tsSubmit) / 1000));
+ }
+
+ if (RT_FAILURE(rcReq))
+ {
+ /* Log the error. */
+ if (pThis->cErrors++ < 100)
+ {
+ if (rcReq == VERR_PDM_MEDIAEX_IOREQ_CANCELED)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("RamDisk#%u: Aborted flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("RamDisk#%u: Aborted %s (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "read"
+ : "write",
+ pIoReq->ReadWrite.cbReqLeft, rcReq));
+ }
+ else
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("RamDisk#%u: Flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("RamDisk#%u: %s (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "Read"
+ : "Write",
+ pIoReq->ReadWrite.cbReqLeft, rcReq));
+ }
+ }
+ }
+
+ if (fUpNotify)
+ {
+ rc = pThis->pDrvMediaExPort->pfnIoReqCompleteNotify(pThis->pDrvMediaExPort,
+ pIoReq, &pIoReq->abAlloc[0], rcReq);
+ AssertRC(rc);
+ }
+
+ return rcReq;
+}
+
+/**
+ * Allocates a memory buffer suitable for I/O for the given request.
+ *
+ * @returns VBox status code.
+ * @retval VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS if there is no I/O memory available to allocate and
+ * the request was placed on a waiting list.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to allocate memory for.
+ * @param cb Size of the buffer.
+ */
+DECLINLINE(int) drvramdiskMediaExIoReqBufAlloc(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, size_t cb)
+{
+ int rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReq->ReadWrite.IoBuf, cb, &pIoReq->ReadWrite.cbIoBuf);
+ if (rc == VERR_NO_MEMORY)
+ {
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ RTListAppend(&pThis->LstIoReqIoBufWait, &pIoReq->NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ ASMAtomicIncU32(&pThis->cIoReqsWaiting);
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+/**
+ * Worker for a read request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The read request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqReadWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ int rc = drvramdiskReadWorker(pThis, &pIoReq->ReadWrite.IoBuf.SgBuf, pIoReq->ReadWrite.offStart,
+ cbReqIo);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for a read request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The read request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqWriteWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ int rc = drvramdiskWriteWorker(pThis, &pIoReq->ReadWrite.IoBuf.SgBuf, pIoReq->ReadWrite.offStart,
+ cbReqIo);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Processes a read/write request.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ * @param fUpNotify Flag whether to notify the driver/device above us about the completion.
+ */
+static int drvramdiskMediaExIoReqReadWriteProcess(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fUpNotify)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+
+ while ( pIoReq->ReadWrite.cbReqLeft
+ && rc == VINF_SUCCESS)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqReadWorker, 2, pThis, pIoReq);
+ else
+ {
+ /* Sync memory buffer from the request initiator. */
+ rc = drvramdiskMediaExIoReqBufSync(pThis, pIoReq, true /* fToIoBuf */);
+ if (RT_SUCCESS(rc))
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqWriteWorker, 2, pThis, pIoReq);
+ }
+
+ if (rc == VINF_SUCCESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ {
+ Assert(!pIoReq->ReadWrite.cbReqLeft || RT_FAILURE(rc));
+ rc = drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rc, fUpNotify);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Frees a I/O memory buffer allocated previously.
+ *
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request for which to free memory.
+ */
+DECLINLINE(void) drvramdiskMediaExIoReqBufFree(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ IOBUFMgrFreeBuf(&pIoReq->ReadWrite.IoBuf);
+
+ if (ASMAtomicReadU32(&pThis->cIoReqsWaiting) > 0)
+ {
+ /* Try to process as many requests as possible. */
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ PPDMMEDIAEXIOREQINT pIoReqCur, pIoReqNext;
+
+ RTListForEachSafe(&pThis->LstIoReqIoBufWait, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReqCur->ReadWrite.IoBuf, pIoReqCur->ReadWrite.cbReq,
+ &pIoReqCur->ReadWrite.cbIoBuf);
+ if (rc == VINF_SUCCESS)
+ {
+ ASMAtomicDecU32(&pThis->cIoReqsWaiting);
+ RTListNodeRemove(&pIoReqCur->NdLstWait);
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReqCur->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReqCur, VERR_PDM_MEDIAEX_IOREQ_CANCELED, true /* fUpNotify */);
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReqCur, true /* fUpNotify */);
+ }
+ else
+ {
+ Assert(rc == VERR_NO_MEMORY);
+ break;
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ }
+ }
+}
+
+
+/**
+ * Returns whether the VM is in a running state.
+ *
+ * @returns Flag indicating whether the VM is currently in a running state.
+ * @param pThis VBox disk container instance data.
+ */
+DECLINLINE(bool) drvramdiskMediaExIoReqIsVmRunning(PDRVRAMDISK pThis)
+{
+ VMSTATE enmVmState = PDMDrvHlpVMState(pThis->pDrvIns);
+ if ( enmVmState == VMSTATE_RESUMING
+ || enmVmState == VMSTATE_RUNNING
+ || enmVmState == VMSTATE_RUNNING_LS
+ || enmVmState == VMSTATE_RESETTING
+ || enmVmState == VMSTATE_RESETTING_LS
+ || enmVmState == VMSTATE_SOFT_RESETTING
+ || enmVmState == VMSTATE_SOFT_RESETTING_LS
+ || enmVmState == VMSTATE_SUSPENDING
+ || enmVmState == VMSTATE_SUSPENDING_LS
+ || enmVmState == VMSTATE_SUSPENDING_EXT_LS)
+ return true;
+
+ return false;
+}
+
+/**
+ * @copydoc FNVDASYNCTRANSFERCOMPLETE
+ */
+static void drvramdiskMediaExIoReqComplete(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq,
+ int rcReq)
+{
+ /*
+ * For a read we need to sync the memory before continuing to process
+ * the request further.
+ */
+ if ( RT_SUCCESS(rcReq)
+ && pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rcReq = drvramdiskMediaExIoReqBufSync(pThis, pIoReq, false /* fToIoBuf */);
+
+ /*
+ * When the request owner instructs us to handle recoverable errors like full disks
+ * do it. Mark the request as suspended, notify the owner and put the request on the
+ * redo list.
+ */
+ if ( RT_FAILURE(rcReq)
+ && (pIoReq->fFlags & PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR)
+ && drvramdiskMediaExIoReqIsRedoSetWarning(pThis, rcReq))
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_SUSPENDED, VDIOREQSTATE_ACTIVE);
+ if (fXchg)
+ {
+ /* Put on redo list and adjust active request counter. */
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListAppend(&pThis->LstIoReqRedo, &pIoReq->NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_SUSPENDED);
+ }
+ else
+ {
+ /* Request was canceled inbetween, so don't care and notify the owner about the completed request. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rcReq, true /* fUpNotify */);
+ }
+ }
+ else
+ {
+ /* Adjust the remaining amount to transfer. */
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ pIoReq->ReadWrite.offStart += cbReqIo;
+ pIoReq->ReadWrite.cbReqLeft -= cbReqIo;
+
+ if ( RT_FAILURE(rcReq)
+ || !pIoReq->ReadWrite.cbReqLeft
+ || ( pIoReq->enmType != PDMMEDIAEXIOREQTYPE_READ
+ && pIoReq->enmType != PDMMEDIAEXIOREQTYPE_WRITE))
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rcReq, true /* fUpNotify */);
+ else
+ drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, true /* fUpNotify */);
+ }
+}
+
+/**
+ * Worker for a flush request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The flush request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFlushWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ /* Nothing to do for a ram disk. */
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for a discard request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The discard request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqDiscardWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = drvramdiskDiscardRecords(pThis, pIoReq->Discard.paRanges, pIoReq->Discard.cRanges);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures}
+ */
+static DECLCALLBACK(int) drvramdiskQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ RT_NOREF1(pInterface);
+ *pfFeatures = PDMIMEDIAEX_FEATURE_F_ASYNC | PDMIMEDIAEX_FEATURE_F_DISCARD;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnNotifySuspend}
+ */
+static DECLCALLBACK(void) drvramdiskNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ if (RT_UNLIKELY(pThis->hIoReqCache != NIL_RTMEMCACHE))
+ return VERR_INVALID_STATE;
+
+ return RTMemCacheCreate(&pThis->hIoReqCache, sizeof(PDMMEDIAEXIOREQINT) + cbIoReqAlloc, 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAlloc}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!(fFlags & ~PDMIMEDIAEX_F_VALID), VERR_INVALID_PARAMETER);
+
+ PPDMMEDIAEXIOREQINT pIoReq = (PPDMMEDIAEXIOREQINT)RTMemCacheAlloc(pThis->hIoReqCache);
+
+ if (RT_UNLIKELY(!pIoReq))
+ return VERR_NO_MEMORY;
+
+ pIoReq->uIoReqId = uIoReqId;
+ pIoReq->fFlags = fFlags;
+ pIoReq->pDisk = pThis;
+ pIoReq->enmState = VDIOREQSTATE_ALLOCATED;
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_INVALID;
+
+ int rc = drvramdiskMediaExIoReqInsert(pThis, pIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ *phIoReq = pIoReq;
+ *ppvIoReqAlloc = &pIoReq->abAlloc[0];
+ }
+ else
+ RTMemCacheFree(pThis->hIoReqCache, pIoReq);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFree}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if ( pIoReq->enmState != VDIOREQSTATE_COMPLETED
+ && pIoReq->enmState != VDIOREQSTATE_ALLOCATED)
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ /* Remove from allocated list. */
+ int rc = drvramdiskMediaExIoReqRemove(pThis, pIoReq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Free any associated I/O memory. */
+ drvramdiskMediaExIoReqBufFree(pThis, pIoReq);
+
+ /* For discard request discard the range array. */
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD
+ && pIoReq->Discard.paRanges)
+ {
+ RTMemFree(pIoReq->Discard.paRanges);
+ pIoReq->Discard.paRanges = NULL;
+ }
+
+ pIoReq->enmState = VDIOREQSTATE_FREE;
+ RTMemCacheFree(pThis->hIoReqCache, pIoReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryResidual}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ RT_NOREF2(pInterface, hIoReq);
+
+ *pcbResidual = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ RT_NOREF1(pInterface);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ *pcbXfer = pIoReq->ReadWrite.cbReq;
+ else
+ *pcbXfer = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return VINF_SUCCESS; /** @todo */
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(uIoReqId);
+
+ int rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for I/O request with ID. */
+ PPDMMEDIAEXIOREQINT pIt;
+ rc = VERR_PDM_MEDIAEX_IOREQID_NOT_FOUND;
+
+ RTListForEach(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, pIt, PDMMEDIAEXIOREQINT, NdAllocatedList)
+ {
+ if (pIt->uIoReqId == uIoReqId)
+ {
+ bool fXchg = true;
+ VDIOREQSTATE enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIt->enmState);
+
+ /*
+ * We might have to try canceling the request multiple times if it transitioned from
+ * ALLOCATED to ACTIVE or to SUSPENDED between reading the state and trying to change it.
+ */
+ while ( ( enmStateOld == VDIOREQSTATE_ALLOCATED
+ || enmStateOld == VDIOREQSTATE_ACTIVE
+ || enmStateOld == VDIOREQSTATE_SUSPENDED)
+ && !fXchg)
+ {
+ fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIt->enmState, VDIOREQSTATE_CANCELED, enmStateOld);
+ if (!fXchg)
+ enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIt->enmState);
+ }
+
+ if (fXchg)
+ {
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_READ;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ pIoReq->ReadWrite.offStart = off;
+ pIoReq->ReadWrite.cbReq = cbRead;
+ pIoReq->ReadWrite.cbReqLeft = cbRead;
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = drvramdiskMediaExIoReqBufAlloc(pThis, pIoReq, cbRead);
+ if (rc == VINF_SUCCESS)
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_WRITE;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ pIoReq->ReadWrite.offStart = off;
+ pIoReq->ReadWrite.cbReq = cbWrite;
+ pIoReq->ReadWrite.cbReqLeft = cbWrite;
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = drvramdiskMediaExIoReqBufAlloc(pThis, pIoReq, cbWrite);
+ if (rc == VINF_SUCCESS)
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_FLUSH;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ return RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqFlushWorker, 2, pThis, pIoReq);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ /* Copy the ranges over now, this can be optimized in the future. */
+ pIoReq->Discard.paRanges = (PRTRANGE)RTMemAllocZ(cRangesMax * sizeof(RTRANGE));
+ if (RT_UNLIKELY(!pIoReq->Discard.paRanges))
+ return VERR_NO_MEMORY;
+
+ int rc = pThis->pDrvMediaExPort->pfnIoReqQueryDiscardRanges(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ 0, cRangesMax, pIoReq->Discard.paRanges,
+ &pIoReq->Discard.cRanges);
+ if (RT_SUCCESS(rc))
+ {
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_DISCARD;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqDiscardWorker, 2, pThis, pIoReq);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount}
+ */
+static DECLCALLBACK(uint32_t) drvramdiskIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ return ASMAtomicReadU32(&pThis->cIoReqsActive);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount}
+ */
+static DECLCALLBACK(uint32_t) drvramdiskIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), 0);
+
+ uint32_t cIoReqSuspended = 0;
+ PPDMMEDIAEXIOREQINT pIoReq;
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListForEach(&pThis->LstIoReqRedo, pIoReq, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ cIoReqSuspended++;
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return cIoReqSuspended;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq,
+ void **ppvIoReqAlloc)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertReturn(!RTListIsEmpty(&pThis->LstIoReqRedo), VERR_NOT_FOUND);
+
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ PPDMMEDIAEXIOREQINT pIoReq = RTListGetFirst(&pThis->LstIoReqRedo, PDMMEDIAEXIOREQINT, NdLstWait);
+ *phIoReq = pIoReq;
+ *ppvIoReqAlloc = &pIoReq->abAlloc[0];
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(!RTListNodeIsLast(&pThis->LstIoReqRedo, &pIoReq->NdLstWait), VERR_NOT_FOUND);
+
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ PPDMMEDIAEXIOREQINT pIoReqNext = RTListNodeGetNext(&pIoReq->NdLstWait, PDMMEDIAEXIOREQINT, NdLstWait);
+ *phIoReqNext = pIoReqNext;
+ *ppvIoReqAllocNext = &pIoReqNext->abAlloc[0];
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ RT_NOREF1(pSSM);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pIoReq->enmState == VDIOREQSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ RT_NOREF1(pSSM);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pIoReq->enmState == VDIOREQSTATE_ALLOCATED, VERR_INVALID_STATE);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) drvramdiskIoReqWorker(RTTHREAD hThrdSelf, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PDRVRAMDISK pThis = (PDRVRAMDISK)pvUser;
+
+ RT_NOREF1(hThrdSelf);
+
+ do
+ {
+ rc = RTReqQueueProcess(pThis->hReqQ, RT_INDEFINITE_WAIT);
+ } while (RT_SUCCESS(rc));
+
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvramdiskQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, &pThis->IMedia);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, &pThis->IMediaEx);
+
+ return NULL;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+static DECLCALLBACK(int) drvramdiskTreeDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
+{
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)pNode;
+
+ RT_NOREF1(pvUser);
+
+ RTMemFree(pSeg->pbSeg);
+ RTMemFree(pSeg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvramdiskDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+
+ if (pThis->pTreeSegments)
+ {
+ RTAvlrFileOffsetDestroy(pThis->pTreeSegments, drvramdiskTreeDestroy, NULL);
+ RTMemFree(pThis->pTreeSegments);
+ }
+ RTReqQueueDestroy(pThis->hReqQ);
+}
+
+/**
+ * Construct a disk integrity driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvramdiskConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvdiskintConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvramdiskQueryInterface;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvramdiskRead;
+ pThis->IMedia.pfnWrite = drvramdiskWrite;
+ pThis->IMedia.pfnFlush = drvramdiskFlush;
+ pThis->IMedia.pfnGetSize = drvramdiskGetSize;
+ pThis->IMedia.pfnBiosIsVisible = drvramdiskBiosIsVisible;
+ pThis->IMedia.pfnGetType = drvramdiskGetType;
+ pThis->IMedia.pfnIsReadOnly = drvramdiskIsReadOnly;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvramdiskBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvramdiskBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvramdiskBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvramdiskBiosSetLCHSGeometry;
+ pThis->IMedia.pfnGetUuid = drvramdiskGetUuid;
+ pThis->IMedia.pfnGetSectorSize = drvramdiskGetSectorSize;
+ pThis->IMedia.pfnReadPcBios = drvramdiskReadPcBios;
+ pThis->IMedia.pfnDiscard = drvramdiskDiscard;
+ pThis->IMedia.pfnIsNonRotational = drvramdiskIsNonRotational;
+
+ /* IMediaEx */
+ pThis->IMediaEx.pfnQueryFeatures = drvramdiskQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvramdiskNotifySuspend;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvramdiskIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvramdiskIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvramdiskIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvramdiskIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvramdiskIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvramdiskIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvramdiskIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvramdiskIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvramdiskIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvramdiskIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvramdiskIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvramdiskIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvramdiskIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvramdiskIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvramdiskIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvramdiskIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvramdiskIoReqSuspendedLoad;
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Size"
+ "|PreAlloc"
+ "|IoBufMax"
+ "|SectorSize"
+ "|NonRotational",
+ "");
+
+ int rc = pHlp->pfnCFGMQueryU64(pCfg, "Size", &pThis->cbDisk);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying the media size"));
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "PreAlloc", &pThis->fPreallocRamDisk, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"PreAlloc\""));
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "NonRotational", &pThis->fNonRotational, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"NonRotational\""));
+
+ uint32_t cbIoBufMax;
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "IoBufMax", &cbIoBufMax, 5 * _1M);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IoBufMax\" from the config"));
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "SectorSize", &pThis->cbSector, 512);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"SectorSize\" from the config"));
+
+ /* Query the media port interface above us. */
+ pThis->pDrvMediaPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAPORT);
+ if (!pThis->pDrvMediaPort)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("No media port interface above"));
+
+ /* Try to attach extended media port interface above.*/
+ pThis->pDrvMediaExPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAEXPORT);
+ if (pThis->pDrvMediaExPort)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIoReqAllocBins); i++)
+ {
+ rc = RTSemFastMutexCreate(&pThis->aIoReqAllocBins[i].hMtxLstIoReqAlloc);
+ if (RT_FAILURE(rc))
+ break;
+ RTListInit(&pThis->aIoReqAllocBins[i].LstIoReqAlloc);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSectIoReqsIoBufWait);
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSectIoReqRedo);
+
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Creating Mutex failed"));
+
+ RTListInit(&pThis->LstIoReqIoBufWait);
+ RTListInit(&pThis->LstIoReqRedo);
+ }
+
+ /* Create the AVL tree. */
+ pThis->pTreeSegments = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
+ if (!pThis->pTreeSegments)
+ rc = VERR_NO_MEMORY;
+
+ if (pThis->pDrvMediaExPort)
+ {
+ rc = RTReqQueueCreate(&pThis->hReqQ);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the worker thread. */
+ rc = RTThreadCreate(&pThis->hThrdWrk, drvramdiskIoReqWorker, pThis, 0,
+ RTTHREADTYPE_IO, 0, "RAMDSK");
+ }
+ }
+
+ if (pThis->pDrvMediaExPort)
+ rc = IOBUFMgrCreate(&pThis->hIoBufMgr, cbIoBufMax, IOBUFMGR_F_DEFAULT);
+
+ /* Read in all data before the start if requested. */
+ if ( RT_SUCCESS(rc)
+ && pThis->fPreallocRamDisk)
+ {
+ LogRel(("RamDisk: Preallocating RAM disk...\n"));
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvRamDisk =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "RamDisk",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "RAM disk driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVRAMDISK),
+ /* pfnConstruct */
+ drvramdiskConstruct,
+ /* pfnDestruct */
+ drvramdiskDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Storage/DrvSCSI.cpp b/src/VBox/Devices/Storage/DrvSCSI.cpp
new file mode 100644
index 00000000..e2364895
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvSCSI.cpp
@@ -0,0 +1,1583 @@
+/* $Id: DrvSCSI.cpp $ */
+/** @file
+ * VBox storage drivers: Generic SCSI command parser and execution driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+//#define 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;
+ /** Where to store the amount of sense data written, optional. */
+ size_t *pcbSense;
+ /** Where to store the transfer direction determined by the VSCSI layer, optional. */
+ PDMMEDIAEXIOREQSCSITXDIR *penmXferDir;
+ /** 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. */
+ PDMQUEUEHANDLE hQueue;
+} 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;
+}
+
+
+/**
+ * Converts the given VSCSI transfer direction enum to the appropriate PDM extended media interface one.
+ *
+ * @returns The PDM extended media interface transfer direction.
+ * @param enmVScsiXferDir The VSCSI transfer direction.
+ */
+static PDMMEDIAEXIOREQSCSITXDIR drvscsiVScsiXferDir2PdmMediaExDir(VSCSIXFERDIR enmVScsiXferDir)
+{
+ switch (enmVScsiXferDir)
+ {
+ case VSCSIXFERDIR_UNKNOWN: return PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN;
+ case VSCSIXFERDIR_T2I: return PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+ case VSCSIXFERDIR_I2T: return PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE;
+ case VSCSIXFERDIR_NONE: return PDMMEDIAEXIOREQSCSITXDIR_NONE;
+ default: return PDMMEDIAEXIOREQSCSITXDIR_INVALID;
+ }
+
+ /*return PDMMEDIAEXIOREQSCSITXDIR_INVALID;*/
+}
+
+
+/* -=-=-=-=- 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)PDMDrvHlpQueueAlloc(pThis->pDrvIns, pThis->hQueue);
+ if (pEjectState)
+ {
+ pEjectState->hSemEvt = hSemEvt;
+ PDMDrvHlpQueueInsert(pThis->pDrvIns, pThis->hQueue, &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);
+
+ /** @todo Don't crash if someone screws this up... Recreated a VISO while it
+ * was mounted and asked the GUI to use it. Got forced umount question.
+ * Said yes. Ended up here with a NULL pointer. */
+ PPDMIMEDIAEX pDrvMediaEx = pThis->pDrvMediaEx;
+ if (pDrvMediaEx)
+ pDrvMediaEx->pfnNotifySuspend(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, PDMMEDIAEXIOREQSCSITXDIR *penmTxDirRet,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, size_t *pcbSenseRet,
+ 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;
+ pReq->pcbSense = pcbSenseRet;
+ pReq->penmXferDir = penmTxDirRet;
+
+ /* 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, VSCSIXFERDIR enmXferDir, size_t cbSense)
+{
+ 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;
+ if (pReq->pcbSense)
+ *pReq->pcbSense = cbSense;
+ if (pReq->penmXferDir)
+ *pReq->penmXferDir = drvscsiVScsiXferDir2PdmMediaExDir(enmXferDir);
+ 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.
+ * @thread EMT
+ *
+ * @todo r=bird: Seems the idea here is that we have to do this on an EMT,
+ * probably because of PDMIMOUNT::pfnUnmount. I don't quite get why
+ * though, as EMT doesn't exactly serialize anything anymore (SMP)...
+ */
+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(RT_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(RT_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)
+{
+ RT_NOREF(pCfg);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
+ LogFlowFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
+
+ /*
+ * 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. */
+ int 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.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+
+ /*
+ * Try attach driver below and query it's media interface.
+ */
+ int 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(RT_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(RT_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->hQueue);
+ 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..7dab18fb
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvVD.cpp
@@ -0,0 +1,5613 @@
+/* $Id: DrvVD.cpp $ */
+/** @file
+ * DrvVD - Generic VBox disk media driver.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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/semaphore.h>
+#include <iprt/sg.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 instance for host mode. */
+ VDIFINST hVdIfTcpNet;
+ /** TCP network stack interface (for INIP). */
+ VDINTERFACETCPNET VDIfTcpNet;
+ /** I/O interface. */
+ VDINTERFACEIO VDIfIo;
+} VBOXIMAGE, *PVBOXIMAGE;
+
+/**
+ * Storage backend data.
+ */
+typedef struct DRVVDSTORAGEBACKEND
+{
+ /** The virtual disk driver instance. */
+ PVBOXDISK pVD;
+ /** 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
+
+/**
+ * VD config node.
+ */
+typedef struct VDCFGNODE
+{
+ /** List node for the list of config nodes. */
+ RTLISTNODE NdLst;
+ /** Pointer to the driver helper callbacks. */
+ PCPDMDRVHLPR3 pHlp;
+ /** The config node. */
+ PCFGMNODE pCfgNode;
+} VDCFGNODE;
+/** Pointer to a VD config node. */
+typedef VDCFGNODE *PVDCFGNODE;
+
+/**
+ * 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;
+
+ /** VD config support.
+ * @{ */
+ /** List head of config nodes. */
+ RTLISTANCHOR LstCfgNodes;
+ /** @} */
+
+ /** Cryptographic support
+ * @{ */
+ /** Pointer to the CFGM node containing the config of the crypto filter
+ * if enable. */
+ VDCFGNODE CfgCrypto;
+ /** 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;
+ if (p->hVdIfTcpNet != NULL)
+ VDIfTcpNetInstDefaultDestroy(p->hVdIfTcpNet);
+ 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->pVD = pThis;
+ 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 = PDMDrvHlpAsyncCompletionEpCreateForFile(pThis->pDrvIns,
+ &pStorageBackend->pEndpoint,
+ pszLocation, fFlags,
+ pStorageBackend->pTemplate);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pszBwGroup)
+ rc = PDMDrvHlpAsyncCompletionEpSetBwMgr(pThis->pDrvIns, 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;
+ }
+
+ PDMDrvHlpAsyncCompletionEpClose(pThis->pDrvIns, pStorageBackend->pEndpoint);
+ }
+
+ PDMDrvHlpAsyncCompletionTemplateDestroy(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ /*
+ * 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.
+ */
+
+ PDMDrvHlpAsyncCompletionEpClose(pThis->pDrvIns, pStorageBackend->pEndpoint);
+ PDMDrvHlpAsyncCompletionTemplateDestroy(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+ RTSGSEG DataSeg;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+ DataSeg.cbSeg = cbRead;
+ DataSeg.pvSeg = pvBuf;
+
+ int rc = PDMDrvHlpAsyncCompletionEpRead(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+ RTSGSEG DataSeg;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+ DataSeg.cbSeg = cbWrite;
+ DataSeg.pvSeg = (void *)pvBuf;
+
+ int rc = PDMDrvHlpAsyncCompletionEpWrite(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+ PPDMASYNCCOMPLETIONTASK pTask;
+
+ LogFlowFunc(("pvUser=%#p pStorage=%#p\n", pvUser, pStorage));
+
+ bool fOld = ASMAtomicXchgBool(&pStorageBackend->fSyncIoPending, true);
+ Assert(!fOld); NOREF(fOld);
+
+ int rc = PDMDrvHlpAsyncCompletionEpFlush(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ int rc = PDMDrvHlpAsyncCompletionEpRead(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ int rc = PDMDrvHlpAsyncCompletionEpWrite(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ int rc = PDMDrvHlpAsyncCompletionEpFlush(pThis->pDrvIns, 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;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ return PDMDrvHlpAsyncCompletionEpGetSize(pThis->pDrvIns, pStorageBackend->pEndpoint, pcbSize);
+}
+
+static DECLCALLBACK(int) drvvdAsyncIOSetSize(void *pvUser, void *pStorage, uint64_t cbSize)
+{
+ RT_NOREF(pvUser);
+ PDRVVDSTORAGEBACKEND pStorageBackend = (PDRVVDSTORAGEBACKEND)pStorage;
+ PVBOXDISK pThis = pStorageBackend->pVD;
+
+ return PDMDrvHlpAsyncCompletionEpSetSize(pThis->pDrvIns, 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)
+{
+ PVDCFGNODE pVdCfgNode = (PVDCFGNODE)pvUser;
+ PCPDMDRVHLPR3 pHlp = pVdCfgNode->pHlp;
+ return pHlp->pfnCFGMAreValuesValid(pVdCfgNode->pCfgNode, pszzValid);
+}
+
+static DECLCALLBACK(int) drvvdCfgQuerySize(void *pvUser, const char *pszName, size_t *pcb)
+{
+ PVDCFGNODE pVdCfgNode = (PVDCFGNODE)pvUser;
+ PCPDMDRVHLPR3 pHlp = pVdCfgNode->pHlp;
+ return pHlp->pfnCFGMQuerySize(pVdCfgNode->pCfgNode, pszName, pcb);
+}
+
+static DECLCALLBACK(int) drvvdCfgQuery(void *pvUser, const char *pszName, char *pszString, size_t cchString)
+{
+ PVDCFGNODE pVdCfgNode = (PVDCFGNODE)pvUser;
+ PCPDMDRVHLPR3 pHlp = pVdCfgNode->pHlp;
+ return pHlp->pfnCFGMQueryString(pVdCfgNode->pCfgNode, pszName, pszString, cchString);
+}
+
+static DECLCALLBACK(int) drvvdCfgQueryBytes(void *pvUser, const char *pszName, void *ppvData, size_t cbData)
+{
+ PVDCFGNODE pVdCfgNode = (PVDCFGNODE)pvUser;
+ PCPDMDRVHLPR3 pHlp = pVdCfgNode->pHlp;
+ return pHlp->pfnCFGMQueryBytes(pVdCfgNode->pCfgNode, 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 */
+
+
+/**
+ * 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->CfgCrypto.pCfgNode
+ && !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->CfgCrypto.pCfgNode
+ && !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;
+ }
+
+ 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->CfgCrypto.pCfgNode)
+ {
+ 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->CfgCrypto, 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);
+ PDMDrvHlpBlkCacheIoXferComplete(pThis->pDrvIns, 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->CfgCrypto.pCfgNode);
+
+ 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)
+ PDMDrvHlpBlkCacheIoXferComplete(pThis->pDrvIns, pThis->pBlkCache, hIoXfer, VINF_SUCCESS);
+ else if (RT_FAILURE(rc) && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ PDMDrvHlpBlkCacheIoXferComplete(pThis->pDrvIns, 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)
+ PDMDrvHlpBlkCacheIoXferComplete(pThis->pDrvIns, pThis->pBlkCache, hIoXfer, VINF_SUCCESS);
+ else if (RT_FAILURE(rc) && rc != VERR_VD_ASYNC_IO_IN_PROGRESS)
+ PDMDrvHlpBlkCacheIoXferComplete(pThis->pDrvIns, 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 drvvdMediaExIoReqWarningFileStale(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("VD#%u: File handle became stale\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvVD_ISCSIDOWN",
+ N_("The file became stale (often due to a restarted NFS server). 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_STALE_FILE_HANDLE)
+ {
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvvdMediaExIoReqWarningFileStale(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.
+ *
+ * @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 = PDMDrvHlpBlkCacheRead(pThis->pDrvIns, 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 && RT_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 = PDMDrvHlpBlkCacheWrite(pThis->pDrvIns, 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 && RT_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 = PDMDrvHlpBlkCacheFlush(pThis->pDrvIns, 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 = PDMDrvHlpBlkCacheDiscard(pThis->pDrvIns, 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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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_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, PDMMEDIAEXIOREQSCSITXDIR *penmTxDirRet,
+ size_t cbBuf, uint8_t *pabSense, size_t cbSense, size_t *pcbSenseRet,
+ uint8_t *pu8ScsiSts, uint32_t cTimeoutMillies)
+{
+ RT_NOREF12(pInterface, uLun, pbCdb, cbCdb, enmTxDir, penmTxDirRet, cbBuf, pabSense, cbSense, pcbSenseRet, 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);
+ PCPDMDRVHLPR3 pHlp = pThis->pDrvIns->pHlpR3;
+ 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);
+
+ pHlp->pfnSSMPutU32(pSSM, DRVVD_IOREQ_SAVED_STATE_VERSION);
+ pHlp->pfnSSMPutU32(pSSM, (uint32_t)pIoReq->enmType);
+ pHlp->pfnSSMPutU32(pSSM, pIoReq->uIoReqId);
+ pHlp->pfnSSMPutU32(pSSM, pIoReq->fFlags);
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ pHlp->pfnSSMPutU64(pSSM, pIoReq->ReadWrite.offStart);
+ pHlp->pfnSSMPutU64(pSSM, pIoReq->ReadWrite.cbReq);
+ pHlp->pfnSSMPutU64(pSSM, pIoReq->ReadWrite.cbReqLeft);
+ }
+ else if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD)
+ {
+ pHlp->pfnSSMPutU32(pSSM, pIoReq->Discard.cRanges);
+ for (unsigned i = 0; i < pIoReq->Discard.cRanges; i++)
+ {
+ pHlp->pfnSSMPutU64(pSSM, pIoReq->Discard.paRanges[i].offStart);
+ pHlp->pfnSSMPutU64(pSSM, pIoReq->Discard.paRanges[i].cbRange);
+ }
+ }
+
+ return pHlp->pfnSSMPutU32(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);
+ PCPDMDRVHLPR3 pHlp = pThis->pDrvIns->pHlpR3;
+ 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;
+
+ pHlp->pfnSSMGetU32(pSSM, &u32);
+ if (u32 <= DRVVD_IOREQ_SAVED_STATE_VERSION)
+ {
+ pHlp->pfnSSMGetU32(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;
+
+ pHlp->pfnSSMGetU32(pSSM, &u32);
+ AssertReturn(u32 == pIoReq->uIoReqId, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ pHlp->pfnSSMGetU32(pSSM, &u32);
+ AssertReturn(u32 == pIoReq->fFlags, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ pHlp->pfnSSMGetU64(pSSM, &pIoReq->ReadWrite.offStart);
+ pHlp->pfnSSMGetU64(pSSM, &u64);
+ pIoReq->ReadWrite.cbReq = (size_t)u64;
+ pHlp->pfnSSMGetU64(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 = pHlp->pfnSSMGetU32(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++)
+ {
+ pHlp->pfnSSMGetU64(pSSM, &pIoReq->Discard.paRanges[i].offStart);
+ pHlp->pfnSSMGetU64(pSSM, &u64);
+ pIoReq->Discard.paRanges[i].cbRange = (size_t)u64;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = pHlp->pfnSSMGetU32(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 pDrvIns Driver instance data.
+ * @param pCfg CFGM node holding plugin list.
+ */
+static int drvvdLoadPlugins(PPDMDRVINS pDrvIns, PCFGMNODE pCfg)
+{
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ PCFGMNODE pCfgPlugins = pHlp->pfnCFGMGetChild(pCfg, "Plugins");
+
+ if (pCfgPlugins)
+ {
+ PCFGMNODE pPluginCur = pHlp->pfnCFGMGetFirstChild(pCfgPlugins);
+ while (pPluginCur)
+ {
+ int rc = VINF_SUCCESS;
+ char *pszPluginFilename = NULL;
+ rc = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMGetNextChild(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)
+{
+ PCPDMDRVHLPR3 pHlp = pThis->pDrvIns->pHlpR3;
+ int rc = VINF_SUCCESS;
+
+ PCFGMNODE pCfgFilter = pHlp->pfnCFGMGetChild(pCfg, "Filters");
+ if (pCfgFilter)
+ {
+ PCFGMNODE pCfgFilterConfig = pHlp->pfnCFGMGetChild(pCfgFilter, "VDConfig");
+ char *pszFilterName = NULL;
+ VDINTERFACECONFIG VDIfConfig;
+ PVDINTERFACE pVDIfsFilter = NULL;
+
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfgFilter, "FilterName", &pszFilterName);
+ if (RT_SUCCESS(rc))
+ {
+ VDCFGNODE CfgNode;
+
+ VDIfConfig.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ VDIfConfig.pfnQuerySize = drvvdCfgQuerySize;
+ VDIfConfig.pfnQuery = drvvdCfgQuery;
+ VDIfConfig.pfnQueryBytes = drvvdCfgQueryBytes;
+
+ CfgNode.pHlp = pThis->pDrvIns->pHlpR3;
+ CfgNode.pCfgNode = pCfgFilterConfig;
+ rc = VDInterfaceAdd(&VDIfConfig.Core, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ &CfgNode, sizeof(VDINTERFACECONFIG), &pVDIfsFilter);
+ AssertRC(rc);
+
+ rc = VDFilterAdd(pThis->pDisk, pszFilterName, VD_FILTER_FLAGS_DEFAULT, pVDIfsFilter);
+
+ PDMDrvHlpMMHeapFree(pThis->pDrvIns, 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;
+
+ /*
+ * Figure out where to place the stats.
+ */
+ uint32_t iInstance = 0;
+ uint32_t iLUN = 0;
+ const char *pcszController = NULL;
+ int rc = pThis->pDrvMediaPort->pfnQueryDeviceLocation(pThis->pDrvMediaPort, &pcszController, &iInstance, &iLUN);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Compose the prefix for the statistics to reduce the amount of repetition below.
+ * The /Public/ bits are official and used by session info in the GUI.
+ */
+ char szCtrlUpper[32];
+ rc = RTStrCopy(szCtrlUpper, sizeof(szCtrlUpper), pcszController);
+ AssertRCReturn(rc, rc);
+
+ RTStrToUpper(szCtrlUpper);
+ char szPrefix[128];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "/Public/Storage/%s%u/Port%u", szCtrlUpper, iInstance, iLUN);
+
+ /*
+ * Do the registrations.
+ */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatQueryBufAttempts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT,
+ "Number of attempts to query a direct buffer.", "%s/QueryBufAttempts", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatQueryBufSuccess, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT,
+ "Number of succeeded attempts to query a direct buffer.", "%s/QueryBufSuccess", szPrefix);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data read.", "%s/BytesRead", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Amount of data written.", "%s/BytesWritten", szPrefix);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests submitted.", "%s/ReqsSubmitted", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests failed.", "%s/ReqsFailed", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsSucceeded, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of I/O requests succeeded.", "%s/ReqsSucceeded", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsFlush, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of flush I/O requests submitted.", "%s/ReqsFlush", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsWrite, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of write I/O requests submitted.", "%s/ReqsWrite", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of read I/O requests submitted.", "%s/ReqsRead", szPrefix);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsDiscard, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT,
+ "Number of discard I/O requests submitted.", "%s/ReqsDiscard", szPrefix);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReqsPerSec, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of processed I/O requests per second.", "%s/ReqsPerSec", szPrefix);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Deregisters statistics associated with the given media driver.
+ *
+ * @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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ 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(pHlp->pfnSSMHandleGetStatus(pSSM)))
+ return VINF_SUCCESS;
+
+ int rc = drvvdSetWritable(pThis);
+ if (RT_FAILURE(rc)) /** @todo does the bugger set any errors? */
+ return pHlp->pfnSSMSetLoadError(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.
+ *
+ * @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))
+ {
+ PDMDrvHlpBlkCacheRelease(pThis->pDrvIns, 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 = PDMDrvHlpBlkCacheResume(pThis->pDrvIns, 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 = PDMDrvHlpBlkCacheSuspend(pThis->pDrvIns, 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 = PDMDrvHlpBlkCacheClear(pThis->pDrvIns, pThis->pBlkCache);
+ AssertRC(rc);
+ }
+
+ if (pThis->fBootAccelEnabled)
+ {
+ pThis->fBootAccelActive = true;
+ pThis->cbDataValid = 0;
+ pThis->offDisk = 0;
+ }
+ pThis->fLocked = false;
+}
+
+/**
+ * @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)
+ {
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+
+ PVDCFGNODE pIt;
+ PVDCFGNODE pItNext;
+ RTListForEachSafe(&pThis->LstCfgNodes, pIt, pItNext, VDCFGNODE, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ RTMemFreeZ(pIt, sizeof(*pIt));
+ }
+}
+
+/**
+ * @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);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PVBOXDISK pThis = PDMINS_2_DATA(pDrvIns, PVBOXDISK);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlowFunc(("\n"));
+
+ 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->CfgCrypto.pCfgNode = NULL;
+ pThis->CfgCrypto.pHlp = pDrvIns->pHlpR3;
+ 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;
+
+ RTListInit(&pThis->LstCfgNodes);
+
+ /* Initialize supported VD interfaces. */
+ pThis->pVDIfsDisk = NULL;
+
+ pThis->VDIfError.pfnError = drvvdErrorCallback;
+ pThis->VDIfError.pfnMessage = NULL;
+ int 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(pDrvIns, 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 = pHlp->pfnCFGMAreValuesValid(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 = pHlp->pfnCFGMAreValuesValid(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryStringAlloc(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);
+ PDMDrvHlpMMHeapFree(pDrvIns, psz);
+ return VERR_PDM_BLOCK_UNKNOWN_TYPE;
+ }
+ PDMDrvHlpMMHeapFree(pDrvIns, psz); psz = NULL;
+
+ rc = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryStringAlloc(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);
+ PDMDrvHlpMMHeapFree(pDrvIns, psz);
+ return rc;
+ }
+ PDMDrvHlpMMHeapFree(pDrvIns, psz); psz = NULL;
+ }
+ else
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Uuid\" from the config"));
+
+#ifdef VBOX_PERIODIC_FLUSH
+ rc = pHlp->pfnCFGMQueryU32Def(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU32Def(pCfg, "IoBufMax", &cbIoBufMax, 5 * _1M);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IoBufMax\" from the config"));
+
+ rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMGetChild(pCurNode, "Parent");
+ if (!pParent)
+ break;
+ pCurNode = pParent;
+ iLevel++;
+ }
+
+ if (pThis->pDrvMediaExPort)
+ rc = IOBUFMgrCreate(&pThis->hIoBufMgr, cbIoBufMax, pThis->CfgCrypto.pCfgNode ? 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 = pHlp->pfnCFGMQueryStringAlloc(pCurNode, "Path", &pszName);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("DrvVD: Configuration error: Querying \"Path\" as string failed"));
+ break;
+ }
+
+ rc = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMGetChild(pCurNode, "VDConfig");
+ pImage->VDIfConfig.pfnAreKeysValid = drvvdCfgAreKeysValid;
+ pImage->VDIfConfig.pfnQuerySize = drvvdCfgQuerySize;
+ pImage->VDIfConfig.pfnQuery = drvvdCfgQuery;
+ pImage->VDIfConfig.pfnQueryBytes = NULL;
+
+ PVDCFGNODE pCfgNode = (PVDCFGNODE)RTMemAllocZ(sizeof(*pCfgNode));
+ if (RT_UNLIKELY(!pCfgNode))
+ {
+ rc = PDMDRV_SET_ERROR(pDrvIns, VERR_NO_MEMORY,
+ N_("DrvVD: Failed to allocate memory for config node"));
+ break;
+ }
+
+ pCfgNode->pHlp = pDrvIns->pHlpR3;
+ pCfgNode->pCfgNode = pCfgVDConfig;
+ RTListAppend(&pThis->LstCfgNodes, &pCfgNode->NdLst);
+
+ rc = VDInterfaceAdd(&pImage->VDIfConfig.Core, "DrvVD_Config", VDINTERFACETYPE_CONFIG,
+ pCfgNode, sizeof(VDINTERFACECONFIG), &pImage->pVDIfsImage);
+ AssertRC(rc);
+
+ /* Check VDConfig for encryption config. */
+ /** @todo This makes sure that the crypto config is not cleared accidentally
+ * when it was set because there are multiple VDConfig entries for a snapshot chain
+ * but only one contains the crypto config.
+ *
+ * This needs to be properly fixed by specifying which part of the image should contain the
+ * crypto stuff.
+ */
+ if (!pThis->CfgCrypto.pCfgNode)
+ {
+ if (pCfgVDConfig)
+ pThis->CfgCrypto.pCfgNode = pHlp->pfnCFGMGetChild(pCfgVDConfig, "CRYPT");
+
+ if (pThis->CfgCrypto.pCfgNode)
+ {
+ /* 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)
+ rc = VDIfTcpNetInstDefaultCreate(&pImage->hVdIfTcpNet, &pImage->pVDIfsImage);
+ 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;
+
+ rc = VDInterfaceAdd(&pImage->VDIfTcpNet.Core, "DrvVD_TCPNET",
+ VDINTERFACETYPE_TCPNET, NULL,
+ sizeof(VDINTERFACETCPNET), &pImage->pVDIfsImage);
+ AssertRC(rc);
+#endif /* VBOX_WITH_INIP */
+ }
+
+ /* 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;
+ }
+
+ PDMDrvHlpMMHeapFree(pDrvIns, pszName);
+ pszName = NULL;
+ PDMDrvHlpMMHeapFree(pDrvIns, pszFormat);
+ pszFormat = NULL;
+
+ /* next */
+ iLevel--;
+ iImageIdx++;
+ pCurNode = pHlp->pfnCFGMGetParent(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))
+ PDMDrvHlpMMHeapFree(pDrvIns, pszCachePath);
+ if (RT_VALID_PTR(pszCacheFormat))
+ PDMDrvHlpMMHeapFree(pDrvIns, 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->CfgCrypto.pCfgNode /* 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))
+ PDMDrvHlpMMHeapFree(pDrvIns, pszName);
+ if (RT_VALID_PTR(pszFormat))
+ PDMDrvHlpMMHeapFree(pDrvIns, 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..d1c0ded6
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-darwin.cpp
@@ -0,0 +1,542 @@
+/* $Id: HBDMgmt-darwin.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API - darwin specifics.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_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.
+ *
+ * @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.
+ *
+ * @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..ddff18d0
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-generic.cpp
@@ -0,0 +1,70 @@
+/* $Id: HBDMgmt-generic.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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..4fa44217
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-win.cpp
@@ -0,0 +1,572 @@
+/* $Id: HBDMgmt-win.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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.
+ *
+ * @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..75317dcf
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt.h
@@ -0,0 +1,103 @@
+/* $Id: HBDMgmt.h $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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.
+ *
+ * @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..808141da
--- /dev/null
+++ b/src/VBox/Devices/Storage/IOBufMgmt.cpp
@@ -0,0 +1,543 @@
+/* $Id: IOBufMgmt.cpp $ */
+/** @file
+ * VBox storage devices: I/O buffer management API.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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).
+ *
+ * @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 + RT_BIT_Z(iBinCur + pThis->u32OrderMin));
+ }
+
+ /* 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 = RT_BIT_Z(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..20f183aa
--- /dev/null
+++ b/src/VBox/Devices/Storage/IOBufMgmt.h
@@ -0,0 +1,119 @@
+/* $Id: IOBufMgmt.h $ */
+/** @file
+ * VBox storage devices: I/O buffer management API.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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.
+ *
+ * @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..80e7f94d
--- /dev/null
+++ b/src/VBox/Devices/Storage/UsbMsd.cpp
@@ -0,0 +1,2416 @@
+/* $Id: UsbMsd.cpp $ */
+/** @file
+ * UsbMSD - USB Mass Storage Device Emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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 */
+ uint8_t bCBWLen = RT_MIN(pCbw->bCBWCBLength, sizeof(pCbw->CBWCB));
+ size_t cbCopy = RT_UOFFSETOF_DYN(USBCBW, CBWCB[bCBWLen]);
+ 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.
+ *
+ * @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 ( RT_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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ /* config. */
+ pHlp->pfnSSMPutBool(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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ /* The config */
+ int rc = usbMsdLiveExec(pUsbIns, pSSM, SSM_PASS_FINAL);
+ AssertRCReturn(rc, rc);
+
+ pHlp->pfnSSMPutU8(pSSM, pThis->bConfigurationValue);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[0].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[1].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[2].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->pReq != NULL);
+
+ if (pThis->pReq)
+ {
+ PUSBMSDREQ pReq = pThis->pReq;
+
+ pHlp->pfnSSMPutU32(pSSM, pReq->enmState);
+ pHlp->pfnSSMPutU32(pSSM, pReq->cbBuf);
+ if (pReq->cbBuf)
+ {
+ AssertPtr(pReq->pbBuf);
+ pHlp->pfnSSMPutMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+
+ pHlp->pfnSSMPutU32(pSSM, pReq->offBuf);
+ pHlp->pfnSSMPutMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+ pHlp->pfnSSMPutU8(pSSM, pReq->iScsiReqStatus);
+ }
+
+ return pHlp->pfnSSMPutU32(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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Verify config. */
+ bool fInUse;
+ int rc = pHlp->pfnSSMGetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pThis->Lun0.pIBase != NULL))
+ return pHlp->pfnSSMSetCfgError(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);
+
+ pHlp->pfnSSMGetU8(pSSM, &pThis->bConfigurationValue);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[0].fHalted);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[1].fHalted);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[2].fHalted);
+ bool fReqAlloc = false;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fReqAlloc);
+ AssertRCReturn(rc, rc);
+ if (fReqAlloc)
+ {
+ PUSBMSDREQ pReq = usbMsdReqAlloc(pThis);
+ AssertReturn(pReq, VERR_NO_MEMORY);
+ pThis->pReq = pReq;
+
+ AssertCompile(sizeof(pReq->enmState) == sizeof(uint32_t));
+ pHlp->pfnSSMGetU32(pSSM, (uint32_t *)&pReq->enmState);
+
+ uint32_t cbBuf = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbBuf);
+ AssertRCReturn(rc, rc);
+ if (cbBuf)
+ {
+ if (usbMsdReqEnsureBuffer(pThis, pReq, cbBuf))
+ {
+ AssertPtr(pReq->pbBuf);
+ Assert(cbBuf == pReq->cbBuf);
+ pHlp->pfnSSMGetMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ pHlp->pfnSSMGetU32(pSSM, &pReq->offBuf);
+ pHlp->pfnSSMGetMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION_PRE_CLEANUP)
+ rc = pHlp->pfnSSMGetU8(pSSM, &pReq->iScsiReqStatus);
+ else
+ {
+ int32_t iScsiReqStatus;
+
+ /* Skip old fields which are unused now or can be determined from the CBW. */
+ pHlp->pfnSSMSkip(pSSM, 4 * 4 + 64);
+ rc = pHlp->pfnSSMGetS32(pSSM, &iScsiReqStatus);
+ pReq->iScsiReqStatus = (uint8_t)iScsiReqStatus;
+ }
+ AssertRCReturn(rc, rc);
+ }
+
+ uint32_t u32;
+ rc = pHlp->pfnSSMGetU32(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, NULL,
+ pReq->Cbw.dCBWDataTransferLength, NULL, 0, NULL,
+ &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) || (pCbw->bCBWCBLength > sizeof(pCbw->CBWCB)))
+ {
+ 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{PDMUSBREG,pfnVMReset}
+ */
+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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ 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 = pHlp->pfnCFGMValidateConfig(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.h b/src/VBox/Devices/Storage/VBoxSCSI.h
new file mode 100644
index 00000000..a03c1531
--- /dev/null
+++ b/src/VBox/Devices/Storage/VBoxSCSI.h
@@ -0,0 +1,133 @@
+/* $Id: VBoxSCSI.h $ */
+/** @file
+ * VBox storage devices - Simple SCSI interface for BIOS access.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @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 *
+*******************************************************************************/
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/version.h>
+
+#ifdef IN_RING3
+RT_C_DECLS_BEGIN
+
+/**
+ * Helper shared by the LsiLogic and BusLogic device emulations to load legacy saved states
+ * before the removal of the VBoxSCSI interface.
+ *
+ * @returns VBox status code.
+ * @param pHlp Pointer to the Ring-3 device helper table.
+ * @param pSSM The SSM handle to operate on.
+ */
+DECLINLINE(int) vboxscsiR3LoadExecLegacy(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM)
+{
+ pHlp->pfnSSMSkip(pSSM, 4);
+
+ /*
+ * 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 ( ( pHlp->pfnSSMHandleRevision(pSSM) < 104311
+ && pHlp->pfnSSMHandleVersion(pSSM) < VBOX_FULL_VERSION_MAKE(5, 0, 12))
+ || ( pHlp->pfnSSMHandleRevision(pSSM) < 104155
+ && pHlp->pfnSSMHandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 0, 51)))
+ pHlp->pfnSSMSkip(pSSM, 12);
+ else
+ pHlp->pfnSSMSkip(pSSM, 20);
+
+ pHlp->pfnSSMSkip(pSSM, 1); /*iCDB*/
+ uint32_t cbBufLeft, iBuf;
+ pHlp->pfnSSMGetU32(pSSM, &cbBufLeft);
+ pHlp->pfnSSMGetU32(pSSM, &iBuf);
+ pHlp->pfnSSMSkip(pSSM, 2); /*fBusy, enmState*/
+
+ if (cbBufLeft + iBuf)
+ pHlp->pfnSSMSkip(pSSM, cbBufLeft + iBuf);
+
+ return VINF_SUCCESS;
+}
+
+
+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..a2864b84
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp
@@ -0,0 +1,434 @@
+/* $Id: VSCSIDevice.cpp $ */
+/** @file
+ * Virtual SCSI driver: Device handling
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ vscsiReqSetXferSize(pVScsiReq, RT_MIN(sizeof(SCSIINQUIRYDATA), 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.
+ */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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:
+ {
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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:
+ {
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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, pVScsiReq->enmXferDir, pVScsiReq->cbSenseWritten);
+
+ 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;
+ pVScsiReq->enmXferDir = VSCSIXFERDIR_UNKNOWN;
+ pVScsiReq->cbSenseWritten = 0;
+ 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..71924d1d
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h
@@ -0,0 +1,714 @@
+/* $Id: VSCSIInternal.h $ */
+/** @file
+ * Virtual SCSI driver: Internal defines
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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;
+ /** Number of bytes of sense data written. */
+ size_t cbSenseWritten;
+ /** Transfer direction as indicated by the CDB. */
+ VSCSIXFERDIR enmXferDir;
+ /** 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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @param pVScsiReq The SCSI request.
+ * @param cbXfer The transfer size for the request.
+ */
+DECLINLINE(void) vscsiReqSetXferSize(PVSCSIREQINT pVScsiReq, size_t cbXfer)
+{
+ pVScsiReq->cbXfer = cbXfer;
+}
+
+/**
+ * Sets the transfer direction for the given request.
+ *
+ * @param pVScsiReq The SCSI request.
+ * @param cbXfer The transfer size for the request.
+ */
+DECLINLINE(void) vscsiReqSetXferDir(PVSCSIREQINT pVScsiReq, VSCSIXFERDIR enmXferDir)
+{
+ pVScsiReq->enmXferDir = enmXferDir;
+}
+
+/**
+ * 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..48fd803e
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp
@@ -0,0 +1,267 @@
+/* $Id: VSCSIIoReq.cpp $ */
+/** @file
+ * Virtual SCSI driver: I/O request handling.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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..8021392f
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp
@@ -0,0 +1,184 @@
+/* $Id: VSCSILun.cpp $ */
+/** @file
+ * Virtual SCSI driver: LUN handling
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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..66d76e22
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp
@@ -0,0 +1,1797 @@
+/* $Id: VSCSILunMmc.cpp $ */
+/** @file
+ * Virtual SCSI driver: MMC LUN implementation (CD/DVD-ROM)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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,
+ /** Medium eject requested (eject button pressed). */
+ MMCEVENTSTATUSTYPE_MEDIA_EJECT_REQUESTED,
+ /** New medium inserted. */
+ MMCEVENTSTATUSTYPE_MEDIA_NEW,
+ /** Medium removed. */
+ MMCEVENTSTATUSTYPE_MEDIA_REMOVED,
+ /** Medium was removed + new medium was inserted. */
+ MMCEVENTSTATUSTYPE_MEDIA_CHANGED,
+ /** 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 DECLCALLBACKTYPE(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] = (OldStatus == MMCEVENTSTATUSTYPE_MEDIA_CHANGED) ? 0x04 /* media changed */ : 0x03; /* media removed */
+ 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. */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT, 0x00);
+ break;
+
+ case SCSI_INQUIRY:
+ {
+ SCSIINQUIRYDATA ScsiInquiryReply;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ vscsiReqSetXferSize(pVScsiReq, RT_MIN(sizeof(SCSIINQUIRYDATA), 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));
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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]);
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcModeSense10(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_SEEK_10:
+ {
+ uint32_t uLba = scsiBE2H_U32(&pVScsiReq->pbCDB[2]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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!! */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_I2T);
+ 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);
+ if (RT_FAILURE(rc))
+ {
+ rcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST,
+ SCSI_ASC_LOGICAL_BLOCK_OOR, 0x00);
+ break;
+ }
+
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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];
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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 */
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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];
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcReadTrackInformation(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_GET_CONFIGURATION:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[7]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ vscsiReqSetXferSize(pVScsiReq, cbMax);
+ rcReq = vscsiLunMmcGetConfiguration(pVScsiLunMmc, pVScsiReq, cbMax);
+ break;
+ }
+ case SCSI_READ_DVD_STRUCTURE:
+ {
+ size_t cbMax = scsiBE2H_U16(&pVScsiReq->pbCDB[8]);
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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));
+
+ vscsiReqSetXferDir(pVScsiReq, enmTxDir == VSCSIIOREQTXDIR_WRITE ? VSCSIXFERDIR_I2T : VSCSIXFERDIR_T2I);
+ 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);
+ rc = VINF_SUCCESS; /* The request was completed properly, so don't indicate an error here which might cause another completion. */
+ }
+ 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..3769c4f6
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp
@@ -0,0 +1,655 @@
+/* $Id: VSCSILunSbc.cpp $ */
+/** @file
+ * Virtual SCSI driver: SBC LUN implementation (hard disks)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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:
+ {
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+
+ /* 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;
+
+ vscsiReqSetXferSize(pVScsiReq, RT_MIN(sizeof(SCSIINQUIRYDATA), scsiBE2H_U16(&pVScsiReq->pbCDB[3])));
+ 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));
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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];
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_I2T);
+ 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;
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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:
+ {
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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];
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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 */
+
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_T2I);
+ 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. */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_I2T);
+ 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))
+ {
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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. */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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
+ {
+ vscsiReqSetXferDir(pVScsiReq, enmTxDir == VSCSIIOREQTXDIR_WRITE ? VSCSIXFERDIR_I2T : VSCSIXFERDIR_T2I);
+ rc = vscsiIoReqTransferEnqueue(pVScsiLun, pVScsiReq, enmTxDir,
+ uLbaStart * 512, cSectorTransfer * 512);
+ }
+ }
+ }
+ else if (pVScsiReq->pbCDB[0] == SCSI_SYNCHRONIZE_CACHE)
+ {
+ /* Enqueue flush */
+ vscsiReqSetXferDir(pVScsiReq, VSCSIXFERDIR_NONE);
+ 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..4c659a2d
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp
@@ -0,0 +1,469 @@
+/* $Id: VSCSILunSsc.cpp $ */
+/** @file
+ * Virtual SCSI driver: SSC LUN implementation (Streaming tape)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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..27be1bc6
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp
@@ -0,0 +1,107 @@
+/* $Id: VSCSISense.cpp $ */
+/** @file
+ * Virtual SCSI driver: Sense handling
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#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)
+ {
+ pVScsiReq->cbSenseWritten = RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense);
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, pVScsiReq->cbSenseWritten);
+ }
+
+ 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)
+ {
+ pVScsiReq->cbSenseWritten = RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense);
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, pVScsiReq->cbSenseWritten);
+ }
+
+ 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)
+ {
+ pVScsiReq->cbSenseWritten = RT_MIN(sizeof(pVScsiSense->abSenseBuf), pVScsiReq->cbSense);
+ memcpy(pVScsiReq->pbSense, pVScsiSense->abSenseBuf, pVScsiReq->cbSenseWritten);
+ }
+
+ 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..dcf5dcfc
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp
@@ -0,0 +1,128 @@
+/* $Id: VSCSIVpdPagePool.cpp $ */
+/** @file
+ * Virtual SCSI driver: VPD page pool
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_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)
+ {
+ vscsiReqSetXferSize(pVScsiReq, pPage->cbPage);
+ 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..8d1e219c
--- /dev/null
+++ b/src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h
@@ -0,0 +1,212 @@
+/* $Id: VSCSIVpdPages.h $ */
+/** @file
+ * Virtual SCSI driver: Definitions for VPD pages.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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..a63713e8
--- /dev/null
+++ b/src/VBox/Devices/Storage/swab.h
@@ -0,0 +1,74 @@
+/* $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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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 */