Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Src/OriginalSCSI.c
/* OriginalSCSI.c */ |
/* |
* OriginalSCSI.c |
* Copyright © 1992-93 Apple Computer Inc. All Rights Reserved. |
* |
* Talk to the Macintosh SCSI Handler using the "old" interface as defined in |
* Inside Mac IV. This is a synchronous call that executes a single SCSI Command |
* on a device. It does not support multiple busses or logical units. |
* |
* Calling Sequence: |
* OSErr OriginalSCSI( |
* short targetID, |
* const SCSI_CommandPtr scsiCommand, |
* unsigned short cmdBlockLength, |
* Boolean writeToDevice, |
* Ptr bufferPtr, |
* unsigned long transferSize, |
* unsigned long transferQuantum, |
* unsigned long completionTimeout, |
* unsigned short *stsBytePtr, |
* unsigned long *actualTransferCount |
* ); |
* The parameters have the following meaning: |
* |
* targetID The SCSI Bus ID of the target (0 .. 6). Note that this |
* function can only access LUN zero. |
* scsiCommand The SCSI Command Block (6, 10, or 12 bytes). The command |
* block length will be determined by examining the command |
* parameter. This is standardized for all but |
* "vendor-specific" commands. |
* writeToDevice TRUE if this command writes to the device. FALSE if this |
* command reads from the device or does not require a data |
* phase. |
* bufferPtr The user data buffer for Read/Write commands. It should be |
* NULL if a data transfer phase is not used for this command. |
* (e.g. for Test Unit Ready). |
* transferSize The total number of bytes to transfer to or from the device. |
* transferQuantum This is needed to configure the transfer information block |
* (TIB). The following values are appropriate: |
* -- Set to zero for a one-shot blind transfer. |
* ActualTransferCount is not correctly returned. |
* -- Set to one if a polled transfer is needed. This is |
* useful for Request Sense or other management requests, |
* especially requests with a variable-length record. On |
* return, the actual number of bytes that were transferred |
* will be in the actualTransferCount variable. |
* -- Set to the transferSize value for a normal, "Blind" |
* transfer. This is the normal case for data requests. |
* ActualTransferCount will equal transferSize on success, |
* but does not correctly return the actual count on phase |
* errors. This, however, can be recovered from the Request |
* Sense record. |
* -- Set to a sub-multiple (such as a block length) for |
* requests that need re-synchronization between sectors. |
* -- Other values will likely result in errors. |
* completionTimeout The timeout (in Ticks) for the command. This should be short |
* for disks, but must be long for tape devices and some setup |
* requests, such as Mode Select. |
* stsBytePtr This short is set to the byte returned in the device's |
* Status Phase. |
* actualTransferCount This will be set to the number of "cycles" through the TIB |
* loop times the transferCount. This should equal the number |
* of bytes transferred if transferCount is set to one. |
* (Ignored if NULL.) |
* Return codes: |
* noErr normal |
* scCommErr no such device (selection error) |
* scPhaseErr user data buffer was the wrong size for the transfer. |
* Look at the status byte to see if this is a problem: |
* you may merely have given a large buffer size to |
* a variable-length request, such as Device Inquiry. |
* sc... other SCSI error. |
* statusErr Device returned "Check condition." The caller should |
* issue a Request Sense SCSI Command to this device. |
* controlErr Device returned "Busy" |
* ioErr Other (serious) device status. The caller should |
* examine the Status and Message bytes to determine |
* the problem. |
* paramErr Could not determine the command length. |
*/ |
#include <scsi.h> |
#include <Errors.h> |
#include <GestaltEqu.h> |
#include <Memory.h> |
#include <Events.h> |
#include "MacSCSICommand.h" |
#ifndef TRUE |
#define FALSE 0 |
#define TRUE 1 |
#endif |
/* |
* Execute a SCSI command using the original (Inside Mac IV) SCSI Manager. |
* Return codes: |
* |
* noErr normal |
* paramErr could not determine command length from command |
* scCommErr could not select this device or bus busy |
* sc... other scsi error |
* statusErr Device returned "Check condition" |
* controlErr Device returned "Busy" (Note: device error) |
* ioErr Other (serious) device status -- bug. |
*/ |
OSErr OriginalSCSI( |
short targetID, /* Device ID on this bus */ |
const SCSI_CommandPtr scsiCommand, /* The actual scsi command */ |
unsigned short cmdBlockLength, /* -> Length of CDB */ |
Boolean writeToDevice, /* TRUE to write */ |
Ptr bufferPtr, /* -> user data buffer */ |
unsigned long transferSize, /* How much to transfer */ |
unsigned long transferQuantum, /* TIB setup parameter */ |
unsigned long completionTimeout, /* Ticks to wait */ |
unsigned short *stsBytePtr, /* <- status phase byte */ |
unsigned long *actualTransferCount |
); |
/* |
* SCSI command status (from status phase) |
*/ |
#define kScsiStatusGood 0x00 /* Normal completion */ |
#define kScsiStatusCheckCondition 0x02 /* Need GetExtendedStatus */ |
#define kScsiStatusConditionMet 0x04 /* For Compare Command? */ |
#define kScsiStatusBusy 0x08 /* Device busy (self-test?) */ |
#define kScsiStatusIntermediate 0x10 /* Intermediate status */ |
#define kScsiStatusResConflict 0x18 /* Reservation conflict */ |
#define kScsiStatusQueueFull 0x28 /* Target can't do command */ |
#define kScsiStatusReservedMask 0x3e /* Vendor specific? */ |
/* |
* 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) |
static void NextFunction(void); /* Dummy function for OriginalSCSI size */ |
static Boolean IsVirtualMemoryRunning(void); |
/* |
* These are bitmasks for the vmHoldMask variable. A bit is set if its associated |
* memory element has been held in protected (non-paged) memory. |
*/ |
#define kHoldFunction 0x0001 /* AsyncSCSI function code */ |
#define kHoldStack 0x0002 /* Local variables */ |
#define kHoldUserBuffer 0x0004 /* User data buffer, if any */ |
#define kHoldCommandBlock 0x0008 /* SCSI Command Data Block */ |
/* |
* Execute a SCSI command. |
* Returns the final status as noted above. |
*/ |
OSErr |
OriginalSCSI( |
short targetID, /* SCSI device on this bus */ |
const SCSI_CommandPtr scsiCommand, /* The actual scsi command */ |
unsigned short cmdBlockLength, /* -> Length of CDB */ |
Boolean writeToDevice, /* TRUE to write */ |
Ptr bufferPtr, /* -> user data buffer */ |
unsigned long transferSize, /* How much to transfer */ |
unsigned long transferQuantum, /* TIB setup parameter */ |
unsigned long completionTimeout, /* Ticks to wait */ |
unsigned short *stsBytePtr, /* <- status phase byte */ |
unsigned long *actualTransferCount |
) |
{ |
OSErr status; /* Final status */ |
OSErr completionStatus; /* Status from ScsiComplete */ |
short totalTries; /* Get/Select retries */ |
short getTries; /* Get retries */ |
short iCount; /* Bus free counter */ |
unsigned long watchdog; /* Timeout after this */ |
unsigned long myTransferCount; /* Gets TIB loop counter */ |
/* |
* 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 */ |
short messageByte; /* For Command Complete |
/* |
* The following parameters are used to manage virtual memory. The code |
* is taken from the DTS SCSI Sample Driver. |
*/ |
unsigned short vmHoldMask; |
unsigned long vmFunctionSize; |
char *vmProtectedStackBase; /* Last local variable */ |
/* |
* These values are used to compute the size of the stack that we must hold in |
* protected (non-virtual) memory. kSCSIManagerStackEstimate is an estimate. |
*/ |
#define kSCSILocalVariableSize ( \ |
(unsigned long) (((Ptr) &status) - ((Ptr) &vmProtectedStackBase)) \ |
) |
#define kSCSIManagerStackEstimate 512 |
#define kSCSIProtectedStackSize (kSCSIManagerStackEstimate + kSCSILocalVariableSize) |
status = noErr; |
vmHoldMask = 0; |
/* |
* If there is a data transfer, setup the tib. |
*/ |
myTransferCount = 0; |
if (transferQuantum == 0) |
transferQuantum = transferSize; |
if (bufferPtr != NULL) { |
tib[0].scOpcode = scInc; |
tib[0].scParam1 = (unsigned long) bufferPtr; |
tib[0].scParam2 = transferQuantum; |
tib[1].scOpcode = scAdd; |
tib[1].scParam1 = (unsigned long) &myTransferCount; |
tib[1].scParam2 = transferQuantum; |
tib[2].scOpcode = scLoop; |
tib[2].scParam1 = (-2 * sizeof (SCSIInstr)); |
tib[2].scParam2 = transferSize / tib[0].scParam2; |
tib[3].scOpcode = scStop; |
tib[3].scParam1 = 0; |
tib[3].scParam2 = 0; |
} |
if (IsVirtualMemoryRunning()) { |
/* |
* Virtual memory is active. Lock all of the memory segments that we |
* need in "real" memory (i.e. non-paged pool) for the duration of the |
* call. Since we need to back out of VM holds if there are errors, |
* we'll use bits in vmHoldMask to record the status of our attempts. |
* |
* Note: in a real application or driver, the user buffers should be |
* held outside of the SCSI Manager code: |
* HoldMemory(data buffer); |
* HoldMemory(autosense buffer); |
* status = CallSCSIManager(...); |
* UnholdMemory(...); |
* |
* First, hold the MacSCSI function. It starts at AsyncSCSI and |
* extends to the start of the next function. This is marked by a |
* dummy function. The left-margin comments indicate the value |
* of vmHoldCount if the indicated HoldMemory succeeded. This is not |
* needed for drivers. |
*/ |
vmFunctionSize = |
(unsigned long) NextFunction - (unsigned long) OriginalSCSI; |
status = HoldMemory(OriginalSCSI, vmFunctionSize); |
if (status == noErr) |
vmHoldMask |= kHoldFunction; |
if (status == noErr) { |
/* |
* Hold a chunk of stack space to call the SCSI Manager and to |
* protect our local variables. This is always needed, as drivers |
* can be called from application contexts. |
*/ |
vmProtectedStackBase = |
(char *) &vmProtectedStackBase - kSCSIManagerStackEstimate; |
status = HoldMemory(vmProtectedStackBase, kSCSIProtectedStackSize); |
if (status == noErr) |
vmHoldMask |= kHoldStack; |
} |
if (status == noErr) { |
/* |
* Lock down the command block. In this sample, we allocated |
* the command block in the application heap. A driver would |
* typically allocate it in the System Heap and, hence, would |
* not require this call. |
*/ |
status = HoldMemory((Ptr) scsiCommand, cmdBlockLength); |
if (status == noErr) |
vmHoldMask |= kHoldCommandBlock; |
} |
if (status == noErr && bufferPtr != NULL) { |
/* |
* Lock down the user buffer, if any. In a real-world application |
* or driver, this would be done before calling the SCSI interface. |
*/ |
status = HoldMemory(bufferPtr, transferSize); |
if (status != noErr) |
vmHoldMask |= kHoldStack; |
} |
if (status != noErr) |
goto exit; |
} |
/* |
* 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 (TickCount() > watchdog) { |
status = scArbNBErr; |
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; 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). |
*/ |
goto exit; |
} |
/* |
* We now own the SCSI bus. Try to select the device. |
*/ |
if ((status = SCSISelect(targetID)) != noErr) |
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, cmdBlockLength); |
if (status == noErr && bufferPtr != NULL) { |
/* |
* This command requires a data transfer. |
*/ |
if (writeToDevice) { |
if (transferQuantum == 1) |
status = SCSIWrite((Ptr) tib); |
else { |
status = SCSIWBlind((Ptr) tib); |
} |
} |
else { |
if (transferQuantum == 1) |
status = SCSIRead((Ptr) tib); |
else { |
status = SCSIRBlind((Ptr) tib); |
} |
} |
} |
finish: |
/* |
* SCSIComplete "runs" the bus-phase algorithm until the bitter end, |
* returning the status and command-completion message bytes.. |
*/ |
completionStatus = SCSIComplete( |
(short *) stsBytePtr, |
&messageByte, |
completionTimeout |
); |
/* |
* If we have an error here, return as the "final" status. |
* |
*/ |
if (completionStatus != noErr) |
status = completionStatus; |
else { |
/* |
* ScsiComplete is happy. If the device is busy, Pause for 1/4 |
* second and try again. |
*/ |
if (*stsBytePtr == kScsiStatusBusy) { |
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 we held memory, unhold it now. We ignore UnholdMemory errors: |
* there isn't much we can do about them. Note that this must be |
* done by driver or asynchronous completion routines. |
*/ |
if ((vmHoldMask & kHoldUserBuffer) != 0) |
(void) UnholdMemory(bufferPtr, transferSize); |
if ((vmHoldMask & kHoldCommandBlock) != 0) |
(void) UnholdMemory((Ptr) scsiCommand, cmdBlockLength); |
if ((vmHoldMask & kHoldStack) != 0) |
(void) UnholdMemory(vmProtectedStackBase, kSCSIProtectedStackSize); |
if ((vmHoldMask & kHoldFunction) != 0) |
(void) UnholdMemory(OriginalSCSI, vmFunctionSize); |
/* |
* 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 (status == scPhaseErr |
&& writeToDevice == FALSE |
&& myTransferCount <= transferSize |
&& myTransferCount > 0) |
status = noErr; |
} |
/* |
* Return an artificial error if the device returns a non-zero status: |
* statusErr Caller should issue RequestSense. |
* controlErr Device is busy (self-test?) try again later. |
* ioErr Something is dreadfully wrong. |
* scPhaseErr This may not be a "real" error -- it may mean that the |
* user data buffer was too large for the transfer. (See |
* the check above.) |
* 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 (*stsBytePtr == kScsiStatusGood |
&& messageByte == kScsiStatusCheckCondition) |
*stsBytePtr = kScsiStatusCheckCondition; |
if (status == noErr) { |
switch (*stsBytePtr) { |
case kScsiStatusGood: break; |
case kScsiStatusCheckCondition: status = statusErr; break; |
case kScsiStatusBusy: status = controlErr; break; |
default: status = ioErr; break; |
} |
} |
return (status); |
} |
static void NextFunction(void) { } /* Dummy function for OriginalSCSI size */ |
static 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)); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14