Src/AsyncSCSI.c

/*                                  AsyncSCSI.c                                 */
/*
 * AsyncSCSI.c
 * Copyright © 1992-93 Apple Computer Inc. All Rights Reserved.
 *
 * Talk to the Macintosh SCSI Manager 4.3 using the "new" interface. This is a
 * synchronous call that executes a single SCSI Command on a device. It will
 * automatically calls Request Sense on errors. Note: this is intended as a self-
 * contained illustration of the Asynchronous SCSI Manager and is (intentionally)
 * inefficient in that it does many "bureaucratic" things that would normally
 * be done once when an application or device driver is initialized.
 *
 * Calling Sequence:
 *      OSErr               AsyncSCSI(
 *              DeviceIdent             scsiDevice,
 *              const SCSI_CommandPtr   scsiCommand,
 *              unsigned short          cmdBlockLength,
 *              Boolean                 writeToDevice,
 *              Ptr                     bufferPtr,
 *              unsigned long           transferSize,
 *              unsigned short          scsiHandshake[handshakeDataLength],
 *              SCSI_Sense_Data         *senseDataPtr,
 *              unsigned long           senseDataSize,
 *              unsigned long           completionTimeout,
 *              unsigned short          *stsBytePtr,
 *              unsigned long           *actualTransferCount
 *          );
 * The parameters have the following meaning:
 *
 *  scsiDevice          The SCSI host bus, target, lun that we are talking to.
 *  scsiCommand         The SCSI Command Block (6, 10, or 12 bytes).
 *  cmdBlockLength      The length in bytes of the command block.
 *  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.
 *  scsiHandshake       This will be copied to the scsiHandshake field in the
 *                      SCSIAction parameter block. Useful handshake fields
 *                      include the following:
 *                          0               No handshake: do one blind transfer,
 *                          512,0           Normal disk blind transfer,
 *                          1,511,0         Disk blind transfer if the device
 *                                          can stall before *and* after the
 *                                          first byte in a sector.
 *                      If scsiHandshake is NULL, a polled transfer will be done..
 *  senseDataPtr        If not NULL and the original request failed, this will
 *                      be filled with the result from a Request Sense operation.
 *  senseDataSize       This is the size of the Request Sense data buffer.
 *  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 "cycles" through the TIB
 *                      loop. This should equal the number of bytes transferred if
 *                      transferCount is set to one. (Ignored if NULL.)
 * Return codes:
 *  noErr           normal
 *  unimpErr        SCSI Manager 4.3 is not available: the calling function should
 *                  call the "old" (Inside Mac IV) SCSI Manager.
 *  statusErr       Device returned "Check condition" and SCSI Manager was able
 *                  to successfully issue Request Sense. There is data in the
 *                  Sense Record, but you cannot assume that the original request
 *                  succeeded.
 *  paramErr        Could not determine the command length.
 *  scsi...         Other error
 */
#include <GestaltEqu.h>
#include <Memory.h>
#include <Events.h>
#include <Errors.h>
#include "MacSCSICommand.h"
#ifndef TRUE
#define TRUE        1
#define FALSE       0
#endif
 
/*
 * These globals are defined by SCSISimpleSample.h, and are needed only for
 * testing and debugging.
 */
extern Boolean                  gEnableSelectWithATN;
extern Boolean                  gDoDisconnect;
extern Boolean                  gDontDisconnect;
 
static void                     NextFunction(void);     /* For HoldMemory size  */
/*
 * This should be specified at application/driver startup, and not the
 * inefficient "call Gestalt each time" shown here.
 */
static Boolean                  IsVirtualMemoryRunning(void);
#ifndef CLEAR
/*
 * Cheap 'n dirty memory clear routine.
 */
#define CLEAR(record) do {                              \
        register char   *ptr = (char *) &record;        \
        register long   size;                           \
        for (size = sizeof record; size > 0; --size)    \
            *ptr++ = 0;                                 \
    } while (0)
 
#endif
 
/*
 * 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 kHoldSenseBuffer        0x0008              /* Sense buffer, if any     */
#define kHoldParamBlock         0x0010              /* SCSIExecIOPB             */
 
OSErr                       AsyncSCSI(
        DeviceIdent             scsiDevice,         /* -> Bus/target/LUN        */
        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 short          scsiHandshake[handshakeDataLength],
        SCSI_Sense_Data         *senseDataPtr,      /* Request Sense results    */
        unsigned long           senseDataSize,      /* Request Sense data size  */
        unsigned long           completionTimeout,  /* Ticks to wait            */
        unsigned short          *stsBytePtr,        /* <- status phase byte     */
        unsigned long           *actualTransferCount
    );
 
/*
 * Execute a SCSI command.
 * Returns the final status as noted above.
 */
OSErr
AsyncSCSI(
        DeviceIdent             scsiDevice,         /* -> Bus/target/LUN        */
        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 short          scsiHandshake[handshakeDataLength],
        SCSI_Sense_Data         *senseDataPtr,      /* Request Sense results    */
        unsigned long           senseDataSize,      /* Request Sense data size  */
        unsigned long           completionTimeout,  /* Ticks to wait            */
        unsigned short          *stsBytePtr,        /* <- status phase byte     */
        unsigned long           *actualTransferCount
    )
{
        OSErr                   status;             /* Result code              */
        SCSIBusInquiryPB        busInquiryPB;       /* Used for SCSIBusInquiry  */
        register SCSIExecIOPB   *execIOPBPtr;       /* Used for SCSIAction      */
#define PB                      (*execIOPBPtr)      /* PB references paramBlock */
        unsigned long           execIOPBSize;       /* SCSIAction pb size       */
        Boolean                 enableSelectWithATN; /* Ok to select with ATN?  */
        register short          i;                  /* Move command block index */
        
        /*
         * These two flags are used to record whether the asynchronous SCSI
         * Manager is present on this machine (one flag records whether it is
         * present, the other whether we've tested for presence). This is
         * reasonable for applications. However, this is not sufficient for
         * drivers or other code that may be called before the system has been
         * completely initialized, as the asynchronous SCSI Manager may be
         * installed by a System Extension.
         */
        static Boolean          gHasAsyncSCSIManager;
        static Boolean          gTestedForAsyncSCSIManager;
        /*
         * The following parameters are used to manage virtual memory.
         */
        unsigned short          vmHoldMask;
        unsigned long           vmFunctionSize;
        void                    *vmProtectedStackBase;  /* Last local var   */
/*
 * 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 the asynchronous SCSI Manager exists, we will allocate a parameter
         * block that will be freed when the function exits (if allocation
         * succeeded). In a real application or driver, this would be allocated
         * once, as part of the per-device initialization.
         */
        execIOPBPtr = NULL;
        /*
         * First, make sure that the asynchronous SCSI Manager has been installed.
         * In an application, this may be done once when the application starts.
         * In a driver, this test must be deferred until the Process Manager
         * is running.
         */
        if (gTestedForAsyncSCSIManager == FALSE) {
            gTestedForAsyncSCSIManager = TRUE;
            gHasAsyncSCSIManager = (
                    NGetTrapAddress(_SCSIAtomic, OSTrap)
                    != NGetTrapAddress(_Unimplemented, OSTrap)
                );
        }
        if (gHasAsyncSCSIManager == FALSE) {
            status = unimpErr;
            goto exit;
        }
        /*
         * Allocate a parameter block for this bus. In a production application
         * or driver, this would be done, once, along with other initialization.
         * Note that we always clear the parameter block: the SCSI Manager will
         * fail with an error if some fields it expects to be NULL (such as
         * the queue link) are non-NULL.
         */
        CLEAR(busInquiryPB);
        busInquiryPB.scsiPBLength = sizeof busInquiryPB;
        busInquiryPB.scsiFunctionCode = SCSIBusInquiry;
        busInquiryPB.scsiDevice = scsiDevice;
        SCSIAction((SCSI_PB *) &busInquiryPB);
        status = busInquiryPB.scsiResult;
        if (status != noErr)
            goto exit;
        /*
         * 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 "LUN 1 test ok" flag, and by checking
         * whether the bug was fixed, either by running on later hardware or by
         * installing a System Update.
         */
        enableSelectWithATN =
                gEnableSelectWithATN
                && (busInquiryPB.scsiWeirdStuff & scsiTargetDrivenSDTRSafe) != 0; 
        /*
         * Allocate a parameter block for this request using the size that
         * was returned in the busInquiry parameter block.
         */
        execIOPBSize = busInquiryPB.scsiIOpbSize;
        execIOPBPtr = (SCSIExecIOPB *) NewPtrClear(execIOPBSize);
        if (execIOPBPtr == NULL) {
            status = MemError();
            goto exit;
        }
        /*
         * Setup the parameter block for the user's request.
         */
        PB.scsiPBLength = execIOPBSize;
        PB.scsiFunctionCode = SCSIExecIO;
        PB.scsiTimeout = completionTimeout;
        PB.scsiDevice = scsiDevice;
        PB.scsiCDBLength = cmdBlockLength;
        /*
         * Copy the command block into the SCSI ExecIO Parameter block to
         * centralize everything for debugging. Also, this is one less thing to
         * have to lock into physical memory. Note that BlockMove of six, ten,
         * or twelve bytes is very inefficient. Utility application software
         * should move the bytes using an inline operation, and drivers will
         * either store a pointer to a per-request command data block or
         * explicitly construct the command in the paramater block.
         */
        for (i = 0; i < cmdBlockLength; i++)
            PB.scsiCDB.cdbBytes[i] = scsiCommand->scsi[i];
        /*
         * 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 = scsiSIMQNoFreeze;
        if (bufferPtr == NULL || transferSize == 0)
            PB.scsiFlags |= scsiDirectionNone;
        else {
            /*
             * If the user did not specify a scsi handshake field, select "polled"
             * transfers, otherwise, select "blind."
             */
            PB.scsiTransferType = (scsiHandshake == NULL)
                        ? scsiTransferPolled
                        : scsiTransferBlind;
            PB.scsiDataPtr = (unsigned char *) bufferPtr;
            PB.scsiDataLength = transferSize;
            PB.scsiDataType = scsiDataBuffer;
            PB.scsiFlags |= (writeToDevice) ? scsiDirectionOut : scsiDirectionIn;
            if (scsiHandshake != NULL) {
                for (i = 0; i < handshakeDataLength; i++)
                    PB.scsiHandshake[i] = scsiHandshake[i];
            }
        }
        /*
         * Do we support autosense?
         */
        if (senseDataPtr != NULL && senseDataSize >= 5) {
            senseDataPtr->errorCode = 0;
            PB.scsiSensePtr = (unsigned char *) senseDataPtr;
            PB.scsiSenseLength = senseDataSize;
        }
        else {
            PB.scsiFlags |= scsiDisableAutosense;
        }
        /*
         * Look at the global flags that can be set to configure the asynchronous
         * SCSI Manager - these are for testing, and would typically be set
         * permanently to a particular (device-specific) state by a real application.
         */
        if (enableSelectWithATN == FALSE)   /* Enabled by user and SCSI manager? */
            PB.scsiIOFlags |= scsiDisableSelectWAtn;
        if (gDoDisconnect)
            PB.scsiFlags |= scsiDoDisconnect;
        if (gDontDisconnect)
            PB.scsiFlags |= scsiDontDisconnect;
        /*
         * We are now ready to perform the operation. If virtual memory is active
         * however, we must lock down all memory segments that can be potentially
         * "touched" while the SCSI request is being executed. All of this is
         * needed for applications. For drivers, this can generally be ignored as
         * the driver code and driver-specific resources are stored in the System
         * Heap, which is always "held" in physical memory and the Device Manager
         * locks down data buffers when PBRead/PBWrite are called. However, note
         * that if Control or Status calls require data transfers, the driver
         * must explicitly lock the buffer.
         */
        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) AsyncSCSI;
            status = HoldMemory(AsyncSCSI, 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 parameter block. In this sample, we allocated
                 * the parameter 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) execIOPBPtr, execIOPBSize);
                if (status == noErr)
                    vmHoldMask |= kHoldParamBlock;
            }       
            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 && PB.scsiSensePtr != NULL) {
                /*
                 * Lock down the sense data. A driver would probably allocate one
                 * of these (in a per-transaction buffer) in the System Heap. An
                 * application should lock this before calling the SCSI Manager.
                 */
                status = HoldMemory(PB.scsiSensePtr, PB.scsiSenseLength);
                if (status == noErr)
                    vmHoldMask |= kHoldSenseBuffer;
            }
        }
        /*
         * Finally, call the asynchronous SCSI Manager. SCSIAction is synchronous
         * because we did not specify a completion routine.
         */
        if (status == noErr) {
            status = SCSIAction((SCSI_PB *) &PB);
            if (status == noErr)
                status = PB.scsiResult;
        }
        /*
         * 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.
         */
exit:   if ((vmHoldMask & kHoldSenseBuffer) != 0)
            (void) UnholdMemory(PB.scsiSensePtr, PB.scsiSenseLength);
        if ((vmHoldMask & kHoldUserBuffer) != 0)
            (void) UnholdMemory(bufferPtr, transferSize);
        if ((vmHoldMask & kHoldParamBlock) != 0)
            (void) UnholdMemory((Ptr) execIOPBPtr, execIOPBSize);
        if ((vmHoldMask & kHoldStack) != 0)
            (void) UnholdMemory(vmProtectedStackBase, kSCSIProtectedStackSize);
        if ((vmHoldMask & kHoldFunction) != 0)
            (void) UnholdMemory(AsyncSCSI, vmFunctionSize);
        /*
         * Now, look at the result of the operation.
         */
        if (execIOPBPtr != NULL) {
            /*
             * Recover some data to return to the user. 
             */
            if (stsBytePtr != NULL)
                *stsBytePtr = PB.scsiSCSIstatus;
            if (actualTransferCount != NULL)
                *actualTransferCount = transferSize - PB.scsiDataResidual;
            /*
             * 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 Request Sense or other
             * administrative commands with variable-length result blocks.
             * Note that the user can then recover the actual block length by
             * examining the actualTransferCount parameter.
             */
            if (status == scsiDataRunError              /* Over/underrun error  */
             && writeToDevice == FALSE                  /* But we're reading    */
             && actualTransferCount != NULL             /* And user wants count */
             && (*actualTransferCount) <= transferSize  /* And its a short read */
             && (*actualTransferCount) > 0)             /* And some data 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;
            DisposePtr((Ptr) execIOPBPtr);
        }
        return (status);
#undef PB
}
 
static void NextFunction(void) { }  /* Dummy function for AsyncSCSI 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));
}