DrvSCSI.cpp revision 0db6a029780d9f9b347500e117320a8d5661efe5
/* $Id$ */
/** @file
* VBox storage drivers: Generic SCSI command parser and execution driver
*/
/*
* Copyright (C) 2006-2010 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
//#define DEBUG
#define LOG_GROUP LOG_GROUP_DRV_SCSI
#include <VBox/pdmthread.h>
#include <iprt/semaphore.h>
#include "Builtins.h"
/**
* SCSI driver instance data.
*
* @implements PDMISCSICONNECTOR
* @implements PDMIBLOCKASYNCPORT
* @implements PDMIMOUNTNOTIFY
*/
typedef struct DRVSCSI
{
/** Pointer driver instance. */
/** Pointer to the attached driver's base interface. */
/** Pointer to the attached driver's block interface. */
/** Pointer to the attached driver's async block interface. */
/** Pointer to the attached driver's block bios interface. */
/** Pointer to the attached driver's mount interface. */
/** Pointer to the SCSI port interface of the device above. */
/** pointer to the Led port interface of the dveice above. */
/** The scsi connector interface .*/
/** The block port interface. */
#if 0 /* these interfaces aren't implemented */
/** The optional block async port interface. */
/** The mount notify interface. */
#endif
/** Fallback status LED state for this drive.
* This is used in case the device doesn't has a LED interface. */
/** Pointer to the status LED for this drive. */
/** Device type. */
/** BIOS PCHS Geometry. */
/** BIOS LCHS Geometry. */
/** Number of sectors this device has. */
/** The dedicated I/O thread for the non async approach. */
/** Queue for passing the requests to the thread. */
/** Request that we've left pending on wakeup or reset. */
/** Indicates whether PDMDrvHlpAsyncNotificationCompleted should be called by
* any of the dummy functions. */
bool volatile fDummySignal;
/** Release statistics: number of bytes written. */
/** Release statistics: number of bytes read. */
/** Converts a pointer to DRVSCSI::ISCSIConnecotr to a PDRVSCSI. */
#define PDMISCSICONNECTOR_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, ISCSIConnector)) )
#ifdef DEBUG
/**
* Dumps a SCSI request structure for debugging purposes.
*
* @returns nothing.
* @param pRequest Pointer to the request to dump.
*/
{
/* Print all scatter gather entries. */
{
}
}
#endif
/**
* Copy the content of a buffer to a scatter gather list only
* copying only the amount of data which fits into the
* scatter gather list.
*
* @returns VBox status code.
* @param pRequest Pointer to the request which contains the S/G list entries.
* @param pvBuf Pointer to the buffer which should be copied.
* @param cbBuf Size of the buffer.
*/
static int drvscsiScatterGatherListCopyFromBuffer(PPDMSCSIREQUEST pRequest, void *pvBuf, size_t cbBuf)
{
unsigned cSGEntry = 0;
#ifdef DEBUG
for (unsigned i = 0; i < cbBuf; i++)
#endif
{
/* We finished. */
if (!cbBuf)
break;
/* Advance the buffer. */
/* Go to the next entry in the list. */
pSGEntry++;
cSGEntry++;
}
return VINF_SUCCESS;
}
{
{
if (*pbSrc)
else
pbDst[i] = ' ';
}
}
/**
* Set the sense and advanced sense key in the buffer for error conditions.
*
* @returns SCSI status code.
* @param pRequest Pointer to the request which contains the sense buffer.
* @param uSCSISenseKey The sense key to set.
* @param uSCSIASC The advanced sense key to set.
*/
{
AssertMsgReturn(pRequest->cbSenseBuffer >= 18, ("Sense buffer is not big enough\n"), SCSI_STATUS_OK);
return SCSI_STATUS_CHECK_CONDITION;
}
/**
* Sets the sense key for a status good condition.
*
* @returns SCSI status code.
* @param pRequest Pointer to the request which contains the sense buffer.
*/
{
AssertMsgReturn(pRequest->cbSenseBuffer >= 18, ("Sense buffer is not big enough\n"), SCSI_STATUS_OK);
/*
* Setting this breaks Linux guests on the BusLogic controller.
* According to the SCSI SPC spec sense data is returned after a
* CHECK CONDITION status or a REQUEST SENSE command.
* Both SCSI controllers have a feature called Auto Sense which
* fetches the sense data automatically from the device
* with REQUEST SENSE. So the SCSI subsystem in Linux should
* find this sense data even if the command finishes successfully
* but if it finds valid sense data it will let the command fail
* and it doesn't detect attached disks anymore.
* Disabling makes it work again and no other guest shows errors
* so I will leave it disabled for now.
*
* On the other hand it is possible that the devices fetch the sense data
* only after a command failed so the content is really invalid if
* the command succeeds.
*/
#if 0
pRequest->pbSenseBuffer[13] = SCSI_ASC_NONE; /* Should be ASCQ but it has the same value for success. */
#endif
return SCSI_STATUS_OK;
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
/**
* Parses the CDB of a request and acts accordingly.
*
* @returns transfer direction type.
* @param pThis Pointer to the SCSI driver instance data.
* @param pRequest Pointer to the request to process.
* @param puOffset Where to store the start offset to start data transfer from.
* @param pcbToTransfer Where to store the number of bytes to transfer.
* @param piTxDir Where to store the data transfer direction.
*/
static int drvscsiProcessCDB(PDRVSCSI pThis, PPDMSCSIREQUEST pRequest, uint64_t *puOffset, uint32_t *pcbToTransfer, int *piTxDir)
{
int iTxDir = PDMBLOCKTXDIR_NONE;
int rc = SCSI_STATUS_OK;
/* We check for a command which needs to be handled even for non existant LUNs. */
{
case SCSI_INQUIRY:
{
/* We support only one attached device at LUN0 at the moment. */
if (pRequest->uLogicalUnit != 0)
{
ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
}
else
{
{
case PDMBLOCKTYPE_HARD_DISK:
break;
default:
}
}
break;
}
case SCSI_REPORT_LUNS:
{
/*
* If allocation length is less than 16 bytes SPC compliant devices have
* to return an error.
*/
else
{
}
break;
}
case SCSI_TEST_UNIT_READY:
{
break;
}
default:
{
/* Now for commands which are only implemented for existant LUNs. */
{
{
case SCSI_READ_CAPACITY:
{
/*
* If sector size exceeds the maximum value that is
* able to be stored in 4 bytes return 0xffffffff in this field
*/
else
break;
}
case SCSI_MODE_SENSE_6:
{
{
}
break;
}
case SCSI_READ_6:
{
break;
}
case SCSI_READ_10:
{
break;
}
case SCSI_READ_12:
{
break;
}
case SCSI_READ_16:
{
break;
}
case SCSI_WRITE_6:
{
break;
}
case SCSI_WRITE_10:
{
break;
}
case SCSI_WRITE_12:
{
break;
}
case SCSI_WRITE_16:
{
break;
}
case SCSI_SYNCHRONIZE_CACHE:
{
/* @todo When async mode implemented we have to move this out here. */
break;
}
case SCSI_READ_BUFFER:
{
switch (uDataMode)
{
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x0a:
break;
case 0x0b:
{
/* We do not implement an echo buffer. */
break;
}
case 0x1a:
case 0x1c:
break;
default:
AssertMsgFailed(("Invalid data mode\n"));
}
break;
}
case SCSI_START_STOP_UNIT:
{
/* Nothing to do. */
break;
}
case SCSI_LOG_SENSE:
{
switch (uPageCode)
{
case 0x00:
{
if (uSubPageCode == 0)
{
aReply[0] = 0;
aReply[1] = 0;
aReply[2] = 0;
aReply[3] = 0;
break;
}
}
default:
}
break;
}
{
{
{
/* Leave the rest 0 */
break;
}
default:
rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET); /* Don't know if this is correct */
}
break;
}
default:
//AssertMsgFailed(("Command %#x [%s] not implemented\n", pRequest->pbCDB[0], SCSICmdText(pRequest->pbCDB[0])));
}
}
else
{
/* Report an error. */
rc = drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_UNIT_DOES_NOT_RESPOND_TO_SELECTION);
}
break;
}
}
return rc;
}
{
int rc = VINF_SUCCESS;
int iTxDir;
int rcCompletion;
LogFlowFunc(("Entered\n"));
#ifdef DEBUG
#endif
{
while(cbToTransfer && cSegmentsLeft)
{
uint32_t cbProcess = (cbToTransfer < pSegActual->cbSeg) ? cbToTransfer : (uint32_t)pSegActual->cbSeg;
if (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
{
if (RT_FAILURE(rc))
}
else
{
if (RT_FAILURE(rc))
}
/* Go to the next entry. */
pSegActual++;
}
}
/* Notify device. */
return rc;
}
/**
* Dummy request function used by drvscsiReset to wait for all pending requests
* to complete prior to the device reset.
*
* @param pThis Pointer to the instace data.
* @returns VINF_SUCCESS.
*/
{
if (pThis->fDummySignal)
return VINF_SUCCESS;
}
/**
* Request function to wakeup the thread.
*
* @param pThis Pointer to the instace data.
* @returns VWRN_STATE_CHANGED.
*/
{
if (pThis->fDummySignal)
return VWRN_STATE_CHANGED;
}
/**
* The thread function which processes the requests asynchronously.
*
* @returns VBox status code.
* @param pDrvIns Pointer to the driver instance data.
* @param pThread Pointer to the thread instance data.
*/
{
int rc = VINF_SUCCESS;
LogFlowFunc(("Entering async IO loop.\n"));
return VINF_SUCCESS;
{
AssertMsg(rc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", rc));
}
return VINF_SUCCESS;
}
/**
* Deals with any pending dummy request
*
* @returns true if no pending dummy request, false if still pending.
* @param pThis The instance data.
* @param cMillies The number of milliseconds to wait for any
* pending request to finish.
*/
{
if (!pThis->pPendingDummyReq)
return true;
if (RT_FAILURE(rc))
return false;
return true;
}
{
int rc;
{
LogRel(("drvscsiAsyncIOLoopWakeup#%u: previous dummy request is still pending\n", pDrvIns->iInstance));
return VERR_TIMEOUT;
}
rc = RTReqCall(pThis->pQueueRequests, &pReq, 10000 /* 10 sec. */, (PFNRT)drvscsiAsyncIOLoopWakeupFunc, 1, pThis);
if (RT_SUCCESS(rc))
else
{
}
return rc;
}
/* -=-=-=-=- ISCSIConnector -=-=-=-=- */
/** @copydoc PDMISCSICONNECTOR::pfnSCSIRequestSend. */
static DECLCALLBACK(int) drvscsiRequestSend(PPDMISCSICONNECTOR pInterface, PPDMSCSIREQUEST pSCSIRequest)
{
int rc;
rc = RTReqCallEx(pThis->pQueueRequests, &pReq, 0, RTREQFLAGS_NO_WAIT, (PFNRT)drvscsiProcessRequestOne, 2, pThis, pSCSIRequest);
return VINF_SUCCESS;
}
/* -=-=-=-=- IBase -=-=-=-=- */
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
/**
* Worker for drvscsiReset, drvscsiSuspend and drvscsiPowerOff.
*
* @param pDrvIns The driver instance.
* @param pfnAsyncNotify The async callback.
*/
static void drvscsiR3ResetOrSuspendOrPowerOff(PPDMDRVINS pDrvIns, PFNPDMDRVASYNCNOTIFY pfnAsyncNotify)
{
if (!pThis->pQueueRequests)
return;
{
{
return;
}
int rc = RTReqCall(pThis->pQueueRequests, &pReq, 0 /*ms*/, (PFNRT)drvscsiAsyncIOLoopSyncCallback, 1, pThis);
if (RT_SUCCESS(rc))
{
return;
}
}
}
/**
* Callback employed by drvscsiSuspend and drvscsiPowerOff.
*
* @returns true if we've quiesced, false if we're still working.
* @param pDrvIns The driver instance.
*/
{
return false;
return true;
}
/**
* @copydoc FNPDMDRVPOWEROFF
*/
{
}
/**
* @copydoc FNPDMDRVSUSPEND
*/
{
}
/**
* Callback employed by drvscsiReset.
*
* @returns true if we've quiesced, false if we're still working.
* @param pDrvIns The driver instance.
*/
{
return false;
return true;
}
/**
* @copydoc FNPDMDRVRESET
*/
{
}
/**
* Destruct a driver instance.
*
* Most VM resources are freed by the VM. This callback is provided so that any non-VM
* resources can be freed correctly.
*
* @param pDrvIns The driver instance data.
*/
{
int rc;
if (pThis->pQueueRequests)
{
}
}
/**
* Construct a block driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
static DECLCALLBACK(int) drvscsiConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, uint32_t fFlags)
{
/*
* Initialize the instance data.
*/
/*
* Try attach driver below and query it's block interface.
*/
/*
* Query the block and blockbios interfaces.
*/
{
AssertMsgFailed(("Configuration error: No block interface!\n"));
return VERR_PDM_MISSING_INTERFACE;
}
if (!pThis->pDrvBlockBios)
{
AssertMsgFailed(("Configuration error: No block BIOS interface!\n"));
return VERR_PDM_MISSING_INTERFACE;
}
/* Query the SCSI port interface above. */
AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
/* Query the optional LED interface above. */
{
/* Get The Led. */
if (RT_FAILURE(rc))
}
else
/* Try to get the optional async block interface. */
if (enmType != PDMBLOCKTYPE_HARD_DISK)
N_("Only hard disks are currently supported as SCSI devices (enmType=%d)"),
enmType);
/* Create request queue. */
/* Register statistics counter. */
/** @todo aeichner: Find a way to put the instance number of the attached
* controller device when we support more than one controller of the same type.
* At the moment we have the 0 hardcoded. */
PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
/* Create I/O thread. */
return VINF_SUCCESS;
}
/**
* SCSI driver registration record.
*/
{
/* u32Version */
/* szDriverName */
"SCSI",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"Generic SCSI driver.",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(DRVSCSI),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
/* pfnSuspend */
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};