/*
* DoScsiCommand.c
*
* This is the common entry to the original and asynchronous SCSI Manager calls:
* if the asynchronous SCSI Manager is requested, it calls it. Otherwise, it
* calls the original SCSI Manager and executes Request Sense if necessary.
*
* This function returns "autosense" in the SCSI_Sense_Data area. This will
* be formatted in the senseMessage string.
*/
/*
* Copyright 1992, 1993, 1997, 1998 by Apple Computer, Inc.
* All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appears in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation.
*
* APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
* NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "DoScsiCommand.h"
#include "util.h"
//
// Defines
//
#define kSCSICommandTimeout (5 * 1000L) /* Five seconds */
/*
* This is the maximum number of times we try to grab the SCSI Bus
*/
#define kMaxSCSIRetries 40 /* 10 seconds, 4 times/sec */
/*
* This test is TRUE if the SCSI bus status indicates "busy" (which is the case
* if either the BSY or SEL bit is set).
*/
#ifndef kScsiStatBSY
#define kScsiStatBSY (1 << 6)
#endif
#ifndef kScsiStatSEL
#define kScsiStatSEL (1 << 1)
#endif
#define ScsiBusBusy() ((SCSIStat() & (kScsiStatBSY | kScsiStatSEL)) != 0)
//
// Types
//
//
// Global Constants
//
//
// Global Variables
//
int gSCSIHiBusID;
SCSIExecIOPB *gSCSIExecIOPBPtr;
UInt32 gSCSIExecIOPBPtrLen;
//
// Forward declarations
//
UInt16 GetCommandLength(const SCSI_CommandPtr cmdPtr);
Boolean IsVirtualMemoryRunning(void);
OSErr OriginalSCSI(
DeviceIdent scsiDevice,
const SCSI_CommandPtr scsiCommand,
UInt8 scsiCommandLen,
Ptr dataBuffer,
ByteCount dataLength,
UInt32 scsiFlags,
ByteCount *actualTransferCount,
UInt8 *scsiStatusByte
);
OSErr DoOriginalSCSICommand(
DeviceIdent scsiDevice,
const SCSI_CommandPtr theSCSICommand,
uint16_t cmdBlockLength,
Ptr dataBuffer,
ByteCount dataLength,
UInt32 scsiFlags,
ByteCount *actualTransferCount,
SCSI_Sense_Data *sensePtr
);
//
// Routines
//
/*
* This returns TRUE if the command failed with "Illegal Request." We need this
* so we can ignore LogSense or ReadDefectData if the device doesn't support
* these functions.
*/
Boolean
IsIllegalRequest(
OSErr scsiStatus,
const SCSI_Sense_Data *senseDataPtr
)
{
Boolean result;
#define SENSE (*senseDataPtr)
result = FALSE;
if (scsiStatus == scsiNonZeroStatus
&& (SENSE.senseKey & kScsiSenseKeyMask) == kScsiSenseIllegalReq
&& SENSE.additionalSenseLength >= 4) {
switch ((SENSE.additionalSenseCode << 8) | SENSE.additionalSenseQualifier) {
case 0x0000:
case 0x2000:
case 0x2022: /* Obsolete */
result = TRUE;
break;
default:
break;
}
}
return (result);
#undef SENSE
}
/*
* This returns TRUE if the command failed with Device Not Ready (No Media Present)
*/
Boolean
IsNoMedia(
OSErr scsiStatus,
const SCSI_Sense_Data *senseDataPtr
)
{
Boolean result;
#define SENSE (*senseDataPtr)
result = FALSE;
if (scsiStatus == scsiNonZeroStatus
&& (SENSE.senseKey & kScsiSenseKeyMask) == kScsiSenseNotReady
&& SENSE.additionalSenseLength >= 4) {
switch ((SENSE.additionalSenseCode << 8) | SENSE.additionalSenseQualifier) {
case 0x0000:
case 0x3A00:
result = TRUE;
break;
default:
break;
}
}
return (result);
#undef SENSE
}
/*
* Do one SCSI Command. If the device returns Check Condition, issue Request Sense
* (original SCSI Manager only) and interpret the sense data. The original SCSI
* command status is in SCB.status. If it is statusErr or scsiNonZeroStatus,
* the sense data is in SCB.sense and the Request Sense status is in
* SCB.requestSenseStatus.
*
* If sensePtr[0] is non-zero, there is a message.
*/
OSErr
DoSCSICommand(
DeviceIdent scsiDevice,
ConstStr255Param currentAction,
const SCSI_CommandPtr callerSCSICommand,
Ptr dataBuffer,
ByteCount dataLength,
UInt32 scsiFlags,
ByteCount *actualTransferCount,
SCSI_Sense_Data *sensePtr,
StringPtr senseMessage
)
{
OSErr status;
SCSI_Command theSCSICommand;
uint16_t cmdBlockLength;
// SpinSpinner(&gCurrentInfoPtr->spinnerRecord);
// ShowProgressAction(currentAction);
/*
* Store the LUN information in the command block - this is needed
* for devices that only examine the command block for LUN values.
* (On SCSI-II, the asynchronous SCSI Manager also includes the
* LUN in the identify message).
*/
theSCSICommand = *callerSCSICommand;
theSCSICommand.scsi[1] &= ~0xE0;
theSCSICommand.scsi[1] |= (scsiDevice.LUN & 0x03) << 5;
cmdBlockLength = GetCommandLength(&theSCSICommand);
if (senseMessage != NULL)
senseMessage[0] = 0;
if (sensePtr != NULL)
sensePtr->errorCode = 0;
if (scsiDevice.bus == kOriginalSCSIBusAdaptor) {
status = DoOriginalSCSICommand(
scsiDevice,
&theSCSICommand,
cmdBlockLength,
dataBuffer,
dataLength,
scsiFlags,
actualTransferCount,
sensePtr
);
}
else {
clear_memory(gSCSIExecIOPBPtr, gSCSIExecIOPBPtrLen);
#define PB (*gSCSIExecIOPBPtr)
PB.scsiPBLength = gSCSIExecIOPBPtrLen;
PB.scsiFunctionCode = SCSIExecIO;
PB.scsiDevice = scsiDevice;
PB.scsiTimeout = kSCSICommandTimeout;
/*
* Fiddle the flags so they're the least disruptive possible.
*/
PB.scsiFlags = scsiFlags | (scsiSIMQNoFreeze | scsiDontDisconnect);
if (sensePtr != NULL) {
PB.scsiSensePtr = (UInt8 *) sensePtr;
PB.scsiSenseLength = sizeof *sensePtr;
}
BlockMoveData(&theSCSICommand, &PB.scsiCDB.cdbBytes[0], cmdBlockLength);
PB.scsiCDBLength = cmdBlockLength;
if (dataBuffer != NULL) {
PB.scsiDataPtr = (UInt8 *) dataBuffer;
PB.scsiDataLength = dataLength;
PB.scsiDataType = scsiDataBuffer;
PB.scsiTransferType = scsiTransferPolled;
}
status = SCSIAction((SCSI_PB *) &PB);
if (status == noErr)
status = PB.scsiResult;
if (status == scsiSelectTimeout)
status = scsiDeviceNotThere;
if (actualTransferCount != NULL) {
/*
* Make sure that the actual transfer count does not exceed
* the allocation count (some devices spit extra data at us!)
*/
*actualTransferCount = dataLength - PB.scsiDataResidual;
if (*actualTransferCount > dataLength)
*actualTransferCount = dataLength;
}
#undef PB
}
if (status == scsiNonZeroStatus
&& sensePtr != NULL
&& sensePtr->errorCode != 0
&& senseMessage != NULL) {
// FormatSenseMessage(sensePtr, senseMessage);
// ShowProgressAction(senseMessage);
}
return (status);
}
/*
* Do a command with autosense using the original SCSI manager.
*/
OSErr
DoOriginalSCSICommand(
DeviceIdent scsiDevice,
const SCSI_CommandPtr theSCSICommand,
uint16_t cmdBlockLength,
Ptr dataBuffer,
ByteCount dataLength,
UInt32 scsiFlags,
ByteCount *actualTransferCount,
SCSI_Sense_Data *sensePtr
)
{
OSErr status;
UInt8 scsiStatusByte;
SCSI_Command scsiStatusCommand;
status = OriginalSCSI(
scsiDevice,
theSCSICommand,
cmdBlockLength,
dataBuffer,
dataLength,
scsiFlags,
actualTransferCount,
&scsiStatusByte
);
if (status == scsiNonZeroStatus
&& scsiStatusByte == kScsiStatusCheckCondition
&& sensePtr != NULL) {
CLEAR(scsiStatusCommand);
CLEAR(*sensePtr);
scsiStatusCommand.scsi6.opcode = kScsiCmdRequestSense;
scsiStatusCommand.scsi[1] |= (scsiDevice.LUN & 0x03) << 5;
scsiStatusCommand.scsi6.len = sizeof *sensePtr;
status = OriginalSCSI(
scsiDevice,
&scsiStatusCommand,
sizeof scsiStatusCommand.scsi6,
(Ptr) sensePtr,
sizeof *sensePtr,
scsiDirectionIn,
NULL,
&scsiStatusByte
);
if (status != noErr && status != scsiDataRunError) {
#ifdef notdef
if (gDebugOnError && scsiStatusByte != kScsiStatusCheckCondition) {
Str255 work;
pstrcpy(work, "\pAutosense failed ");
AppendSigned(work, status);
AppendChar(work, ' ');
AppendHexLeadingZeros(work, scsiStatusByte, 2);
DebugStr(work);
}
#endif
sensePtr->errorCode = 0;
status = scsiAutosenseFailed;
}
else {
status = scsiNonZeroStatus;
}
}
return (status);
}
OSErr
OriginalSCSI(
DeviceIdent scsiDevice,
const SCSI_CommandPtr scsiCommand,
UInt8 scsiCommandLen,
Ptr dataBuffer,
ByteCount dataLength,
UInt32 scsiFlags,
ByteCount *actualTransferCount,
UInt8 *scsiStatusBytePtr
)
{
OSErr status; /* Final status */
OSErr completionStatus; /* Status from ScsiComplete */
short totalTries; /* Get/Select retries */
short getTries; /* Get retries */
short iCount; /* Bus free counter */
uint32_t watchdog; /* Timeout after this */
uint32_t myTransferCount; /* Gets TIB loop counter */
short scsiStatusByte; /* Gets SCSIComplete result */
short scsiMsgByte; /* Gets SCSIComplete result */
Boolean bufferHoldFlag;
/*
* The TIB has the following format:
* [0] scInc user buffer transferQuantum or transferSize
* [1] scAdd &theTransferCount 1
* [2] scLoop -> tib[0] transferSize / transferQuantum
* [3] scStop
* The intent of this is to return, in actualTransferCount, the number
* of times we cycled through the tib[] loop. This will be the actual
* transfer count if transferQuantum equals one, or the number of
* "blocks" if transferQuantum is the length of one sector.
*/
SCSIInstr tib[4]; /* Current TIB */
status = noErr;
bufferHoldFlag = FALSE;
scsiStatusByte = 0xFF;
scsiMsgByte = 0xFF;
myTransferCount = 0;
/*
* If there is a data transfer, setup the tib.
*/
if (dataBuffer != NULL) {
tib[0].scOpcode = scInc;
tib[0].scParam1 = (uint32_t) dataBuffer;
tib[0].scParam2 = 1;
tib[1].scOpcode = scAdd;
tib[1].scParam1 = (uint32_t) &myTransferCount;
tib[1].scParam2 = 1;
tib[2].scOpcode = scLoop;
tib[2].scParam1 = (-2 * sizeof (SCSIInstr));
tib[2].scParam2 = dataLength / tib[0].scParam2;
tib[3].scOpcode = scStop;
tib[3].scParam1 = 0;
tib[3].scParam2 = 0;
}
if (IsVirtualMemoryRunning() && dataBuffer != NULL) {
/*
* Lock down the user buffer, if any. In a real-world application
* or driver, this would be done before calling the SCSI interface.
*/
#ifdef notdef
FailOSErr(
HoldMemory(dataBuffer, dataLength),
"\pCan't lock data buffer in physical memory"
);
#else
HoldMemory(dataBuffer, dataLength);
#endif
bufferHoldFlag = TRUE;
}
/*
* Arbitrate for the scsi bus. This will fail if some other device is
* accessing the bus at this time (which is unlikely).
*
*** Do not set breakpoints or call any functions that may require device
*** I/O (such as display code that accesses font resources between
*** SCSIGet and SCSIComplete,
*
*/
for (totalTries = 0; totalTries < kMaxSCSIRetries; totalTries++) {
for (getTries = 0; getTries < 4; getTries++) {
/*
* Wait for the bus to go free.
*/
watchdog = TickCount() + 300; /* 5 second timeout */
while (ScsiBusBusy()) {
if (/*gStopNow || StopNow() ||*/ TickCount() > watchdog) {
status = scsiBusy;
goto exit;
}
}
/*
* The bus is free, try to grab it
*/
for (iCount = 0; iCount < 4; iCount++) {
if ((status = SCSIGet()) == noErr)
break;
}
if (status == noErr) {
break; /* Success: we have the bus */
}
/*
* The bus became busy again. Try to wait for it to go free.
*/
for (iCount = 0;
/*gStopNow == FALSE && StopNow() == FALSE &&*/ iCount < 100 && ScsiBusBusy();
iCount++)
;
} /* The getTries loop */
if (status != noErr) {
/*
* The SCSI Manager thinks the bus is not busy and not selected,
* but "someone" has set its internal semaphore that signals
* that the SCSI Manager itself is busy. The application will have
* to handle this problem. (We tried getTries * 4 times).
*/
status = scsiBusy;
goto exit;
}
/*
* We now own the SCSI bus. Try to select the device.
*/
if ((status = SCSISelect(scsiDevice.targetID)) != noErr) {
switch (status) {
/*
* We get scBadParmsErr if we try to arbitrate for the initiator.
*/
case scBadParmsErr: status = scsiTIDInvalid; break;
case scCommErr: status = scsiDeviceNotThere; break;
case scArbNBErr: status = scsiBusy; break;
case scSequenceErr: status = scsiRequestInvalid; break;
}
goto exit;
}
/*
* From this point on, we must exit through SCSIComplete() even if an
* error is detected. Send a command to the selected device. There are
* several failure modes, including an illegal command (such as a
* write to a read-only device). If the command failed because of
* "device busy", we will try it again.
*/
status = SCSICmd((Ptr) scsiCommand, scsiCommandLen);
if (status != noErr) {
switch (status) {
case scCommErr: status = scsiCommandTimeout; break;
case scPhaseErr: status = scsiSequenceFailed; break;
}
}
if (status == noErr && dataBuffer != NULL) {
/*
* This command requires a data transfer.
*/
if (scsiFlags == scsiDirectionOut) {
status = SCSIWrite((Ptr) tib);
} else {
status = SCSIRead((Ptr) tib);
}
switch (status) {
case scCommErr: status = scsiCommandTimeout; break;
case scBadParmsErr: status = scsiRequestInvalid; break;
case scPhaseErr: status = noErr; /* Don't care */ break;
case scCompareErr: /* Can't happen */ break;
}
}
/*
* SCSIComplete "runs" the bus-phase algorithm until the bitter end,
* returning the status and command-completion message bytes..
*/
completionStatus = SCSIComplete(
&scsiStatusByte,
&scsiMsgByte,
5 * 60L
);
if (status == noErr && completionStatus != noErr) {
switch (completionStatus) {
case scCommErr: status = scsiCommandTimeout; break;
case scPhaseErr: status = scsiSequenceFailed; break;
case scComplPhaseErr: status = scsiSequenceFailed; break;
}
}
if (completionStatus == noErr && scsiStatusByte == kScsiStatusBusy) {
/*
* ScsiComplete is happy. If the device is busy,
* pause for 1/4 second and try again.
*/
watchdog = TickCount() + 15;
while (TickCount() < watchdog)
;
continue; /* Do next totalTries attempt */
}
/*
* This is the normal exit (success) or final failure exit.
*/
break;
} /* totalTries loop */
exit:
if (bufferHoldFlag) {
(void) UnholdMemory(dataBuffer, dataLength);
}
/*
* Return the number of bytes transferred to the caller. If the caller
* supplied an actual count and the count is no greater than the maximum,
* ignore any phase errors.
*/
if (actualTransferCount != NULL) {
*actualTransferCount = myTransferCount;
if (*actualTransferCount > dataLength) {
*actualTransferCount = dataLength;
}
}
/*
* Also, there is a bug in the combination of System 7.0.1 and the 53C96
* that may cause the real SCSI Status Byte to be in the Message byte.
*/
if (scsiStatusByte == kScsiStatusGood
&& scsiMsgByte == kScsiStatusCheckCondition) {
scsiStatusByte = kScsiStatusCheckCondition;
}
if (status == noErr) {
switch (scsiStatusByte) {
case kScsiStatusGood: break;
case kScsiStatusBusy: status = scsiBusy; break;
case 0xFF: status = scsiProvideFail; break;
default: status = scsiNonZeroStatus; break;
}
}
if (status == noErr
&& (scsiFlags & scsiDirectionMask) != scsiDirectionNone
&& myTransferCount != dataLength) {
status = scsiDataRunError;
}
if (scsiStatusBytePtr != NULL) {
*scsiStatusBytePtr = scsiStatusByte;
}
return (status);
}
UInt16
GetCommandLength(
const SCSI_CommandPtr cmdPtr
)
{
uint16_t result;
/*
* Look at the "group code" in the command operation. Return zero
* error for the reserved (3, 4) and vendor-specific command (6, 7)
* command groups. Otherwise, set the command length from the group code
* value as specified in the SCSI-II spec.
*/
switch (cmdPtr->scsi6.opcode & 0xE0) {
case (0 << 5): result = 6; break;
case (1 << 5):
case (2 << 5): result = 10; break;
case (5 << 5): result = 12; break;
default: result = 0; break;
}
return (result);
}
Boolean
IsVirtualMemoryRunning(void)
{
OSErr status;
long response;
status = Gestalt(gestaltVMAttr, &response);
/*
* VM is active iff Gestalt succeeded and the response is appropriate.
*/
return (status == noErr && ((response & (1 << gestaltVMPresent)) != 0));
}
void
AllocatePB()
{
OSErr status;
SCSIBusInquiryPB busInquiryPB;
#define PB (busInquiryPB)
if (gSCSIExecIOPBPtr == NULL) {
CLEAR(PB);
PB.scsiPBLength = sizeof PB;
PB.scsiFunctionCode = SCSIBusInquiry;
PB.scsiDevice.bus = 0xFF; /* Get info about the XPT */
status = SCSIAction((SCSI_PB *) &PB);
if (status == noErr)
status = PB.scsiResult;
if (PB.scsiHiBusID == 0xFF) {
gSCSIHiBusID = -1;
} else {
gSCSIHiBusID = PB.scsiHiBusID;
}
gSCSIExecIOPBPtrLen = PB.scsiMaxIOpbSize;
if (gSCSIExecIOPBPtrLen != 0)
gSCSIExecIOPBPtr = (SCSIExecIOPB *) NewPtrClear(gSCSIExecIOPBPtrLen);
}
#undef PB
}