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/VSCSI | |
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 '')
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp | 434 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h | 714 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp | 267 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp | 184 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp | 1797 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp | 655 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp | 469 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp | 107 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp | 128 | ||||
-rw-r--r-- | src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h | 212 |
10 files changed, 4967 insertions, 0 deletions
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 */ + |