/* $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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_VSCSI #include #include #include #include #include #include #include #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; if (!pVScsiReq->cbCDB) { *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00); return 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 (pVScsiReq->cbCDB < 10) { *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00); break; } /* * 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: { if (pVScsiReq->cbCDB < 5) { *prcReq = vscsiReqSenseErrorSet(&pVScsiDevice->VScsiSense, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00); break; } 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->cbCDB < 8) { *prcReq = vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00); break; } 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]; PVSCSILUNDESC pVScsiLunDesc = pVScsiLun->pVScsiLunDesc; uint8_t const bOpc = pVScsiReq->pbCDB[0]; uint8_t const cbCdbMin = pVScsiLunDesc->pacbCdbOpc[bOpc]; /* Fail if the opcode is not supported or the CDB is too short. */ if ( cbCdbMin != VSCSI_LUN_CDB_SZ_INVALID && pVScsiReq->cbCDB >= cbCdbMin) rc = pVScsiLunDesc->pfnVScsiLunReqProcess(pVScsiLun, pVScsiReq); else { /* * CDB length is smaller than what the LUN expects, respond with an * ILLEGAL OPCODE error. */ vscsiLunReqSenseErrorSet(pVScsiLun, pVScsiReq, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE, 0x00); vscsiDeviceReqComplete(pVScsiDevice, pVScsiReq, SCSI_STATUS_CHECK_CONDITION, false, VINF_SUCCESS); } } 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; }