Source to bsd/dev/ppc/drvSymbios8xx/Sym8xxExecute.m


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/* Sym8xxExecute.m created by russb2 on Sat 30-May-1998 */

#import "Sym8xxController.h"

/*-----------------------------------------------------------------------------*
 * IOThread Routines
 *
 * This module contains routines that run on the driver's IOThread.
 *
 *-----------------------------------------------------------------------------*/

@implementation Sym8xxController(Execute)

/*-----------------------------------------------------------------------------*
 *  This is the main command processing loop for the driver's I/O thread.
 *  It removes SRBs from the driver's command queue and processes them according
 *  to the command code in srb->srbCmd.
 *
 *-----------------------------------------------------------------------------*/
- (void) commandRequestOccurred
{
    SRB				*srb = NULL;

    while ( 1 )
    {
       [srbPendingQLock lock];

       if ( queue_empty(&srbPendingQ) )
       {
          [srbPendingQLock unlock];
           break;
       }
       queue_remove_first( &srbPendingQ, srb, SRB *, srbQ );

       [srbPendingQLock unlock];

    
        /*
         * If we are in the quiet period after a SCSI Bus reset, then reject new SRBs. In
         * general the client thread processing will block new SRBs, however, some may have
         * been on the IOThread's SRB queue when the reset occurred.
         */
        if ( resetQuiesceTimer )
        {
            srb->srbSCSIResult = SR_IOST_RESET;
            [srb->srbCmdLock unlockWith: ksrbCmdComplete];  
            continue;
        }

        switch ( srb->srbCmd )
        {
            /*
             * For a SCSI CDB request, stuff the physical address of the SRB's  Nexus struct into a
             * mailbox and signal the Symbios script engine.
             */
            case ksrbCmdExecuteReq:
                srb->nexus.targetParms.scntl3Reg = adapter->targetClocks[srb->target].scntl3Reg;
                srb->nexus.targetParms.sxferReg  = adapter->targetClocks[srb->target].sxferReg;

                adapter->nexusPtrsVirt[srb->nexus.tag] = &srb->nexus;
                adapter->nexusPtrsPhys[srb->nexus.tag] = (Nexus *)EndianSwap32( (u_int32_t)&srb->srbPhys->nexus );
                adapter->schedMailBox[mailBoxIndex++]  = (Nexus *)EndianSwap32 ( (u_int32_t)&srb->srbPhys->nexus );

                [self Sym8xxSignalScript: srb];
                break;

            case ksrbCmdResetSCSIBus:
                [self Sym8xxSCSIBusReset: srb];
                break;
           
            case ksrbCmdAbortReq:
	    case ksrbCmdBusDevReset:
                [self Sym8xxAbortBdr: srb];
                break;

            default:
                ; 
        }         
    }
}


/*-----------------------------------------------------------------------------*
 * Interrupts from the Symbios chipset are dispatched here at task time under the
 * IOThread's context.
 *-----------------------------------------------------------------------------*/
- (void) interruptOccurred
{
    do
    {
        /* 
         * The chipset's ISTAT reg gives us the general interrupting condiditions,
         * with DSTAT and SIST providing more detailed information.
         */
        istatReg = Sym8xxReadRegs( chipBaseAddr, ISTAT, ISTAT_SIZE );

        /* The INTF bit in ISTAT indicates that the script is signalling the driver
         * that its IODone mailbox is full and that we should process a completed
         * request. The script continues to run after posting this interrupt unlike 
         * other chipset interrupts which require the driver to restart the script
         * engine.
         */
        if ( istatReg & INTF )
        {
            Sym8xxWriteRegs( chipBaseAddr, ISTAT, ISTAT_SIZE, istatReg );
            [self Sym8xxProcessIODone];
        }

        /*
         * Handle remaining interrupting conditions
         */  
        if ( istatReg & (SIP | DIP) )
        {
            [self Sym8xxProcessInterrupt];    
        }
    }
    while ( istatReg & (SIP | DIP | INTF) );

    [self enableAllInterrupts];

}

/*-----------------------------------------------------------------------------*
 * Process a request posted in the script's IODone mailbox.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxProcessIODone
{
    SRB				*srb;
    Nexus			*nexus;
    IODoneMailBox		*pMailBox;
    
 
    /*
     * The IODone mailbox contains an index into our Nexus pointer tables.
     *
     * The Nexus struct is part of the SRB so we can get our SRB address
     * by subtracting the offset of the Nexus struct in the SRB.
     */
    pMailBox = (IODoneMailBox *)&SCRIPT_VAR(R_ld_IOdone_mailbox);
    nexus = adapter->nexusPtrsVirt[pMailBox->nexus];        
    srb   = (SRB *)((u_int32_t)nexus - offsetof(SRB, nexus));    

    /*
     * If there was no request sense performed, then update the transfer
     * counts in the SRB.
     */
    if ( srb->srbState == ksrbStateCDBDone  )
    {
        [self Sym8xxUpdateXferOffset: srb];
    }

    /* 
     * Clear the completed Nexus pointer from our tables and clear the
     * IODone mailbox.
     */
    adapter->nexusPtrsVirt[pMailBox->nexus] = (Nexus *) -1;
    adapter->nexusPtrsPhys[pMailBox->nexus] = (Nexus *) -1;
    SCRIPT_VAR(R_ld_IOdone_mailbox)       = 0;

    /*
     * Don't ask why we need to do this -- we shouldn't if the Indirect SCSI
     * drivers worked properly!
     * 
     */
    if ( nexus->cdbData.cdb_opcode == C6OP_INQUIRY )
    {
        [self Sym8xxCheckInquiryData: srb];
    }

    /*
     * Wake up the client's thread to do post-processing
     */
    [srb->srbCmdLock unlockWith: ksrbCmdComplete];  
}

/*-----------------------------------------------------------------------------*
 * General script interrupt processing
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxProcessInterrupt
{
    SRB			*srb 		= NULL;
    Nexus		*nexus 		= NULL;
    u_int32_t		nexusIndex;
    u_int32_t		scriptPhase;
    u_int32_t		fifoCnt 	= 0;
    u_int32_t		dspsReg 	= 0;
    u_int32_t		dspReg  	= 0;


    /*
     * Read DSTAT/SIST regs to determine why the script stopped.
     */
    dstatReg = Sym8xxReadRegs( chipBaseAddr,  DSTAT, DSTAT_SIZE );
    IODelay(5);
    sistReg =  Sym8xxReadRegs( chipBaseAddr,  SIST,  SIST_SIZE );

//  kprintf( "SCSI(Symbios8xx): SIST = %04x DSTAT = %02x\n\r", sistReg, dstatReg  );

    /*
     * This Script var tells us what the script thinks it was doing when the interrupt occurred.
     */
    scriptPhase = EndianSwap32( SCRIPT_VAR(R_ld_phase_flag) );

    /*
     * SCSI Bus reset detected 
     *
     * Clean up the carnage.     
     * Note: This may be either an adapter or target initiated reset.
     */
    if ( sistReg & RSTI )
    {
        [self Sym8xxProcessSCSIBusReset];
        return;
    }

    /*
     * Calculate our current SRB/Nexus.
     *
     * Read a script var to determine the index of the nexus it was processing
     * when the interrupt occurred. The script will invalidate the index if there
     * is no target currently connected or the script cannot determine which target
     * has reconnected.
     */
    nexusIndex = EndianSwap32(SCRIPT_VAR(R_ld_nexus_index));
    if ( nexusIndex >= MAX_SCSI_TAG )
    {
        [self Sym8xxProcessNoNexus];
        return;
    }
    nexus  = adapter->nexusPtrsVirt[nexusIndex];        
    if ( nexus == (Nexus *) -1 )
    {
        [self Sym8xxProcessNoNexus];
        return;
    }
    srb = (SRB *)((u_int32_t)nexus - offsetof(SRB, nexus));  

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_phase_handler];
   
    /*   
     * Parity and SCSI Gross Errors.
     *
     * Abort the current connection. The abort completion will trigger
     * clean-up of the current SRB/Nexus.
     */
    if ( sistReg & PAR )
    {  
         srb->srbSCSIResult = SR_IOST_PARITY;
         [self Sym8xxAbortCurrent: srb];
    }

    else if ( sistReg & SGE )
    {
         srb->srbSCSIResult = SR_IOST_BV;
         [self Sym8xxAbortCurrent: srb];
    }
       
    /*
     * Unexpected disconnect. 
     *
     * If we were currently trying to abort this connection then mark the abort
     * as completed. For all cases clean-up and wake-up the client thread.
     */ 
    else if ( sistReg & UDC )
    {
        if ( srb->srbSCSIResult == SR_IOST_GOOD )
        {
            srb->srbSCSIResult = SR_IOST_BV;
        }
        adapter->nexusPtrsVirt[nexusIndex] = (Nexus *) -1;
        adapter->nexusPtrsPhys[nexusIndex] = (Nexus *) -1;

        if ( scriptPhase == A_kphase_ABORT_CURRENT )
        {
            abortCurrentSRB = NULL;
        }

        [srb->srbCmdLock unlockWith: ksrbCmdComplete];  

        scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];
    }

    /*
     * Phase Mis-match
     *
     * If we are in MsgOut phase then calculate how much of the message we sent. For
     * now, however, we dont handle the target rejecting messages, so the request is aborted.
     *
     * If we are in DataIn/DataOut phase. We update the SRB/Nexus with our current data 
     * pointers.
     */
    else if ( sistReg & MA )
    {
        if ( scriptPhase == A_kphase_MSG_OUT )
        {
            srb->srbMsgResid = [self Sym8xxCheckFifo:srb FifoCnt:&fifoCnt];
            nexus->msg.ppData   = EndianSwap32( EndianSwap32(nexus->msg.ppData) + EndianSwap32(nexus->msg.length) 
                                         - srb->srbMsgResid );
            nexus->msg.length   = EndianSwap32( srb->srbMsgResid );

            [self Sym8xxAbortCurrent: srb];
        }
        else if ( (scriptPhase == A_kphase_DATA_OUT) || (scriptPhase == A_kphase_DATA_IN) )
        {
            [self Sym8xxAdjustDataPtrs:srb Nexus:nexus];
        }
        else
        {
//          kprintf("SCSI(Symbios8xx): Unexpected phase mismatch - scriptPhase = %08x\n\r", scriptPhase);
            srb->srbSCSIResult = SR_IOST_BV;
            [self Sym8xxAbortCurrent: srb];
        }

        [self Sym8xxClearFifo];
    }
    
    /*
     * Selection Timeout.
     *
     * Clean-up the current request.
     */
    else if ( sistReg & STO )
    {
        srb->srbSCSIResult = SR_IOST_SELTO;

        adapter->nexusPtrsVirt[nexusIndex] = (Nexus *) -1;
        adapter->nexusPtrsPhys[nexusIndex] = (Nexus *) -1;
        SCRIPT_VAR(R_ld_IOdone_mailbox)    = 0;

        [srb->srbCmdLock unlockWith: ksrbCmdComplete];  

        scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];
    }
        
    /*
     * Handle script initiated interrupts
     */
    else if ( dstatReg & SIR )
    {
        dspsReg = Sym8xxReadRegs( chipBaseAddr, DSPS, DSPS_SIZE );

        switch ( dspsReg )
        {
            /* 
             * Non-zero SCSI status
             *
             * Send request sense CDB or complete request depending on SCSI status value
             */
            case A_status_error:
                if ( [self Sym8xxProcessStatus:srb] == NO )
                {
                    [self Sym8xxProcessIODone];
                }
                break;
             
            /*
             * Received SDTR/WDTR message from target.
             *
             * Prepare reply message if we requested negotiation. Otherwise reject
             * target initiated negotiation.
             */
	    case A_negotiateSDTR:
                [self Sym8xxNegotiateSDTR:srb Nexus: nexus];
                break;

	    case A_negotiateWDTR:
                [self Sym8xxNegotiateWDTR:srb Nexus: nexus];
                break;

            /*
             * Partial SG List completed.
             *
             * Refresh the list from the remaining addresses to be transfered and set the
             * script engine to branch into the list.
             */
            case A_sglist_complete:
                [self Sym8xxUpdateSGList:srb];
                scriptRestartAddr = (u_int32_t)&srb->srbPhys->nexus.sgListData[2];
                break;

            /*
             * Completed abort request
             *
             * Clean-up the aborted request.
             */
	    case A_abort_current:	
                if ( srb->srbSCSIResult == SR_IOST_GOOD )
                {
                    srb->srbSCSIResult = SR_IOST_BV;
                }

                adapter->nexusPtrsVirt[nexusIndex] = (Nexus *) -1;
                adapter->nexusPtrsPhys[nexusIndex] = (Nexus *) -1; 

                abortCurrentSRB = NULL;

                [srb->srbCmdLock unlockWith: ksrbCmdComplete];  

                scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];
                break;
    
            /*
             * Script detected protocol errors
             *
             * Abort the current request.
             */
            case A_unknown_msg_reject:
            case A_unknown_phase:
            case A_unexpected_msg:
            case A_unexpected_ext_msg:
                srb->srbSCSIResult = SR_IOST_TABT;
                [self Sym8xxAbortCurrent: srb];
                break;

            default:
                kprintf( "SCSI(Symbios8xx): Unknown Script Int = %08x\n\r", dspsReg );
                srb->srbSCSIResult = SR_IOST_INT;
                [self Sym8xxAbortCurrent: srb];
        }
    }

    /*
     * Illegal script instruction.
     *
     * We're toast! Abort the current request and hope for the best!
     */
    else if ( dstatReg & IID )
    {
        dspReg  = Sym8xxReadRegs( chipBaseAddr, DSP, DSP_SIZE );

        kprintf("SCSI(Symbios8xx): Illegal script instruction - dsp = %08x srb=%08x\n\r", dspReg, (u_int32_t)srb );

        srb->srbSCSIResult = SR_IOST_INT;
        [self Sym8xxAbortCurrent: srb];
    }

    if ( scriptRestartAddr )
    {
        Sym8xxWriteRegs( chipBaseAddr, DSP, DSP_SIZE, scriptRestartAddr );
    }
}    

/*-----------------------------------------------------------------------------*
 * Handle non-zero SCSI status
 *
 * Returns: 
 *    NO  - Clean-up request now.
 *    YES - Wait for request sense to complete.
 *
 * This routine filter's out BUSY and more arcane SCSI status conditions,
 * leaving CHECK_CONDITION, for which it set's up a request sense operation.
 *
 *-----------------------------------------------------------------------------*/
- (BOOL) Sym8xxProcessStatus: (SRB *) srb;
{
    IODoneMailBox		*pMailBox;

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];
 
    pMailBox = (IODoneMailBox *)&SCRIPT_VAR(R_ld_IOdone_mailbox);

    /*
     * If a previous request sense failed, then clean-up the request now.
     */
    if ( srb->srbState != ksrbStateCDBDone )
    {
        if ( srb->srbSCSIResult == SR_IOST_GOOD )
        {
            srb->srbSCSIResult = SR_IOST_CHKSNV;
        }
        return NO;
    }

    /*
     * Update the SRB with our byte transferred count and SCSI status.
     * This information needs to be captured before we issue the request
     * sense.
     */
    srb->srbSCSIStatus = pMailBox->status;

    [self Sym8xxUpdateXferOffset: srb];

    if ( pMailBox->status != STAT_CHECK )
    {
        srb->srbSCSIResult = ST_IOST_BADST;
        return NO;
    }
    if ( srb->senseData == NULL )
    {
        srb->srbSCSIResult = SR_IOST_CHKSNV;
        return NO;
    }

    [self Sym8xxIssueRequestSense: srb];

    return YES;
}

/*-----------------------------------------------------------------------------*
 * Prepare request sense request.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxIssueRequestSense:(SRB *) srb
{
    IODoneMailBox		*pMailBox;
    u_int32_t			reqSenseMailBox;

    pMailBox = (IODoneMailBox *)&SCRIPT_VAR(R_ld_IOdone_mailbox);

    /*
     * We put the request sense Nexus in the last completed script mailbox and
     * back-up the script's mailbox pointer.
     */
    reqSenseMailBox = (u_int8_t)(EndianSwap32(SCRIPT_VAR(R_ld_counter)) - 1);
    SCRIPT_VAR(R_ld_counter) = EndianSwap32( reqSenseMailBox );

    srb->srbTimeout = kReqSenseTimeoutMS / kSCSITimerIntervalMS + 1; 

    srb->srbState      = ksrbStateReqSenseDone;
    srb->srbSCSIResult = SR_IOST_CHKSV;

    /*
     * Reuse the original Nexus struct. The original CDB is not preserved. The
     * original status and transfer counts and tag are kept in the SRB.
     */
    bzero( &srb->nexus.cdbData, 6 );
    srb->nexus.cdbData.cdb_c6.c6_opcode = C6OP_REQSENSE;
    srb->nexus.cdbData.cdb_c6.c6_lun    = srb->lun;
    srb->nexus.cdbData.cdb_c6.c6_len    = srb->senseDataLength;
    srb->nexus.cdb.length               = EndianSwap32( 6 );

    /* 
     * Force renegotiation on request sense.
     */
    targets[srb->target].flags &= ~(kTFXferSync | kTFXferWide16);
    srb->srbRequestFlags       &= ~(ksrbRFCmdQueueAllowed | ksrbRFDisconnectAllowed);
    [self Sym8xxCalcMsgs:srb];

    /*
     * Create a new SG List for the request sense data
     *
     */
    srb->xferOffset        = 0;
    srb->xferOffsetPrev    = 0;
    srb->xferClient        = IOVmTaskSelf();
    srb->xferBuffer        = srb->senseData;
    srb->xferCount         = srb->senseDataLength;
    srb->directionMask     = 0x01000000;
    srb->nexus.ppSGList    = (SGEntry *)EndianSwap32((u_int32_t)&srb->srbPhys->nexus.sgListData[2]);
    [self Sym8xxUpdateSGList: srb];

    /*
     * If the original request was using cmd-queuing, we clean-up the original tagged request
     * and convert it to a non-tagged request sense.
     */
    if ( srb->nexus.tag >= MIN_SCSI_TAG )
    {
        adapter->nexusPtrsVirt[pMailBox->nexus] = (Nexus *) -1;
        adapter->nexusPtrsPhys[pMailBox->nexus] = (Nexus *) -1;

        srb->nexus.tag  = (srb->target << 3) | srb->lun;
        adapter->nexusPtrsVirt[srb->nexus.tag] = &srb->nexus;
        adapter->nexusPtrsPhys[srb->nexus.tag] = (Nexus *)EndianSwap32( (u_int32_t)&srb->srbPhys->nexus );
    }

    adapter->schedMailBox[reqSenseMailBox]  = (Nexus *)EndianSwap32 ( (u_int32_t)&srb->srbPhys->nexus );

    [self Sym8xxSignalScript: srb];

    SCRIPT_VAR(R_ld_IOdone_mailbox) = 0;
}

/*-----------------------------------------------------------------------------*
 * Current Data Pointer calculations
 * 
 * To do data transfers the driver generates a list of script instructions 
 * in system storage to deliver data to the requested physical addresses. The
 * script branches to the list when the target enters data transfer phase.
 *
 * When the target changes phase during a data transfer, data is left trapped
 * inside the various script engine registers. This routine determines how much
 * data was not actually transfered to/from the target and generates a new
 * S/G List entry for the partial transfer and a branch back into the original
 * S/G list. These script instructions are stored in two reserved slots at the
 * top of the original S/G List.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxAdjustDataPtrs:(SRB *) srb Nexus:(Nexus *) nexus
{
    u_int32_t		i;
    u_int32_t		sgResid;
    u_int32_t		fifoCnt;
    u_int32_t		dspReg;
    u_int32_t		sgDone;
    u_int8_t		scntl2Reg;
    Nexus		*nexusPhys;

    /*
     * Determine SG element residual
     *
     * This routine returns how much of the current S/G List element the 
     * script was processing remains to be sent/received. All the information
     * required to do this is stored in the script engine's registers.
     */
    sgResid = [self Sym8xxCheckFifo:srb FifoCnt:&fifoCnt];

    /*
     * Determine which script instruction in our SGList we were executing when
     * the target changed phase.
     *
     * The script engine's dspReg tells us where the script thinks it was. Based
     * on the physical address of our current SRB/Nexus we can calculate
     * an index into our S/G List.  
     */
    dspReg  = Sym8xxReadRegs( chipBaseAddr, DSP, DSP_SIZE );

    i = ((dspReg - (u_int32_t)srb->srbPhys->nexus.sgListData) / sizeof(SGEntry)) - 1;
       
    if ( i > MAX_SGLIST_ENTRIES-1 )
    {
       kprintf("SCSI(Symbios8xx): Bad sgListIndex\n\r");
       [self Sym8xxAbortCurrent: srb];
       return;
    }

    /* 
     * Wide/odd-byte transfers.
     *     
     * When dealing with Wide data transfers, if a S/G List ends with an odd-transfer count, then a
     * valid received data byte is left in the script engine's SWIDE register. The least painful way
     * to recover this byte is to construct a small script thunk to transfer one additional byte. The
     * script will automatically draw this byte from the SWIDE register rather than the SCSI bus.
     * The script thunk then branches back to script's PhaseHandler entrypoint.
     * 
     */
    nexusPhys = &srb->srbPhys->nexus;

    scntl2Reg = Sym8xxReadRegs( chipBaseAddr, SCNTL2, SCNTL2_SIZE );
    if ( scntl2Reg & WSR )
    {
        adapter->xferSWideInst[0] = EndianSwap32( srb->directionMask | 1 );
        adapter->xferSWideInst[1] = nexus->sgListData[i].physAddr;
        adapter->xferSWideInst[2] = EndianSwap32( 0x80080000 );
        adapter->xferSWideInst[3] = EndianSwap32( (u_int32_t)&chipRamAddrPhys[Ent_phase_handler] );

        scriptRestartAddr = (u_int32_t) adapterPhys->xferSWideInst;
        
        /*
         * Note: There is an assumption here that the sgResid count will be > 1. It appears 
         *       that the script engine does not generate a phase-mismatch interrupt until 
         *       we attempt to move > 1 byte from the SCSI bus and the only byte available is
         *       in SWIDE. 
         */        
        sgResid--;
    }

    /*
     * Calculate partial S/G List instruction and branch
     *
     * Fill in slots 0/1 of the SGList based on the SGList index (i) and SGList residual count
     * (sgResid) calculated above.
     *
     */
    sgDone  = (EndianSwap32( nexus->sgListData[i].length ) & 0x00ffffff) - sgResid;

    nexus->sgListData[0].length   = EndianSwap32( sgResid | srb->directionMask );
    nexus->sgListData[0].physAddr = EndianSwap32( EndianSwap32(nexus->sgListData[i].physAddr) + sgDone );
    /*
     * If a previously calculated SGList 0 entry was interrupted again, we dont need to calculate
     * a new branch address since the previous one is still valid.
     */
    if ( i != 0 )
    {
        nexus->sgListData[1].length   = EndianSwap32( 0x80080000 );
        nexus->sgListData[1].physAddr = EndianSwap32( (u_int32_t)&nexusPhys->sgListData[i+1] );
        nexus->sgNextIndex            = i + 1;
    }
    nexus->ppSGList = (SGEntry *)EndianSwap32( (u_int32_t) &nexusPhys->sgListData[0] );
 
    /*
     * The script sets this Nexus variable to non-zero each time it calls the driver generated
     * S/G list. This allows the driver's completion routines to differentiate between a successful
     * transfer vs no data transfer at all.
     */
    nexus->dataXferCalled = 0;

    return;
}

/*-----------------------------------------------------------------------------*
 * Determine SG element residual
 *
 * This routine returns how much of the current S/G List element the 
 * script was processing remains to be sent/received. All the information
 * required to do this is stored in the script engine's registers.
 *
 *-----------------------------------------------------------------------------*/
- (u_int32_t) Sym8xxCheckFifo: (SRB *) srb FifoCnt:(u_int32_t *)pfifoCnt
{
    BOOL		fSCSISend;
    BOOL		fXferSync;
    u_int32_t		scriptPhase 	= 0;
    u_int32_t		dbcReg    	= 0;
    u_int32_t		dfifoReg  	= 0;
    u_int32_t		ctest5Reg 	= 0;
    u_int8_t		sstat0Reg 	= 0;
    u_int8_t		sstat1Reg 	= 0;
    u_int8_t		sstat2Reg 	= 0;
    u_int32_t		fifoCnt   	= 0;
    u_int32_t		sgResid	  	= 0;

    scriptPhase = EndianSwap32( SCRIPT_VAR(R_ld_phase_flag) );

    fSCSISend =  (scriptPhase == A_kphase_DATA_OUT) || (scriptPhase == A_kphase_MSG_OUT);
 
    fXferSync =  ((scriptPhase == A_kphase_DATA_OUT) || (scriptPhase == A_kphase_DATA_IN)) 
                         && (srb->nexus.targetParms.sxferReg & 0x1F);  

    dbcReg = Sym8xxReadRegs( chipBaseAddr, DBC, DBC_SIZE ) & 0x00ffffff;

    if ( !(dstatReg & DFE) )
    {
        ctest5Reg = Sym8xxReadRegs( chipBaseAddr, CTEST5, CTEST5_SIZE );
        dfifoReg  = Sym8xxReadRegs( chipBaseAddr, DFIFO,  DFIFO_SIZE );

        if ( ctest5Reg & DFS )
        {
            fifoCnt = ((((ctest5Reg & 0x03) << 8) | dfifoReg) - dbcReg) & 0x3ff;
        }
        else
        {
            fifoCnt = (dfifoReg - dbcReg) & 0x7f;
        }
    }

    sstat0Reg = Sym8xxReadRegs( chipBaseAddr, SSTAT0, SSTAT0_SIZE );
    sstat2Reg = Sym8xxReadRegs( chipBaseAddr, SSTAT2, SSTAT2_SIZE );
 
    if ( fSCSISend )
    {    
        fifoCnt += (sstat0Reg & OLF )  ? 1 : 0;
        fifoCnt += (sstat2Reg & OLF1)  ? 1 : 0;

        if ( fXferSync )
        {
            fifoCnt += (sstat0Reg & ORF )  ? 1 : 0;
            fifoCnt += (sstat2Reg & ORF1)  ? 1 : 0;
        }
    }
    else
    {
        if ( fXferSync )
        {
            sstat1Reg = Sym8xxReadRegs( chipBaseAddr, SSTAT0, SSTAT0_SIZE );
            fifoCnt +=  (sstat1Reg >> 4) | (sstat2Reg & FF4);  
        }
        else
        {
            fifoCnt += (sstat0Reg & ILF )  ? 1 : 0;
            fifoCnt += (sstat2Reg & ILF1)  ? 1 : 0;
        }
    }   
    
    sgResid   = dbcReg + fifoCnt;
    *pfifoCnt = fifoCnt;

    return sgResid;
}

/*-----------------------------------------------------------------------------*
 * Calculate transfer counts.
 *
 * This routine updates srb->xferDone with the amount of data transferred
 * by the last S/G List executed.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxUpdateXferOffset:(SRB *) srb
{
    u_int32_t		i;
    u_int32_t		xferOffset;

    /*
     * srb->xferOffset contains the client buffer offset INCLUDING the range
     * covered by the current SGList.
     */
    xferOffset = srb->xferOffset;

    /*
     * If script did not complete the current transfer list then we need to determine
     * how much of the list was completed.
     */
    if ( srb->nexus.dataXferCalled == 0 )
    {
        /* 
         * srb->xferOffsetPrev contains the client buffer offset EXCLUDING the
         * range covered by the current SGList.
         */
        xferOffset = srb->xferOffsetPrev;

        /*
         * Calculate bytes transferred for partially completed list.
         *
         * To calculate the amount of this list completed, we sum the residual amount
         * in SGList Slot 0 and the completed list elements 2 to sgNextIndex-1.
         */
        if ( srb->nexus.sgNextIndex != 0 )
        {
            xferOffset += EndianSwap32( srb->nexus.sgListData[srb->nexus.sgNextIndex-1].length )
                             - EndianSwap32( srb->nexus.sgListData[0].length );

            for ( i=2; i < srb->nexus.sgNextIndex-1; i++ )
            {
                xferOffset += EndianSwap32( srb->nexus.sgListData[i].length ) & 0x00ffffff;
            }
        }
    }
    
    /*
     * The script leaves the result of any Ignore Wide Residual message received from the target
     * during the transfer.
     */
    xferOffset -= srb->nexus.wideResidCount;


#if 0
    {
        u_int32_t	resid = srb->xferOffset - xferOffset;
        if ( resid )
        {
            kprintf( "SCSI(Symbios8xx): Incomplete transfer - Req Count = %08x Act Count = %08x - srb = %08x\n\r", 
                    srb->xferCount, xferOffset, (u_int32_t)srb );
        }   
    }
#endif

    srb->xferDone = xferOffset;
}

/*-----------------------------------------------------------------------------*
 * No SRB/Nexus Processing.
 *
 * In some cases (mainly Aborts) not having a SRB/Nexus is normal. In other
 * cases it indicates a problem such a reconnection from a target that we
 * have no record of.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxProcessNoNexus
{
    u_int32_t			dspsReg;
    u_int32_t			dspReg      = 0;
    u_int32_t			scriptPhase = -1 ;

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];

    dspsReg = Sym8xxReadRegs( chipBaseAddr, DSPS, DSPS_SIZE );

    scriptPhase = EndianSwap32( SCRIPT_VAR(R_ld_phase_flag) );

    /* 
     * If we were trying to abort or disconnect a target and the bus
     * is now free we consider the abort to have completed.
     */
    if ( sistReg & UDC ) 
    {
        if ( (scriptPhase == A_kphase_ABORT_MAILBOX) && abortSRB )
        {
            [abortSRB->srbCmdLock unlockWith: ksrbCmdComplete];
            abortSRB = (SRB *) NULL;  
            SCRIPT_VAR(R_ld_AbortBdr_mailbox) = 0;
        }         
        else if ( scriptPhase == A_kphase_ABORT_CURRENT )
        {
            abortCurrentSRB = NULL;
        }
    }
    /*
     * If we were trying to connect to a target to send it an abort message, and
     * we timed out, we consider the abort as completed.
     *
     * Note: In this case the target may be hung, but at least its not on the bus.
     */
    else if ( sistReg & STO )
    {
        if ( (scriptPhase == A_kphase_ABORT_MAILBOX) && abortSRB )
        {
            [abortSRB->srbCmdLock unlockWith: ksrbCmdComplete];
            abortSRB = (SRB *) NULL;  
            SCRIPT_VAR(R_ld_AbortBdr_mailbox) = 0;
        }         
    }     
    
    /*
     * If the script died, without a vaild nexusIndex, we abort anything that is currently
     * connected and hope for the best!
     */
    else if ( dstatReg & IID )
    {
        dspReg  = Sym8xxReadRegs( chipBaseAddr, DSP, DSP_SIZE );
        kprintf("SCSI(Symbios8xx): Illegal script instruction - dsp = %08x srb=0\n\r", dspReg );
        [self Sym8xxAbortCurrent: (SRB *) -1];
    }

    /*
     * Script signaled conditions
     */
    else if ( dstatReg & SIR )
    {
        switch ( dspsReg )
        {
            case A_abort_current:
                abortCurrentSRB = NULL;
                break;
              
            case A_abort_mailbox:
                [abortSRB->srbCmdLock unlockWith: ksrbCmdComplete];
                abortSRB = (SRB *) NULL;  
                SCRIPT_VAR(R_ld_AbortBdr_mailbox) = 0;
                break;
           
            default:
               [self Sym8xxAbortCurrent: (SRB *)-1];
        }
    }             
    else
    {
        [self Sym8xxAbortCurrent: (SRB *)-1];
    }

    if ( scriptRestartAddr )
    {
        Sym8xxWriteRegs( chipBaseAddr, DSP, DSP_SIZE, scriptRestartAddr );
    }
}
 

/*-----------------------------------------------------------------------------*
 * Abort currently connected target.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxAbortCurrent:(SRB *)srb
{
    if ( abortCurrentSRB )
    {
        if ( abortCurrentSRB != srb )
        {
//          kprintf("SCSI(Symbios8xx): Multiple abort immediate SRBs - resetting\n\r");
            [self Sym8xxSCSIBusReset: (SRB *)NULL];
        }
        return;
    }
   
    abortCurrentSRB        = srb;
    abortCurrentSRBTimeout = kAbortTimeoutMS / kSCSITimerIntervalMS + 1; 

    /*
     * Issue abort or abort tag depending on whether the is a tagged request
     */
    SCRIPT_VAR(R_ld_AbortCode) = EndianSwap32( ((srb != (SRB *)-1) && (srb->nexus.tag >= MIN_SCSI_TAG)) ? 0x0d : 0x06 );
    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_issueAbort_BDR];

    [self Sym8xxClearFifo];
}

/*-----------------------------------------------------------------------------*
 * This routine clears the script engine's SCSI and DMA fifos.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxClearFifo
{
    u_int8_t		ctest3Reg;
    u_int8_t		stest2Reg;
    u_int8_t		stest3Reg;

    stest2Reg  = Sym8xxReadRegs( chipBaseAddr, STEST2, STEST2_SIZE );
    if ( stest2Reg & ROF )
    {
        Sym8xxWriteRegs( chipBaseAddr, STEST2, STEST2_SIZE, stest2Reg );
    }

    ctest3Reg = Sym8xxReadRegs( chipBaseAddr, CTEST3, CTEST3_SIZE );
    ctest3Reg |= CLF;
    Sym8xxWriteRegs( chipBaseAddr, CTEST3, CTEST3_SIZE, ctest3Reg );

    stest3Reg = Sym8xxReadRegs( chipBaseAddr, STEST3, STEST3_SIZE );
    stest3Reg |= CSF;
    Sym8xxWriteRegs( chipBaseAddr,STEST3, STEST3_SIZE, stest3Reg );

    do
    {
        ctest3Reg = Sym8xxReadRegs( chipBaseAddr, CTEST3, CTEST3_SIZE );
        stest2Reg = Sym8xxReadRegs( chipBaseAddr, STEST3, STEST3_SIZE );
        stest3Reg = Sym8xxReadRegs( chipBaseAddr, STEST3, STEST3_SIZE );
    } 
    while( (ctest3Reg & CLF) || (stest3Reg & CSF) || (stest2Reg & ROF) );            
}

/*-----------------------------------------------------------------------------*
 * This routine processes the target's response to our SDTR message.
 * 
 * We calculate the values for the script engine's timing registers
 * for synchronous registers, and update our tables indicating that
 * requested data transfer mode is in-effect.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxNegotiateSDTR:(SRB *) srb Nexus:(Nexus *)nexus
{
    u_int32_t		x;
    u_int8_t		*pMsg;
    u_int32_t		syncPeriod;
    
    /*
     * If we were not negotiating, the send MsgReject to targets negotiation
     * attempt.
     */
    if ( !(srb->srbRequestFlags & ksrbRFNegotiateSync) )
    {
        [self Sym8xxSendMsgReject: srb];
        return;
    }

    /* 
     * Get pointer to negotiation message received from target.
     */
    pMsg = (u_int8_t *) &SCRIPT_VAR(R_ld_message);

    /*
     * The target's SDTR response contains the (transfer period / 4).
     *
     * We set our sync clock divisor to 1, 2, or 4 giving us a clock rates
     * of:
     *     80Mhz (Period = 12.5ns), 
     *     40Mhz (Period = 25.0ns)
     *     20Mhz (Period = 50.0ns) 
     *
     * This is further divided by the value in the sxfer reg to give us the final sync clock rate.
     *
     * The requested sync period is scaled up by 1000 and the clock periods are scaled up by 10
     * giving a result scaled up by 100. This is rounded-up and converted to sxfer reg values.
     */
    syncPeriod = (u_int32_t)pMsg[3] << 2;
    if ( syncPeriod < 100 )
    {
        nexus->targetParms.scntl3Reg |= SCNTL3_INIT_875_ULTRA;
        x = (syncPeriod * 1000) / 125;
    }
    else if ( syncPeriod < 200 )
    {
        nexus->targetParms.scntl3Reg  |= SCNTL3_INIT_875_FAST;
        x = (syncPeriod * 1000) / 250;
    }
    else 
    {
        nexus->targetParms.scntl3Reg  |= SCNTL3_INIT_875_SLOW;
        x = (syncPeriod * 1000) / 500;
    }
           
    if ( x % 100 ) x += 100;
    
    /*
     * sxferReg  Bits: 5-0 - Transfer offset
     *                 7-6 - Sync Clock Divisor (0 = sync clock / 4)
     */
    nexus->targetParms.sxferReg = ((x/100 - 4) << 5) | pMsg[4];

    /*
     * Update our per-target tables and set-up the hardware regs for this request.
     *
     * On reconnection attempts, the script will use our per-target tables to set-up
     * the scntl3 and sxfer registers in the script engine.
     */
    adapter->targetClocks[srb->target].sxferReg  = nexus->targetParms.sxferReg;
    adapter->targetClocks[srb->target].scntl3Reg = nexus->targetParms.scntl3Reg;

    Sym8xxWriteRegs( chipBaseAddr, SCNTL3, SCNTL3_SIZE, nexus->targetParms.scntl3Reg );
    Sym8xxWriteRegs( chipBaseAddr, SXFER,  SXFER_SIZE,  nexus->targetParms.sxferReg );

    targets[srb->target].flags |= kTFXferSync;

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_clearACK];
}
   
/*-----------------------------------------------------------------------------*
 * This routine processes the target's response to our WDTR message.
 *
 * In addition, if there is a pending SDTR message, this routine sends it
 * to the target.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxNegotiateWDTR:(SRB *) srb Nexus:(Nexus *)nexus
{
    u_int8_t		*pMsg;
    u_int32_t		msgBytesSent;
    u_int32_t           msgBytesLeft;

    /*
     * If we were not negotiating, the send MsgReject to targets negotiation
     * attempt.
     */
   if ( !(srb->srbRequestFlags & ksrbRFNegotiateWide) )
    {
        [self Sym8xxSendMsgReject: srb];
        return;
    }

    /* 
     * Set Wide (16-bit) vs Narrow (8-bit) data transfer mode based on target's response.
     */
    pMsg = (u_int8_t *) &SCRIPT_VAR(R_ld_message);

    if ( pMsg[3] == 1 )
    {
        nexus->targetParms.scntl3Reg |= EWS;
    }
    else
    {
        nexus->targetParms.scntl3Reg &= ~EWS;
    }

    /*
     * Update our per-target tables and set-up the hardware regs for this request.
     *
     * On reconnection attempts, the script will use our per-target tables to set-up
     * the scntl3 and sxfer registers in the script engine.
     */

    adapter->targetClocks[srb->target].scntl3Reg = nexus->targetParms.scntl3Reg;
    Sym8xxWriteRegs( chipBaseAddr, SCNTL3, SCNTL3_SIZE, nexus->targetParms.scntl3Reg );

    targets[srb->target].flags |= kTFXferWide16;

    /*
     * If there any pending messages left for the target, send them now, 
     */
    msgBytesSent = EndianSwap32( nexus->msg.length );
    msgBytesLeft = srb->srbMsgLength - msgBytesSent;
    if ( msgBytesLeft )
    {
        nexus->msg.length = EndianSwap32( msgBytesLeft );
        nexus->msg.ppData = EndianSwap32( EndianSwap32( nexus->msg.ppData ) + msgBytesSent );                
        scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_issueMessageOut];
    }

    /*
     * Otherwise, tell the script we're done with MsgOut phase.
     */
    else
    {
        scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_clearACK];
    }
}  

/*-----------------------------------------------------------------------------*
 * Reject message received from target.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxSendMsgReject:(SRB *) srb
{
    srb->nexus.msg.ppData = EndianSwap32((u_int32_t)&srb->srbPhys->nexus.msgData);
    srb->nexus.msg.length = EndianSwap32(0x01);
    srb->nexus.msgData[0] = 0x07;

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_issueMessageOut];
}
  
/*-----------------------------------------------------------------------------*
 * This routine snoops inquiry data.
 *
 * The indirect SCSI Disk driver in driverKit, does not bother to check the target's 
 * capabilities before enabling Synchronous Negotiation or Cmd Queueing. If things 
 * were left to themselves, targets that did not support tags would be broken since 
 * the request comming from driverKit always indicate that tags are allowed.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxCheckInquiryData: (SRB *)srb
{
    IOMemoryDescriptor			*mem;
    inquiry_reply_t			inqData;
    u_int32_t				inqSize;

    bzero( &inqData, sizeof(inqData) );

    inqSize = (srb->xferDone < sizeof(inqData)) ? srb->xferDone : sizeof(inqData);

    mem = [[ IOSimpleMemoryDescriptor alloc ] initWithAddress: (void *)srb->xferBuffer length: inqSize ];
    [mem setClient: srb->xferClient];

    do
    {
        if ( srb->srbSCSIResult != SR_IOST_GOOD )
        {
            continue;
        }
        if ( srb->xferDone < offsetof(inquiry_reply_t, ir_vendorid) )
        {
            continue;
        }
          
        if ( [mem readFromClient: (void *) &inqData count: inqSize] != inqSize )
        {
            continue;
        }
        if ( inqData.ir_qual != DEVQUAL_OK )
        {
            continue;
        }

        if ( inqData.ir_wbus16 )
        {
            targets[srb->target].flags |= kTFXferWide16Supported;
        }
        if ( inqData.ir_sync )
        {
           targets[srb->target].flags |= kTFXferSyncSupported;
        }
        if ( inqData.ir_cmdque )
        {
           targets[srb->target].flags |= kTFCmdQueueSupported;
        }
    }
    while ( 0 );
    [mem release];
}


/*-----------------------------------------------------------------------------*
 * This routine initiates a SCSI Bus Reset.
 *
 * This may be an internally generated request as part of error recovery or
 * a client's bus reset request.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxSCSIBusReset: (SRB *)srb
{
    if ( srb )
    {
        if ( resetSRB )
        {
            srb->srbSCSIResult = SR_IOST_CMDREJ;
            [srb->srbCmdLock unlockWith: ksrbCmdComplete];  
            return;
        }    
        resetSRB = srb;
    }

    Sym8xxWriteRegs( chipBaseAddr, SCNTL1, SCNTL1_SIZE, SCNTL1_SCSI_RST );
    IODelay( 25 );
    Sym8xxWriteRegs( chipBaseAddr, SCNTL1, SCNTL1_SIZE, SCNTL1_INIT );
}
    
/*-----------------------------------------------------------------------------*
 * This routine handles a SCSI Bus Reset interrupt.
 *
 * The SCSI Bus reset may be generated by a target on the bus, internally from
 * the driver's error recovery or from a client request.
 *
 * Once the reset is detected we establish a settle period where new client requests
 * are blocked in the client thread. In addition we flush all currently executing
 * scsi requests back to the client.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxProcessSCSIBusReset
{
    SRB			*srb   = 0;
    Nexus		*nexus = 0;
    u_int32_t		i;

    /*
     * If we got another bus reset event during the settle period we extend the settle
     * period accordingly.
     */
    if ( resetQuiesceTimer )
    {
        resetQuiesceTimer = kResetQuiesceDelayMS / kSCSITimerIntervalMS + 1;
        return;
    }

    resetSeqNum = srbSeqNum;
//  kprintf("SCSI(Symbios8xx): Reset Started - SRB Seq = %d\n\r", resetSeqNum);

    /*
     * We take the resetQuiesceSem lock which will block new client thread requests.
     * 
     * Note: The client thread checks resetQuiesceTimer for <> 0 before taking this lock.
     */
    [resetQuiesceSem lock];
    resetQuiesceTimer = kResetQuiesceDelayMS / kSCSITimerIntervalMS + 1;

    /*
     * We end any aborts currently in progress
     */
    abortCurrentSRB = (SRB *)NULL;

    if ( abortSRB )
    {
        [abortSRB->srbCmdLock unlockWith: ksrbCmdComplete];
        abortSRB = (SRB *) NULL;  
    }

   [self Sym8xxClearFifo];

   /* 
    * We return anything in our Nexus table back to the client
    */
   for ( i=0; i < MAX_SCSI_TAG; i++ )
    {
        nexus = adapter->nexusPtrsVirt[i];
        if ( nexus == (Nexus *) -1 )
        {
            continue;
        }

        srb = (SRB *)((u_int32_t)nexus - offsetof(SRB, nexus));
  
        srb->srbSCSIResult = SR_IOST_RESET;

        adapter->nexusPtrsVirt[i] = (Nexus *) -1;
        adapter->nexusPtrsPhys[i] = (Nexus *) -1;

        [srb->srbCmdLock unlockWith: ksrbCmdComplete];  
    }

    /*
     * We clear the script's request mailboxes. Any work in the script mailboxes is
     * already in the NexusPtr tables so we have already have handled the SRB/Nexus
     * cleanup.
     */
    for ( i=0; i < MAX_SCHED_MAILBOXES; i++ )
    {
        adapter->schedMailBox[i] = 0;
    }

    SCRIPT_VAR(R_ld_AbortBdr_mailbox) = 0;
    SCRIPT_VAR(R_ld_IOdone_mailbox)   = 0;
    SCRIPT_VAR(R_ld_counter)          = 0;
    mailBoxIndex                      = 0;

    /*
     * Reset the data transfer mode/clocks in our per-target tables back to Async/Narrow 8-bit
     */
    for ( i=0; i < MAX_SCSI_TARGETS; i++ )
    {
        targets[i].flags &= ~(kTFXferSync | kTFXferWide16);

        adapter->targetClocks[i].scntl3Reg = SCNTL3_INIT_875;
        adapter->targetClocks[i].sxferReg  = 0;
    }

    scriptRestartAddr = (u_int32_t) &chipRamAddrPhys[Ent_select_phase];
    Sym8xxWriteRegs( chipBaseAddr, DSP, DSP_SIZE, scriptRestartAddr );

}

/*-----------------------------------------------------------------------------*
 * This routine sets the SIGP bit in the script engine's ISTAT
 * register. This signals the script to wake-up for a WAIT for
 * reselection instruction. The script will then check the mailboxes
 * for work to do.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxSignalScript:(SRB *)srb
{
    Sym8xxWriteRegs( chipBaseAddr, ISTAT, ISTAT_SIZE, SIGP );
}

/*-----------------------------------------------------------------------------*
 * Timeout handler.
 *
 * This routine is scheduled and implements timeouts for the driver.
 *
 * The following items are timed:
 *
 * - Reset settle period.
 * - Aborts
 * - SRBs
 *
 *-----------------------------------------------------------------------------*/
- (void) timeoutOccurred
{
    SRB				*srb;
    Nexus			*nexus;
    u_int32_t			i;
    u_int32_t			nexusIndex;
    u_int32_t			mailboxNexusIndex = -1;

    if ( SCRIPT_VAR( R_ld_IOdone_mailbox ) )
    {
        mailboxNexusIndex = ((IODoneMailBox *)&SCRIPT_VAR(R_ld_IOdone_mailbox))->nexus;
    }

    nexusIndex = EndianSwap32( SCRIPT_VAR(R_ld_nexus_index) );

    /*
     * If we are in a reset settle period, suspend all other timing. 
     *
     * When the reset settle period completes, return the SRB if the
     * client requested the bus reset. Also unlock the reset semaphore.
     */
    if ( resetQuiesceTimer )
    {
        if ( --resetQuiesceTimer )
        {
            goto timeoutOccurred_Exit;
        }

        if ( resetSRB )
        {
            [resetSRB->srbCmdLock unlockWith: ksrbCmdComplete];  
            resetSRB = (SRB *) NULL;
        }
//      kprintf("SCSI(Symbios8xx): Reset Ended - SRB Seq = %d\n\r", srbSeqNum);
        [resetQuiesceSem unlock];
    }

    /*
     * Check whether an abort timed out. If it does, then its likely that a target is
     * hung on the bus. In this case the only recourse is to issue a SCSI Bus reset.
     */
    if ( abortCurrentSRB && abortCurrentSRBTimeout )
    {
        if ( !(--abortCurrentSRBTimeout) )
        {
//          kprintf("SCSI(Symbios8xx): Abort Current SRB failed - resetting\n\r");
            [self Sym8xxSCSIBusReset: (SRB *)NULL];
            goto timeoutOccurred_Exit;
        }
    }

    if ( abortSRB && abortSRBTimeout )
    {
        if ( !(--abortSRBTimeout) )
        {
//          kprintf("SCSI(Symbios8xx): MailBox abort failed - resetting\n\r");
            [self Sym8xxSCSIBusReset: (SRB *)NULL];
            goto timeoutOccurred_Exit;
        }
    }

    /*
     * Scan the Nexus pointer table looking for SRBs to timeout
     */
    for ( i=0; i < MAX_SCSI_TAG; i++ )
    {
         nexus = adapter->nexusPtrsVirt[i];
         if ( nexus == (Nexus *) -1 )
         {
             continue;
         }

         srb = (SRB *)((u_int32_t)nexus - offsetof(SRB, nexus));
         if ( srb->srbTimeout )
         {
             if ( !(--srb->srbTimeout) )
             {  
//               kprintf("SCSI(Symbios8xx): Timeout - Target = %d SRB = %08x SRB Seq = %d\n\r", 
//                            srb->target, (u_int32_t)srb, srb->srbSeqNum );

                 /* If the SRB we're timing out is in the script's IODone mailbox, then
                  * clear the mailbox in addition to timing out the request.
                  */
                 if ( i == mailboxNexusIndex )
                 {
                     SCRIPT_VAR(R_ld_IOdone_mailbox) = 0;
                 }
                 
                 /* 
                  * If the target for the SRB we're timing out is currently connected on 
                  * the SCSI Bus, then issue an abort. Since it is likely that the script
                  * is running in this scenario, we call a Sym8xxAbortScript to shutdown the
                  * script engine in an orderly fashion.
                  */
                 if ( i == nexusIndex )
                 {
                     [self Sym8xxAbortScript];
                     srb->srbSCSIResult = SR_IOST_IOTO;
                     [self Sym8xxAbortCurrent: srb];
                     Sym8xxWriteRegs( chipBaseAddr, DSP, DSP_SIZE, scriptRestartAddr );
                 }  
                
                 /* 
                  * If target for the SRB we're timing out is not on the SCSI bus, then
                  * we mark the request as requiring an Abort and return it to the client
                  * thread. The client thread will then schedule the abort.
                  */ 
                 else
                 {
                     adapter->nexusPtrsVirt[i] = (Nexus *) -1;
                     adapter->nexusPtrsPhys[i] = (Nexus *) -1;
                     
                     srb->srbSCSIResult = SR_IOST_IOTO;
                     srb->srbCmd        = ksrbCmdProcessTimeout;
                     [srb->srbCmdLock unlockWith: ksrbCmdComplete];  
                 }
             }
         }
    }

   
timeoutOccurred_Exit: ;
    /*
     * Reschedule the next timer interval.
     */
    ns_timeout((func) Sym8xxTimerReq, (void *) self, (ns_time_t) kSCSITimerIntervalMS * 1000 * 1000, (int)CALLOUT_PRI_THREAD);
}  
  
/*-----------------------------------------------------------------------------*
 * This routine does a mailbox abort.
 *
 * This type of abort is used for targets not currently connected to the SCSI Bus.
 *
 * The script will select the target and send a tag (if required) followed by the
 * appropriate abort message (abort/abort-tag)
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxAbortBdr:(SRB *) srb
{
    IOAbortBdrMailBox 			abortMailBox;

    abortSRB        = srb;
    abortSRBTimeout = kAbortTimeoutMS / kSCSITimerIntervalMS + 1; 

    /*
     * Setup a script variable containing the abort information.
     */
    abortMailBox.message   = ( srb->nexus.tag < MIN_SCSI_TAG) ? 0x06 : 0x0d;
    abortMailBox.identify  = srb->lun | 0xC0 ;
    abortMailBox.scsi_id   = srb->target;
    abortMailBox.tag       = ( srb->nexus.tag < MIN_SCSI_TAG) ? 0 : srb->nexus.tag;

    SCRIPT_VAR(R_ld_AbortBdr_mailbox) = *(u_int32_t *) &abortMailBox;

    [self Sym8xxSignalScript: srb];
}

/*-----------------------------------------------------------------------------*
 * This routine is used to shutdown the script engine in an orderly fashion.
 *
 * Normally the script engine automatically stops when an interrupt is generated. However,
 * in the case of timeouts we need to change the script engine's dsp reg (instruction pointer).
 * to issue an abort.
 *
 *-----------------------------------------------------------------------------*/
- (void) Sym8xxAbortScript
{
    ns_time_t		currentTime;
    ns_time_t		endTime;

    [self disableAllInterrupts];
    
    /*
     * We set the ABRT bit in ISTAT and spin until the script engine acknowledges the
     * abort or we timeout.
     */
    Sym8xxWriteRegs( chipBaseAddr, ISTAT, ISTAT_SIZE, ABRT );
    
    IOGetTimestamp( &endTime );

    endTime += (kAbortScriptTimeoutMS * 1000 * 1000);

    do
    {
        IOGetTimestamp( &currentTime );

        istatReg = Sym8xxReadRegs( chipBaseAddr, ISTAT, ISTAT_SIZE );

        if ( istatReg & SIP )
        {
            Sym8xxReadRegs( chipBaseAddr, SIST, SIST_SIZE );
            continue;
        }
    
        if ( istatReg & DIP )
        {
            Sym8xxWriteRegs( chipBaseAddr, ISTAT, ISTAT_SIZE, 0x00 );
            Sym8xxReadRegs( chipBaseAddr, DSTAT, DSTAT_SIZE );
            break;
        }
    }
    while ( currentTime < endTime );
    
    istatReg = SIGP;
    Sym8xxWriteRegs( chipBaseAddr, ISTAT, ISTAT_SIZE, istatReg );

    [self enableAllInterrupts];

    if ( currentTime >= endTime )
    {
//      kprintf( "SCSI(Symbios8xx): Abort script failed - resetting bus\n\r" );
        [self Sym8xxSCSIBusReset: NULL];
    }  

  }
    
@end