From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp | 1797 ++++++++++++++++++++++++ 1 file changed, 1797 insertions(+) create mode 100644 src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp (limited to 'src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp') 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_VSCSI +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +}; -- cgit v1.2.3