diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Storage | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Storage')
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 */ |