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.
SCSIFindNextDevice.c
/* |
File: SCSIFindNextDevice.c |
Contains: Find all SCSI devices. The alogrithm first asks the SCSI Manager for the |
number of buses, then loops through each bus for each device and LUN. |
old SCSI Manager. This is made complex by the flexible SCSI Manager 4.3 |
architecture: it is possible for the asynchronous SCSI Manager to only |
be available on a third-party bus interface, for example. Because of this, |
we must always scan the bus using the original SCSI Manager even if the |
asynchronous manager is present. |
Written by: Martin Minow. |
Copyright: Copyright © 1992-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
7/14/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
*/ |
#include <Errors.h> |
#include <Events.h> |
#include <Memory.h> |
#include <OSUtils.h> |
#include <Traps.h> |
#include <Types.h> |
#include "SCSIFindDevices.h" |
#ifndef FALSE |
#define FALSE 0 |
#define TRUE 1 |
#endif |
/* |
* All functions use the same formal parameter to access the "global" record. |
*/ |
#define REC (*scsiFindDevicesPtr) |
/* |
* These are the states that control the overall process. |
*/ |
enum { |
kStateInitialize = 0, |
kStateNextBus, |
kStateNextTarget, |
kStateNextLUN, |
kStateCheckForHardWired, |
kStateNextHardWiredTarget, |
kStateNextHardWiredLUN, |
kStateLastStateWithoutATrailingCommaBecauseWeCareAboutYou |
}; |
/* |
* These are the commands that may be sent to the device. Request Sense is only |
* sent by the original SCSI Manager. |
*/ |
#define kScsiCmdInquiry 0x12 |
#define kScsiCmdRequestSense 0x03 |
/* |
* These are the device types that SCSI knows about. |
*/ |
enum { |
kScsiDevTypeDirect = 0, |
kScsiDevTypeSequential, |
kScsiDevTypePrinter, |
kScsiDevTypeProcessor, |
kScsiDevTypeWorm, /* Write-once, read multiple */ |
kScsiDevTypeCDROM, |
kScsiDevTypeScanner, |
kScsiDevTypeOptical, |
kScsiDevTypeChanger, |
kScsiDevTypeComm, |
kScsiDevTypeGraphicArts0A, |
kScsiDevTypeGraphicArts0B, |
kScsiDevTypeFirstReserved, /* Start of reserved sequence */ |
kScsiDevTypeUnknownOrMissing = 0x1F, |
kScsiDevTypeMask = 0x1F |
}; |
/* |
* These are device type modifiers. We need them to distinguish between "unknown" |
* and "missing" devices. |
*/ |
enum { |
kScsiDevTypeQualifierConnected = 0x00, /* Exists and is connected */ |
kScsiDevTypeQualifierNotConnected = 0x20, /* Logical unit exists */ |
kScsiDevTypeQualifierReserved = 0x40, |
kScsiDevTypeQualifierMissing = 0x60, /* No such logical unit */ |
kScsiDevTypeQualifierVendorSpecific = 0x80, /* Other bits are unspecified */ |
kScsiDevTypeQualifierMask = 0xE0 |
}; |
#define kScsiDevTypeMissing \ |
(kScsiDevTypeUnknownOrMissing | kScsiDevTypeQualifierMissing) |
/* |
* This is the data that is returned after a GetExtendedStatus request. The |
* errorCode gives a general indication of the error, which may be qualified by |
* the additionalSenseCode and additionalSenseQualifier fields. These may be |
* device (vendor) specific values, however. The info[] field contains additional |
* information. For a media error, it contains the failing logical block number |
* (most-significant byte first). |
*/ |
struct SCSI_Sense_Data { /* Request Sense result */ |
unsigned char errorCode; /* 0 Class code, valid lbn */ |
unsigned char segmentNumber; /* 1 Segment number */ |
unsigned char senseKey; /* 2 Sense key and flags */ |
unsigned char info[4]; |
unsigned char additionalSenseLength; |
unsigned char reservedForCopy[4]; |
unsigned char additionalSenseCode; |
unsigned char additionalSenseQualifier; |
unsigned char fruCode; /* Field replacable unit code */ |
unsigned char senseKeySpecific[2]; |
unsigned char additional[101]; |
}; |
typedef struct SCSI_Sense_Data SCSI_Sense_Data; |
/* |
* The high-bit of errorCode signals whether there is a logical |
* block. The low value signals whether there is a valid sense |
*/ |
#define kScsiSenseHasLBN 0x80 /* Logical block number set */ |
#define kScsiSenseInfoValid 0x70 /* Is sense key valid? */ |
#define kScsiSenseInfoMask 0x70 /* Mask for sense info */ |
/* |
* These bits may be set in the sense key |
*/ |
#define kScsiSenseKeyMask 0x0F |
#define kScsiSenseILI 0x20 /* Illegal logical Length */ |
#define kScsiSenseEOM 0x40 /* End of media */ |
#define kScsiSenseFileMark 0x80 /* End of file mark */ |
/* |
* SCSI sense codes. (Returned after request sense). |
*/ |
#define kScsiSenseNone 0x00 /* No error */ |
#define kScsiSenseRecoveredErr 0x01 /* Warning */ |
#define kScsiSenseNotReady 0x02 /* Device not ready */ |
#define kScsiSenseMediumErr 0x03 /* Device medium error */ |
#define kScsiSenseHardwareErr 0x04 /* Device hardware error */ |
#define kScsiSenseIllegalReq 0x05 /* Illegal request for dev. */ |
#define kScsiSenseUnitAtn 0x06 /* Unit attention (not err) */ |
#define kScsiSenseDataProtect 0x07 /* Data protection */ |
#define kScsiSenseBlankCheck 0x08 /* Tape-specific error */ |
#define kScsiSenseVendorSpecific 0x09 /* Vendor-specific error */ |
#define kScsiSenseCopyAborted 0x0a /* Copy request cancelled */ |
#define kScsiSenseAbortedCmd 0x0b /* Initiator aborted cmd. */ |
#define kScsiSenseEqual 0x0c /* Comparison equal */ |
#define kScsiSenseVolumeOverflow 0x0d /* Write past end mark */ |
#define kScsiSenseMiscompare 0x0e /* Comparison failed */ |
#define kScsiSenseCurrentErr 0x70 |
#define kScsiSenseDeferredErr 0x71 |
/* |
* 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) |
/* |
* These private routines do all the work. |
*/ |
static void CheckForAsyncSCSI( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
); |
static void GetHighHostBusAdaptor( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
); |
static OSErr SetupForNextSCSIBus( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
); |
static Boolean IsRegisteredAsynchDevice( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
); |
static Boolean CheckForDevice( |
register SCSIFindDevicesPtr scsiFindDevicesPtr, |
Boolean useAsyncManager |
); |
static OSErr OriginalSCSI( |
unsigned short targetID, |
Ptr command, |
Ptr resultData, |
unsigned long resultSize, |
long *actualTransferSize |
); |
static void ClearMemory( |
void *recordPtr, |
unsigned long recordLength |
); |
#define CLEAR(record) ClearMemory(&record, sizeof record); |
/* |
* This is the function that does all the work. Each time it is called, it finds |
* the next device (next logical unit, next target, next bus). The overall design |
* uses a state machine that continues within the function until one of three |
* things happen: it finds a device, it reaches the end of the device sequence, |
* or it gets an error. |
*/ |
OSErr |
SCSIFindNextDevice( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
) |
{ |
OSErr status; |
Boolean doNextState; |
if (REC.deviceID.bus == 0xFF) |
REC.state = kStateInitialize; |
/* |
* This is the overall state machine that processes application calls. |
* Code in this section only organizes the lower-level routines. Many of |
* the tests within the state switch change state. In all cases, they set |
* doNextState TRUE and exit the switch. This, eventually, continues at |
* the while statement which re-runs the state switch. A state-machine |
* organization is not necessarily the best (and goto's would be slightly |
* more efficient), but looping through a common switch statement |
* simplified debugging. |
*/ |
doNextState = TRUE; |
while (doNextState) { |
doNextState = FALSE; |
switch (REC.state) { |
case kStateInitialize: |
/* |
* Initialization: check for the presence of the asynchronous |
* SCSI Manager and get the last host bus. Then start with the |
* first bus. |
*/ |
REC.scsiExecIOPB = NULL; |
REC.execIOPBSize = 0; |
CheckForAsyncSCSI(scsiFindDevicesPtr); |
GetHighHostBusAdaptor(scsiFindDevicesPtr); |
REC.state = kStateNextBus; |
doNextState = TRUE; |
break; |
case kStateNextBus: |
/* |
* Look for the first/next bus. If we have a bus, we do some |
* bus-specific initializations including, primarily, creating |
* the SCSI parameter block. |
*/ |
if (REC.deviceID.bus == 0xFF) |
REC.deviceID.bus = 0; /* Do the first bus */ |
else { |
++REC.deviceID.bus; /* Do the next bus */ |
} |
if (REC.deviceID.bus <= REC.lastHostBus) { |
/* |
* We have another bus to test. Make a SCSI parameter block |
* that is properly sized for this bus. |
*/ |
status = SetupForNextSCSIBus(scsiFindDevicesPtr); |
if (status == noErr) { |
/* |
* This bus exists: check its targets. |
*/ |
REC.deviceID.targetID = 0xFF; |
REC.state = kStateNextTarget; |
doNextState = TRUE; |
} |
else if (status == eofErr) { /* eofErr is private */ |
/* |
* This bus does not exist. There may be gaps in the bus |
* sequence if a third-party SCSI adaptor installs its |
* own, private, SCSI Manager 4.3 on a machine that does |
* not otherwise support asynchronous SCSI. Just continue |
* with the next bus until we reach REC.lastHostBus. |
*/ |
REC.state = kStateNextBus; |
doNextState = TRUE; |
} |
else { |
/* |
* Oops: this is a serious error. Since doNextState is |
* FALSE, we will exit the while loop and return to the |
* caller with a serious error. |
*/ |
} |
} |
else { |
/* |
* We've examined all of the buses accessable through SCSI |
* Manager 4.3. Check for third-party devices that are only |
* accessable through the original SCSI Manager (because they |
* patch the SCSI Manager traps). |
*/ |
REC.deviceID.targetID = 0xFF; |
REC.state = kStateCheckForHardWired; |
doNextState = TRUE; |
} |
break; |
case kStateNextTarget: |
/* |
* Look for the next device on this bus. If we run off the end, |
* cycle back through the switch to look for the next bus. |
*/ |
if (REC.deviceID.targetID == 0xFF) |
REC.deviceID.targetID = 0; /* Do the first target */ |
else { |
++REC.deviceID.targetID; /* Do the next target */ |
} |
/* |
* REC.initiatorID is set on a bus-by-bus basis. We cannot assume |
* that it is always equal to seven. Skip over the initiator. |
* REC.maxTargetID is normally seven; but it's provided to us |
* by the SCSI Manager, so we'll use that value. |
*/ |
if (REC.deviceID.targetID == REC.initiatorID) |
++REC.deviceID.targetID; |
if (REC.deviceID.targetID > REC.maxTargetID) { |
/* |
* We've done all targets for this bus. Go on to the next bus. |
*/ |
REC.state = kStateNextBus; |
} |
else { |
/* |
* New target: do the first logical unit for this bus/target. |
*/ |
REC.deviceID.LUN = 0xFF; |
REC.state = kStateNextLUN; |
} |
doNextState = TRUE; |
break; |
case kStateNextLUN: |
/* |
* We have a host bus and target ID. Cycle through the logical |
* units for this target ID. We will always look at LUN zero. |
* When we reach the end, the switch will take us to the next |
* target ID. |
*/ |
if (REC.deviceID.LUN == 0xFF) |
REC.deviceID.LUN = 0; /* Do the first LUN */ |
else { |
++REC.deviceID.LUN; /* Do the next LUN */ |
} |
if (REC.deviceID.LUN > REC.maxBusLUN) { |
REC.state = kStateNextTarget; |
doNextState = TRUE; |
} |
else { |
/* |
* Look for this LUN. Failures look for the next target. |
*/ |
if (CheckForDevice(scsiFindDevicesPtr, REC.useAsynchSCSI)) |
status = noErr; /* This one exists */ |
else { |
/* |
* This target/LUN was not found. Go on to the next |
* SCSI bus ID. Note that we presume that there are no |
* gaps in the LUN sequence. |
*/ |
REC.state = kStateNextTarget; |
doNextState = TRUE; |
} |
} |
break; |
case kStateCheckForHardWired: |
/* |
* We have found all devices that can be accessed through the |
* asynchronous SCSI Manager. Some third-party hardware interfaces |
* do not use the asynchronous SCSI Manager, but patch original |
* SCSI traps. If the asynchronous SCSI Manager is not available, |
* we don't have to continue here (the above code found all |
* devices. In this segment, we must hard-wire the initiator ID |
* to seven, as there is no supported way to determine it from the |
* SCSI Manager or operating system. |
*/ |
if (REC.isAsyncSCSIPresent == FALSE) |
status = eofErr; /* All done, thank you */ |
else { |
REC.deviceID.bus = 0; /* Set the hard-wired data */ |
REC.initiatorID = 7; |
REC.maxTargetID = 7; |
REC.state = kStateNextHardWiredTarget; |
REC.deviceID.targetID = 0xFF; |
doNextState = TRUE; |
} |
break; |
case kStateNextHardWiredTarget: |
/* |
* Look at the next target on the built-in bus. |
*/ |
if (REC.deviceID.targetID == 0xFF) |
REC.deviceID.targetID = 0; |
else { |
++REC.deviceID.targetID; |
} |
if (REC.deviceID.targetID == REC.initiatorID) |
++REC.deviceID.targetID; |
if (REC.deviceID.targetID >= REC.maxTargetID) |
status = eofErr; /* All done, thank you */ |
else { |
REC.deviceID.LUN = 0xFF; |
REC.state = kStateNextHardWiredLUN; |
doNextState = TRUE; |
} |
break; |
case kStateNextHardWiredLUN: |
/* |
* Look for the next LUN on the built-in bus. |
*/ |
if (REC.deviceID.LUN == 0xFF) { |
REC.maxBusLUN = REC.maxLUN; |
REC.deviceID.LUN = 0; /* Do the first LUN */ |
} |
else { |
++REC.deviceID.LUN; /* Do the next LUN */ |
} |
if (REC.deviceID.LUN > REC.maxBusLUN) { |
REC.state = kStateNextHardWiredTarget; |
doNextState = TRUE; |
} |
else { |
/* |
* Look for this LUN. failures look for the next target. |
* IsRegisteredAsynchDevice is TRUE if this device did not |
* register with the asynchronous SCSI Manager. |
*/ |
if (IsRegisteredAsynchDevice(scsiFindDevicesPtr)) { |
/* |
* Since we know about this device, it would have been |
* found by the first pass through the asynchronous |
* SCSI Manager. So, we don't need to look at it here. |
*/ |
REC.state = kStateNextHardWiredLUN; |
doNextState = TRUE; |
} |
else if (CheckForDevice(scsiFindDevicesPtr, FALSE)) { |
/* |
* This device was not registered, but it does exist |
* if we check using the original SCSI Manager. Probably |
* because someone patched the SCSI Manager traps. |
* Return this to the caller. |
*/ |
status = noErr; /* This one exists */ |
} |
else { |
/* |
* This target/LUN was not found. Go on to the next |
* SCSI bus ID. Note that we presume that there are no |
* gaps in the LUN sequence. |
*/ |
REC.state = kStateNextHardWiredTarget; |
doNextState = TRUE; |
} |
} |
break; |
default: /* Illegal state */ |
status = abortErr; /* Can't happen */ |
break; |
} /* State switch */ |
} /* While doNextState */ |
if (status != noErr) { |
/* |
* We've done everything that we can. Re-initialize and release |
* our saved parameter block. |
*/ |
REC.deviceID.bus = 0xFF; /* Force reinitialize */ |
if (REC.scsiExecIOPB != NULL) { |
DisposePtr((Ptr) REC.scsiExecIOPB); |
REC.scsiExecIOPB = NULL; |
} |
} |
return (status); |
} |
/* |
* Check whether the Asynchronous SCSI trap is available. |
* Set REC.isAsyncSCSI appropriately. |
*/ |
static void |
CheckForAsyncSCSI( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
) |
{ |
TrapType trapType; |
short theTrap; |
/* |
* TrapAvailable (see Inside Mac VI 3-8) |
*/ |
#define NumToolboxTraps() ( \ |
(NGetTrapAddress(_InitGraf, ToolTrap) \ |
== NGetTrapAddress(0xAA6E, ToolTrap)) \ |
? 0x200 : 0x400 \ |
) |
#define GetTrapType(theTrap) ( \ |
(((theTrap) & 0x0800) != 0) ? ToolTrap : OSTrap \ |
) |
theTrap = _SCSIAtomic; |
trapType = GetTrapType(theTrap); |
if (trapType == ToolTrap) { |
theTrap &= 0x07FF; |
if (theTrap >= NumToolboxTraps()) |
theTrap = _Unimplemented; |
} |
REC.isAsyncSCSIPresent = ( |
NGetTrapAddress(theTrap, trapType) |
!= NGetTrapAddress(_Unimplemented, ToolTrap) |
); |
#undef NumToolboxTraps |
#undef GetTrapType |
} |
/* |
* If we have the asynchronous SCSI Manager, find out how many buses are present |
* on this system. If we're limited to the original SCSI Manager, force a "single |
* bus" scan, since the function that actually calls the SCSI Manager ignores the |
* bus if the asynchronous manager is not present. |
*/ |
static void |
GetHighHostBusAdaptor( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
) |
{ |
OSErr status; |
SCSIBusInquiryPB busInquiryPB; |
#define PB (busInquiryPB) |
if (REC.isAsyncSCSIPresent == FALSE) |
REC.lastHostBus = 0; |
else { |
CLEAR(PB); |
PB.scsiPBLength = sizeof PB; |
PB.scsiFunctionCode = SCSIBusInquiry; |
PB.scsiDevice.bus = 0xFF; |
status = SCSIAction((SCSI_PB *) &PB); |
REC.lastHostBus = PB.scsiHiBusID; |
} |
#undef PB |
} |
/* |
* Start to check a new SCSI bus. If we find a bus, allocate (or re-allocate) |
* the SCSIExecIO command block. Note that it is possible to have buses with |
* no devices, and gaps in the bus sequence. For example, in a Macintosh with |
* two buses (such as the Quadra 950 or PowerMac 8100), you can have a bus |
* with no devices.Also, if you install a third-party bus adaptor that supports |
* the asynchronous SCSI Manager on a machine with two buses, it would be |
* assigned bus 2 (with buses 0 and 1 referencing the internal system buses). |
* In this case, a system could have no devices on bus 0 or 1. |
*/ |
static OSErr |
SetupForNextSCSIBus( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
) |
{ |
OSErr status; |
SCSIBusInquiryPB busInquiryPB; |
#define PB (busInquiryPB) |
/* |
* If we don't support asynchronous SCSI, nothing happens here. |
*/ |
if (REC.isAsyncSCSIPresent == FALSE) { |
REC.useAsynchSCSI = FALSE; |
REC.initiatorID = 7; |
REC.maxTargetID = 7; |
REC.maxBusLUN = REC.maxLUN; |
status = noErr; |
} |
else { |
CLEAR(PB); |
PB.scsiPBLength = sizeof PB; |
PB.scsiFunctionCode = SCSIBusInquiry; |
PB.scsiDevice.bus = REC.deviceID.bus; /* Other values are zero */ |
status = SCSIAction((SCSI_PB *) &PB); |
if (status == noErr) { |
REC.useAsynchSCSI = TRUE; /* Asynch works on this bus */ |
REC.initiatorID = PB.scsiInitiatorID; |
REC.maxTargetID = PB.scsiMaxTarget; |
/* |
* If we are running on a Quadra 840-AV with a CD300, the |
* Macintosh will hang if it tries to access LUN 1. We check for |
* this problem in two ways: by examining a global maxLUN flag, |
* and by checking whether the bug was fixed, either by running |
* on later hardware or by installing a System Update. |
*/ |
REC.enableATN = |
(busInquiryPB.scsiWeirdStuff & scsiTargetDrivenSDTRSafe) != 0; |
REC.maxBusLUN = (REC.enableATN) ? REC.maxLUN : 0; |
/* |
* Allocate a parameter block for this request using the size that |
* was returned in the busInquiry parameter block. |
*/ |
if (REC.execIOPBSize != PB.scsiIOpbSize) { |
if (REC.scsiExecIOPB != NULL) { |
DisposePtr((Ptr) REC.scsiExecIOPB); |
REC.scsiExecIOPB = NULL; |
} |
REC.scsiExecIOPB = |
(SCSIExecIOPB *) NewPtrClear(PB.scsiIOpbSize); |
if (REC.scsiExecIOPB == NULL) |
status = MemError(); |
else { |
REC.execIOPBSize = PB.scsiIOpbSize; |
status = noErr; |
} |
} |
} |
else if (PB.scsiDevice.bus == 0) { |
/* |
* If bus zero is not registered, it must be accessed via the |
* original API. Set the initiatorID to the default 7 - there is |
* no supported mechanism for determining the actual id (it's |
* hidden inside the parameter RAM, but cannot be set by a |
* published mechanism). |
*/ |
REC.useAsynchSCSI = FALSE; |
REC.initiatorID = 7; |
REC.maxTargetID = 7; |
REC.maxBusLUN = REC.maxLUN; |
status = noErr; |
} |
else { |
/* |
* This is a problem: this bus cannot be accessed unless it has |
* been registered. For example, the second bus of a Quadra |
* 950 cannot be accessed if the SCSI Manager extension is not |
* installed and a third-party asynchronous SCSI Manager card |
* is installed as bus 2. Return a private error to skip this bus. |
*/ |
status = eofErr; |
} |
} |
return (status); |
#undef PB |
} |
/* |
* This is called if the asynchronous SCSI Manager is present to sweep up any |
* third party devices that did not register with the asynchronous SCSI Manager. |
*/ |
static Boolean |
IsRegisteredAsynchDevice( |
register SCSIFindDevicesPtr scsiFindDevicesPtr |
) |
{ |
OSErr status; |
SCSIGetVirtualIDInfoPB scsiGetVirtualIDInfo; |
CLEAR(scsiGetVirtualIDInfo); |
scsiGetVirtualIDInfo.scsiPBLength = sizeof scsiGetVirtualIDInfo; |
scsiGetVirtualIDInfo.scsiOldCallID = REC.deviceID.targetID; |
status = SCSIAction((SCSI_PB *) &scsiGetVirtualIDInfo); |
return (status == noErr); |
} |
/* |
* This is the only function that sends SCSI commands to a device. It will send a |
* Device Inquiry and, if Check Condition is returned, issue Request Sense. |
*/ |
static Boolean |
CheckForDevice( |
register SCSIFindDevicesPtr scsiFindDevicesPtr, |
Boolean useAsyncManager |
) |
{ |
OSErr status; |
OSErr requestSenseStatus; |
SCSI_Sense_Data senseData; |
#define SENSE (senseData) |
Boolean result; |
/* |
* For the old SCSI Manager only. |
*/ |
unsigned char command[6]; |
long tempLong; |
if (useAsyncManager) { |
#define PB (*REC.scsiExecIOPB) |
/* |
* Setup the parameter block for the user's request. |
*/ |
PB.scsiPBLength = REC.execIOPBSize; |
PB.scsiFunctionCode = SCSIExecIO; |
PB.scsiTimeout = 1000; |
PB.scsiDevice = REC.deviceID; |
PB.scsiCDBLength = 6; |
PB.scsiCDB.cdbBytes[0] = kScsiCmdInquiry; |
PB.scsiCDB.cdbBytes[4] = sizeof REC.inquiry; |
/* |
* Stuff the LUN into the command for SCSI-1 devices. |
*/ |
/* PB.scsiCDB.cdbBytes[1] &= ~0xE0; -- already zero */ |
PB.scsiCDB.cdbBytes[1] |= (REC.deviceID.LUN & 0x03) << 5; |
/* |
* Specify the transfer direction, if any, and setup the other SCSI |
* operation flags. scsiSIMQNoFreeze prevents the SCSI Manager from |
* blocking further operation if an error is detected. |
*/ |
PB.scsiFlags = scsiTransferPolled; |
PB.scsiDataPtr = (unsigned char *) &REC.inquiry; |
PB.scsiDataLength = sizeof REC.inquiry; |
PB.scsiDataType = scsiDataBuffer; |
PB.scsiSensePtr = (unsigned char *) &senseData; |
PB.scsiSenseLength = sizeof senseData; |
PB.scsiFlags = scsiSIMQNoFreeze | scsiDirectionIn | scsiDontDisconnect; |
if (REC.enableATN == FALSE) |
PB.scsiIOFlags |= scsiDisableSelectWAtn; |
status = SCSIAction((SCSI_PB *) &PB); |
if (status == noErr) |
status = PB.scsiResult; |
/* |
* Note: scsiDataRunError is issued if our transfer request was larger |
* or smaller than the actual transfer length. We need to examine the |
* actual transfer sizes to see how to handle this error. This is |
* not necessarily complete or correct. The intent here is to supress |
* the transfer length error when executing Device Inquiry or other |
* administrative commands with variable-length result blocks. |
*/ |
REC.actualInquirySize = PB.scsiDataLength - PB.scsiDataResidual; |
if (status == scsiDataRunError /* Over/underrun error */ |
&& REC.actualInquirySize > 0) /* And its a short read */ |
status = noErr; /* If so, ignore error */ |
/* |
* If the device issued Check Condition and the SCSI Manager was able |
* to retrieve a Request Sense datum, change the error to our private |
* "Check Condition" status. |
*/ |
if (status == scsiNonZeroStatus |
&& (PB.scsiResultFlags & scsiAutosenseValid) != 0) { |
status = statusErr; |
requestSenseStatus = noErr; |
} |
#undef PB |
} |
else { |
CLEAR(command); |
command[0] = kScsiCmdInquiry; |
command[4] = sizeof REC.inquiry; |
/* |
* Stuff the LUN into the command. |
*/ |
/* command[1] &= ~0xE0; -- already zero */ |
command[1] |= (REC.deviceID.LUN & 0x03) << 5; |
status = OriginalSCSI( |
REC.deviceID.targetID, |
(Ptr) &command, |
(Ptr) &REC.inquiry, |
sizeof REC.inquiry, |
&REC.actualInquirySize |
); |
if (status == statusErr) { |
/* |
* Check condition |
*/ |
CLEAR(command); |
command[0] = kScsiCmdRequestSense; |
command[4] = sizeof senseData; |
/* |
* Stuff the LUN into the command. |
*/ |
/* command[1] &= ~0xE0; -- already zero */ |
command[1] |= (REC.deviceID.LUN & 0x03) << 5; |
requestSenseStatus = OriginalSCSI( |
REC.deviceID.targetID, |
(Ptr) &command, |
(Ptr) &senseData, |
sizeof senseData, |
&tempLong |
); |
if (status == noErr) |
status = statusErr; |
} |
} |
/* |
* Look at the result. |
*/ |
switch (status) { |
case noErr: |
if (REC.inquiry.devType == kScsiDevTypeMissing) |
result = FALSE; |
else { |
result = TRUE; /* Normal successful return */ |
} |
break; |
case statusErr: |
/* |
* The target returned Check Condition. We need to look at the sense |
* data, if any, to distinguish between an "offline" but present device, |
* and a non-existant logical unit. Note: some drives return Check |
* Condition, and "no sense error" if we try to access an incorrect |
* logical unit. This might reasonably be remapped as "illegal request. |
*/ |
if (requestSenseStatus != noErr |
|| (SENSE.errorCode & kScsiSenseInfoMask) != kScsiSenseInfoValid) |
result = FALSE; |
else { |
switch (SENSE.senseKey & kScsiSenseKeyMask) { |
case kScsiSenseIllegalReq: |
result = FALSE; |
break; |
default: |
/* |
* Wierd: some drives seem to set the End of Media bit |
* in the sense key if an invalid LUN is selected. |
*/ |
if ((SENSE.senseKey & kScsiSenseEOM) != 0) |
result = FALSE; |
else { |
result = TRUE; |
} |
break; |
} |
} |
break; |
default: /* Other errors == no such device */ |
result = FALSE; |
break; |
} |
return (result); |
} |
/* |
* This is a very limited wrapper for the original SCSI Manager that can handle |
* Device Inquiry and Request Sense (only). |
*/ |
static OSErr |
OriginalSCSI( |
unsigned short targetID, |
Ptr command, |
Ptr resultData, |
unsigned long resultSize, |
long *actualTransferSize |
) |
{ |
OSErr status; |
OSErr completionStatus; |
short totalTries; /* Get/Select retries */ |
short getTries; /* Get retries */ |
short iCount; /* Bus free counter */ |
unsigned long watchdog; /* Timeout after this */ |
SCSIInstr tib[4]; |
short messageByte; |
short statusByte; |
*actualTransferSize = 0; |
tib[0].scOpcode = scInc; |
tib[0].scParam1 = (unsigned long) resultData; |
tib[0].scParam2 = 1; |
tib[1].scOpcode = scAdd; |
tib[1].scParam1 = (unsigned long) actualTransferSize; |
tib[1].scParam2 = 1; |
tib[2].scOpcode = scLoop; |
tib[2].scParam1 = (-2 * sizeof (SCSIInstr)); |
tib[2].scParam2 = resultSize; |
tib[3].scOpcode = scStop; |
tib[3].scParam1 = 0; |
tib[3].scParam2 = 0; |
/* |
* 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) command, 6); |
if (status == noErr) |
status = SCSIRead((Ptr) tib); |
/* |
* SCSIComplete "runs" the bus-phase algorithm until the bitter end, |
* returning the status and command-completion message bytes.. |
*/ |
completionStatus = SCSIComplete(&statusByte, &messageByte, 60L); |
/* |
* 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 (statusByte == 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: /* |
* Ignore phase errors if the buffer was large enough |
*/ |
if (status == scPhaseErr && *actualTransferSize <= resultSize) |
status = noErr; |
/* |
* Return statusErr if the device returns Check Condition: |
* 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 (statusByte == kScsiStatusGood |
&& messageByte == kScsiStatusCheckCondition) |
statusByte = kScsiStatusCheckCondition; |
if (status == noErr && statusByte == kScsiStatusCheckCondition) |
status = statusErr; |
return (status); |
} |
static void |
ClearMemory( |
void *recordPtr, |
register unsigned long recordLength |
) |
{ |
register char *ptr; |
for (ptr = (char *) recordPtr; recordLength > 0; --recordLength) |
*ptr++ = 0; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14