summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage/VSCSI
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Storage/VSCSI')
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIDevice.cpp434
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIInternal.h714
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIIoReq.cpp267
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILun.cpp184
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunMmc.cpp1797
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSbc.cpp655
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSILunSsc.cpp469
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSISense.cpp107
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPagePool.cpp128
-rw-r--r--src/VBox/Devices/Storage/VSCSI/VSCSIVpdPages.h212
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 */
+