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; 
}