Logo Search packages:      
Sourcecode: mac-fdisk version File versions  Download package

DoSCSICommandWithSense.c

/*
 * DoScsiCommand.c
 *
 * This is the common entry to the original and asynchronous SCSI Manager calls:
 * if the asynchronous SCSI Manager is requested, it calls it. Otherwise, it
 * calls the original SCSI Manager and executes Request Sense if necessary.
 *
 * This function returns "autosense" in the SCSI_Sense_Data area. This will
 * be formatted in the senseMessage string.
 */

/*
 * Copyright 1992, 1993, 1997 by Apple Computer, Inc.
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */
#include "SCSIStuff.h"

OSErr                       OriginalSCSI(
      DeviceIdent             scsiDevice,
      const SCSI_CommandPtr   scsiCommand,
      UInt8                   scsiCommandLen,
      Ptr                     dataBuffer,
      ByteCount               dataLength,
      UInt32                  scsiFlags,
      ByteCount               *actualTransferCount,
      UInt8                   *scsiStatusByte
    );
UInt16                      GetCommandLength(
      const SCSI_CommandPtr   cmdPtr
    );
/*
 * This is the maximum number of times we try to grab the SCSI Bus
 */
#define kMaxSCSIRetries         40                  /* 10 seconds, 4 times/sec  */
#define kSCSICommandTimeout     (5 * 1000L)         /* Five seconds             */

/*
 * 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)
Boolean                     IsVirtualMemoryRunning(void);

/*
 * This returns TRUE if the command failed with "Illegal Request." We need this
 * so we can ignore LogSense or ReadDefectData if the device doesn't support
 * these functions.
 */
Boolean
IsIllegalRequest(
      OSErr                   scsiStatus,
      const SCSI_Sense_Data   *senseDataPtr
    )
{
      Boolean                 result;
#define SENSE   (*senseDataPtr)

      result = FALSE;
      if (scsiStatus == scsiNonZeroStatus
       && (SENSE.senseKey & kScsiSenseKeyMask) == kScsiSenseIllegalReq
       && SENSE.additionalSenseLength >= 4) {
          switch ((SENSE.additionalSenseCode << 8) | SENSE.additionalSenseQualifier) {
          case 0x0000:
          case 0x2000:
          case 0x2022:    /* Obsolete */
            result = TRUE;
            break;
          default:
            break;
          }
      }
      return (result);
#undef SENSE
}

/*
 * This returns TRUE if the command failed with Device Not Ready (No Media Present)
 */
Boolean
IsNoMedia(
      OSErr                   scsiStatus,
      const SCSI_Sense_Data   *senseDataPtr
    )
{
      Boolean                 result;
#define SENSE   (*senseDataPtr)

      result = FALSE;
      if (scsiStatus == scsiNonZeroStatus
       && (SENSE.senseKey & kScsiSenseKeyMask) == kScsiSenseNotReady
       && SENSE.additionalSenseLength >= 4) {
          switch ((SENSE.additionalSenseCode << 8) | SENSE.additionalSenseQualifier) {
          case 0x0000:
          case 0x3A00:
            result = TRUE;
            break;
          default:
            break;
          }
      }
      return (result);
#undef SENSE
}

OSErr
DoOriginalSCSICommand(
      DeviceIdent             scsiDevice,
      const SCSI_CommandPtr   theSCSICommand,
      unsigned short          cmdBlockLength,
      Ptr                     dataBuffer,
      ByteCount               dataLength,
      UInt32                  scsiFlags,
      ByteCount               *actualTransferCount,
      SCSI_Sense_Data         *sensePtr
    );

/*
 * Do one SCSI Command. If the device returns Check Condition, issue Request Sense
 * (original SCSI Manager only) and interpret the sense data. The original SCSI
 * command status is in SCB.status. If it is statusErr or scsiNonZeroStatus,
 * the sense data is in SCB.sense and the Request Sense status is in
 * SCB.requestSenseStatus.
 *
 * If sensePtr[0] is non-zero, there is a message.
 */
OSErr
DoSCSICommand(
      DeviceIdent             scsiDevice,
      ConstStr255Param        currentAction,
      const SCSI_CommandPtr   callerSCSICommand,
      Ptr                     dataBuffer,
      ByteCount               dataLength,
      UInt32                  scsiFlags,
      ByteCount               *actualTransferCount,
      SCSI_Sense_Data         *sensePtr,
      StringPtr               senseMessage
    )
{
      OSErr                   status;
      SCSI_Command            theSCSICommand;
      unsigned short          cmdBlockLength;
            
//      SpinSpinner(&gCurrentInfoPtr->spinnerRecord);
//      ShowProgressAction(currentAction);
      /*
       * Store the LUN information in the command block - this is needed
       * for devices that only examine the command block for LUN values.
       * (On SCSI-II, the asynchronous SCSI Manager also includes the
       * LUN in the identify message).
       */
      theSCSICommand = *callerSCSICommand;
      theSCSICommand.scsi[1] &= ~0xE0;
      theSCSICommand.scsi[1] |= (scsiDevice.LUN & 0x03) << 5;
      cmdBlockLength = GetCommandLength(&theSCSICommand);
      if (senseMessage != NULL)
          senseMessage[0] = 0;
      if (sensePtr != NULL)
          sensePtr->errorCode = 0;
      if (scsiDevice.bus == kOriginalSCSIBusAdaptor) {
          status = DoOriginalSCSICommand(
                  scsiDevice,
                  &theSCSICommand,
                  cmdBlockLength,
                  dataBuffer,
                  dataLength,
                  scsiFlags,
                  actualTransferCount,
                  sensePtr
                );
      }
      else {
          ClearMemory(gSCSIExecIOPBPtr, gSCSIExecIOPBPtrLen);
#define PB  (*gSCSIExecIOPBPtr)
          PB.scsiPBLength = gSCSIExecIOPBPtrLen;
          PB.scsiFunctionCode = SCSIExecIO;
          PB.scsiDevice = scsiDevice;
          PB.scsiTimeout = kSCSICommandTimeout;
          /*
           * Fiddle the flags so they're the least disruptive possible.
           */
          PB.scsiFlags = scsiFlags | (scsiSIMQNoFreeze | scsiDontDisconnect);
          if (sensePtr != NULL) {
            PB.scsiSensePtr = (UInt8 *) sensePtr;
            PB.scsiSenseLength = sizeof *sensePtr;
          }
          BlockMoveData(&theSCSICommand, &PB.scsiCDB.cdbBytes[0], cmdBlockLength);
          PB.scsiCDBLength = cmdBlockLength;
          if (dataBuffer != NULL) {
            PB.scsiDataPtr = (UInt8 *) dataBuffer;
            PB.scsiDataLength = dataLength;
            PB.scsiDataType = scsiDataBuffer;
            PB.scsiTransferType = scsiTransferPolled;
          }
          status = SCSIAction((SCSI_PB *) &PB);
          if (status == noErr)
            status = PB.scsiResult;
          if (status == scsiSelectTimeout)
            status = scsiDeviceNotThere;
          if (actualTransferCount != NULL) {
            /*
             * Make sure that the actual transfer count does not exceed
             * the allocation count (some devices spit extra data at us!)
             */
            *actualTransferCount = dataLength - PB.scsiDataResidual;
            if (*actualTransferCount > dataLength)
                *actualTransferCount = dataLength;
          }
#undef PB
      }
      if (status == scsiNonZeroStatus
       && sensePtr != NULL
       && sensePtr->errorCode != 0
       && senseMessage != NULL) {
//          FormatSenseMessage(sensePtr, senseMessage);
//          ShowProgressAction(senseMessage);
      }
      return (status);
}

/*
 * Do a command with autosense using the original SCSI manager.
 */
OSErr
DoOriginalSCSICommand(
      DeviceIdent             scsiDevice,
      const SCSI_CommandPtr   theSCSICommand,
      unsigned short          cmdBlockLength,
      Ptr                     dataBuffer,
      ByteCount               dataLength,
      UInt32                  scsiFlags,
      ByteCount               *actualTransferCount,
      SCSI_Sense_Data         *sensePtr
    )
{
      OSErr                   status;
      UInt8                   scsiStatusByte;
      SCSI_Command            scsiStatusCommand;

      status = OriginalSCSI(
                scsiDevice,
                theSCSICommand,
                cmdBlockLength,
                dataBuffer,
                dataLength,
                scsiFlags,
                actualTransferCount,
                &scsiStatusByte
          );
      if (status == scsiNonZeroStatus
       && scsiStatusByte == kScsiStatusCheckCondition
       && sensePtr != NULL) {
          CLEAR(scsiStatusCommand);
          CLEAR(*sensePtr);
          scsiStatusCommand.scsi6.opcode = kScsiCmdRequestSense;
          scsiStatusCommand.scsi[1] |= (scsiDevice.LUN & 0x03) << 5;
          scsiStatusCommand.scsi6.len = sizeof *sensePtr;
          status = OriginalSCSI(
                  scsiDevice,
                  &scsiStatusCommand,
                  sizeof scsiStatusCommand.scsi6,
                  (Ptr) sensePtr,
                  sizeof *sensePtr,
                  scsiDirectionIn,
                  NULL,
                  &scsiStatusByte
                );
          if (status != noErr && status != scsiDataRunError) {
#ifdef notdef
            if (gDebugOnError && scsiStatusByte != kScsiStatusCheckCondition) {
                Str255          work;

                pstrcpy(work, "\pAutosense failed ");
                AppendSigned(work, status);
                AppendChar(work, ' ');
                AppendHexLeadingZeros(work, scsiStatusByte, 2);
                DebugStr(work);
            }
#endif
            sensePtr->errorCode = 0;
            status = scsiAutosenseFailed;
          }
          else {
            status = scsiNonZeroStatus;
          }
      }
      return (status);
}

OSErr
OriginalSCSI(
      DeviceIdent             scsiDevice,
      const SCSI_CommandPtr   scsiCommand,
      UInt8                   scsiCommandLen,
      Ptr                     dataBuffer,
      ByteCount               dataLength,
      UInt32                  scsiFlags,
      ByteCount               *actualTransferCount,
      UInt8                   *scsiStatusBytePtr
    )
{
      OSErr                   status;             /* Final status             */
      OSErr                   completionStatus;   /* Status from ScsiComplete */
      short                   totalTries;         /* Get/Select retries       */
      short                   getTries;           /* Get retries              */
      short                   iCount;             /* Bus free counter         */
      unsigned long           watchdog;           /* Timeout after this       */
      unsigned long           myTransferCount;    /* Gets TIB loop counter    */
      short                   scsiStatusByte;     /* Gets SCSIComplete result */
      short                   scsiMsgByte;        /* Gets SCSIComplete result */
      Boolean                 bufferHoldFlag;
      /*
       * The TIB has the following format:
       *  [0] scInc   user buffer         transferQuantum or transferSize
       *  [1] scAdd   &theTransferCount   1
       *  [2] scLoop  -> tib[0]           transferSize / transferQuantum
       *  [3] scStop
       * The intent of this is to return, in actualTransferCount, the number
       * of times we cycled through the tib[] loop. This will be the actual
       * transfer count if transferQuantum equals one, or the number of
       * "blocks" if transferQuantum is the length of one sector.
       */
      SCSIInstr               tib[4];             /* Current TIB              */

      status = noErr;
      bufferHoldFlag = FALSE;
      scsiStatusByte = 0xFF;
      scsiMsgByte = 0xFF;
      myTransferCount = 0;
      /*
       * If there is a data transfer, setup the tib.
       */
      if (dataBuffer != NULL) {
          tib[0].scOpcode = scInc;
          tib[0].scParam1 = (unsigned long) dataBuffer;
          tib[0].scParam2 = 1;
          tib[1].scOpcode = scAdd;
          tib[1].scParam1 = (unsigned long) &myTransferCount;
          tib[1].scParam2 = 1;
          tib[2].scOpcode = scLoop;
          tib[2].scParam1 = (-2 * sizeof (SCSIInstr));
          tib[2].scParam2 = dataLength / tib[0].scParam2;
          tib[3].scOpcode = scStop;
          tib[3].scParam1 = 0;
          tib[3].scParam2 = 0;
      }
      if (IsVirtualMemoryRunning() && dataBuffer != NULL) {
          /*
           * Lock down the user buffer, if any. In a real-world application
           * or driver, this would be done before calling the SCSI interface.
           */
#ifdef notdef
          FailOSErr(
            HoldMemory(dataBuffer, dataLength),
            "\pCan't lock data buffer in physical memory"
          );
#else
      HoldMemory(dataBuffer, dataLength);
#endif
          bufferHoldFlag = TRUE;
      }
      /*
       * Arbitrate for the scsi bus.  This will fail if some other device is
       * accessing the bus at this time (which is unlikely).
       *
       *** Do not set breakpoints or call any functions that may require device
       *** I/O (such as display code that accesses font resources between
       *** SCSIGet and SCSIComplete,
       *
       */
      for (totalTries = 0; totalTries < kMaxSCSIRetries; totalTries++) {
          for (getTries = 0; getTries < 4; getTries++) {
            /*
             * Wait for the bus to go free.
             */
            watchdog = TickCount() + 300;       /* 5 second timeout         */
            while (ScsiBusBusy()) {
                if (/*gStopNow || StopNow() ||*/ TickCount() > watchdog) {
                  status = scsiBusy;
                  goto exit;
                }
            }
            /*
             * The bus is free, try to grab it
             */
            for (iCount = 0; iCount < 4; iCount++) {
                if ((status = SCSIGet()) == noErr)
                  break;
            }
            if (status == noErr)
                break;                          /* Success: we have the bus */
            /*
             * The bus became busy again. Try to wait for it to go free.
             */
            for (iCount = 0;
                  /*gStopNow == FALSE && StopNow() == FALSE &&*/ iCount < 100 && ScsiBusBusy();
                  iCount++)
                ;
          } /* The getTries loop */
          if (status != noErr) {
            /*
             * The SCSI Manager thinks the bus is not busy and not selected,
             * but "someone" has set its internal semaphore that signals
             * that the SCSI Manager itself is busy. The application will have
             * to handle this problem. (We tried getTries * 4 times).
             */
            status = scsiBusy;
            goto exit;
          }
          /*
           * We now own the SCSI bus. Try to select the device.
           */
          if ((status = SCSISelect(scsiDevice.targetID)) != noErr) {
            switch (status) {
            /*
             * We get scBadParmsErr if we try to arbitrate for the initiator.
             */
            case scBadParmsErr: status = scsiTIDInvalid;        break;
            case scCommErr:     status = scsiDeviceNotThere;    break;
            case scArbNBErr:    status = scsiBusy;              break;
            case scSequenceErr: status = scsiRequestInvalid;    break;
            }
            goto exit;
          }
          /*
           * From this point on, we must exit through SCSIComplete() even if an
           * error is detected. Send a command to the selected device. There are
           * several failure modes, including an illegal command (such as a
           * write to a read-only device). If the command failed because of
           * "device busy", we will try it again.
           */
          status = SCSICmd((Ptr) scsiCommand, scsiCommandLen);
          if (status != noErr) {
            switch (status) {
            case scCommErr:     status = scsiCommandTimeout;    break;
            case scPhaseErr:    status = scsiSequenceFailed;    break;
            }
          }
          if (status == noErr && dataBuffer != NULL) {
            /*
             * This command requires a data transfer.
             */
            if (scsiFlags == scsiDirectionOut)
                status = SCSIWrite((Ptr) tib);
            else {
                status = SCSIRead((Ptr) tib);
            }
            switch (status) {
            case scCommErr:     status = scsiCommandTimeout;        break;
            case scBadParmsErr: status = scsiRequestInvalid;        break;
            case scPhaseErr:    status = noErr; /* Don't care */    break;
            case scCompareErr:                  /* Can't happen */  break;
            }
          }
          /*
           * SCSIComplete "runs" the bus-phase algorithm until the bitter end,
           * returning the status and command-completion message bytes..
           */
          completionStatus = SCSIComplete(
                  &scsiStatusByte,
                  &scsiMsgByte,
                  5 * 60L
                );
          if (status == noErr && completionStatus != noErr) {
            switch (completionStatus) {
            case scCommErr:         status = scsiCommandTimeout;    break;
            case scPhaseErr:        status = scsiSequenceFailed;    break;
            case scComplPhaseErr:   status = scsiSequenceFailed;    break;
            }
          }
          if (completionStatus == noErr && scsiStatusByte == kScsiStatusBusy) {
            /*
             * ScsiComplete is happy. If the device is busy,
             * pause for 1/4 second and try again.
             */
            watchdog = TickCount() + 15;
            while (TickCount() < watchdog)
                ;
            continue;               /* Do next totalTries attempt       */
          }
          /*
           * This is the normal exit (success) or final failure exit.
           */
          break;
      } /* totalTries loop */
exit:   if (bufferHoldFlag)
          (void) UnholdMemory(dataBuffer, dataLength);
      /*
       * Return the number of bytes transferred to the caller. If the caller
       * supplied an actual count and the count is no greater than the maximum,
       * ignore any phase errors.
       */
      if (actualTransferCount != NULL) {
          *actualTransferCount = myTransferCount;
          if (*actualTransferCount > dataLength)
            *actualTransferCount = dataLength;
      }
      /*
       * Also, there is a bug in the combination of System 7.0.1 and the 53C96
       * that may cause the real SCSI Status Byte to be in the Message byte.
       */
      if (scsiStatusByte == kScsiStatusGood
       && scsiMsgByte == kScsiStatusCheckCondition)
          scsiStatusByte = kScsiStatusCheckCondition;
      if (status == noErr) {
          switch (scsiStatusByte) {
          case kScsiStatusGood:                               break;
          case kScsiStatusBusy:   status = scsiBusy;          break;
          case 0xFF:              status = scsiProvideFail;   break;
          default:                status = scsiNonZeroStatus; break;
          }
      }
      if (status == noErr
       && (scsiFlags & scsiDirectionMask) != scsiDirectionNone
       && myTransferCount != dataLength)
          status = scsiDataRunError;          
      if (scsiStatusBytePtr != NULL)
          *scsiStatusBytePtr = scsiStatusByte;
      return (status);
}

UInt16
GetCommandLength(
      const SCSI_CommandPtr   cmdPtr
    )
{
      unsigned short          result;
      /*
       * Look at the "group code" in the command operation. Return zero
       * error for the reserved (3, 4) and vendor-specific command (6, 7)
       * command groups. Otherwise, set the command length from the group code
       * value as specified in the SCSI-II spec.
       */
      switch (cmdPtr->scsi6.opcode & 0xE0) {
      case (0 << 5):  result = 6;     break;
      case (1 << 5):
      case (2 << 5):  result = 10;    break;
      case (5 << 5):  result = 12;    break;
      default:        result = 0;     break;
      }
      return (result);
}

Boolean
IsVirtualMemoryRunning(void)
{
      OSErr                       status;
      long                        response;
      
      status = Gestalt(gestaltVMAttr, &response);
      /*
       * VM is active iff Gestalt succeeded and the response is appropriate.
       */
      return (status == noErr && ((response & (1 << gestaltVMPresent)) != 0));
}

Generated by  Doxygen 1.6.0   Back to index