Source to bsd/dev/ppc/drvAppleMesh_SCSI/MESH_DBDMA.m
/*
* 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@
*/
/**
* Copyright (c) 1994-1996 NeXT Software, Inc. All rights reserved.
* Copyright 1993-1995 by Apple Computer, Inc., all rights reserved.
* Copyright 1997-1998 Apple Computer Inc. All Rights Reserved.
* @author Martin Minow mailto:[email protected]
* @revision 1997.02.13 Initial conversion from Copland sources.
*
* Set tabs every 4 characters.
*
* Edit History
* 1997.02.25 MM Initial conversion from Copland sources.
*/
#import <sys/systm.h>
#import <driverkit/generalFuncs.h>
#import <driverkit/kernelDriver.h>
#import <driverkit/align.h>
#import <driverkit/interruptMsg.h>
#import <driverkit/scsiTypes.h>
#import <driverkit/debugging.h>
#import <driverkit/IODirectDevice.h>
#import <driverkit/IOMemoryDescriptor.h>
#import <driverkit/IOSimpleMemoryDescriptor.h>
#import <driverkit/IOSCSIController.h>
#import <driverkit/IOPower.h>
#import <driverkit/return.h>
#import <bsd/dev/scsireg.h>
#import <mach/kern_return.h>
#import <mach/mach_interface.h>
#import <mach/message.h>
#import <machkit/NXLock.h>
#import <machdep/ppc/proc_reg.h>
#import <machdep/ppc/powermac.h>
#import <machdep/ppc/interrupts.h>
#import <machdep/ppc/dbdma.h>
#import <kernserv/prototypes.h>
#import <objc/objc.h>
extern void flush_cache_v( vm_offset_t pa, unsigned length ); /* Should be available from kernel headers! */
// extern void invalidate_cache_v( vm_offset_t pa, unsigned length );
extern void kprintf( const char *, ... );
extern kern_return_t msg_send_from_kernel( msg_header_t*, int, int );
#import "MESH_DBDMA.h"
#undef ASSERT
#define ASSERT(x)
#if CustomMiniMon
extern globals g; /**** Use custom MiniMon's globals ****/
extern UInt32 gMESH_DBDMA, gMESH_DBDMA_Phys;
#else
globals g; /**** Instantiate the globals ****/
#endif /* CustomMiniMon */
/* Channel Program. Note that this script must match the offsets */
/* specified in AppleMeshDefinitions.h. This script is copied into */
/* the channel command area (with appropriate entries byte-swapped */
/* so it ends up with the correct endian-ness). */
/* Lines beginning with "slash, star, star, slash" are modified */
/* by the driver before it starts the Channel Program. */
const DBDMADescriptor gDescriptorList[] =
{
/* 0x00 kcclProblem - Branch here for anomalies */
{ MESH_REG( kMeshInterruptMask, kMeshIntrMask ) }, // Enable MESH interrupt
{ STOP( kcclStageCCLx ) }, // anomaly
/* 0x20 through 0x60 - Data for information phases: */
{ RESERVE }, // kcclCMDOdata - CDB ( 6,10,12,16 bytes)
{ RESERVE }, // kcclMSGOdata - MSGO data (last byte @3F)
{ RESERVE }, // kcclMSGIdata - MSGI data & STATUS
{ RESERVE }, // kcclSenseCDB - CDB for (auto) Sense
{ RESERVE }, // kcclBatchSize, kcclStageLabel
/* 0x70 - kcclSense - AutoSense input: */
{ MESH_REG( kMeshTransferCount1, 0x00 ) }, // set MESH xfer count to 255
{ MESH_REG( kMeshTransferCount0, kMaxAutosenseByteCount & 0xFF )},
{ MESH_REG( kMeshSequence, kMeshDataInCmd | kMeshSeqDMA )},// Data-In to Seq register
{ SENSE( kMaxAutosenseByteCount ) }, // Sense INPUT
{ BRANCH( kcclGetStatus ) }, // do finish sequence
/* 0xC0 - kcclPrototype - Prototype MESH 4-command Transfer sequence: */
{ MOVE_4( kcclBatchSize, 0, kRelAddressCP ) }, // MESH batch size
{ MESH_REG( kMeshTransferCount1, 0 ) }, // Set high order Transfer Count
{ MESH_REG( kMeshTransferCount0, 0 ) }, // Set low order Transfer Count
{ MESH_REG( kMeshSequence, kMeshDataInCmd | kMeshSeqDMA )}, // Assume Data-In
{ RESERVE }, // spare
{ RESERVE }, // spare
/* 0x120 kcclStart - Arbitrate (START CHANNEL PROGRAM HERE): */
/* 0x140 kcclBrProblem */
{ STAGE( kcclStageArb ) },
{ MESH_REG( kMeshSequence, kMeshArbitrateCmd ) }, // issue Arbitrate
{ BR_IF_PROBLEM }, // branch if exception or error
/* 0x150 - Select with Attention: */
{ STAGE( kcclStageSelA ) },
{ CLEAR_CMD_DONE },
{ MESH_REG( kMeshSequence, kMeshSelectCmd | kMeshSeqAtn ) }, // select with attention
{ BR_IF_PROBLEM }, // branch if failed
/* 0x190 kcclMsgoStage- Message-Out: */
{ STAGE( kcclStageMsgO ) },
{ CLEAR_CMD_DONE },
/* 0x1B0 kcclMsgoBranch - modify this BRANCH to fall through for multibyte messages: */
/**/{ BRANCH( kcclLastMsgo ) }, // kcclMsgoBranch - go do only byte of Msg
/* 0x1C0 - do all but last byte of multibyte message: */
{ MESH_REG( kMeshTransferCount1, 0x00 ) }, // count does include last byte
/**/{ MESH_REG( kMeshTransferCount0, 0xFF ) }, // kcclMsgoMTC - modify MESH xfer count here
{ MESH_REG( kMeshSequence, kMeshMessageOutCmd | kMeshSeqAtn | kMeshSeqDMA ) }, // DMA MsgO with ATN
/**/{ MSGO( kcclMSGOdata, 255 ) }, // kcclMsgoDTC - output all but last byte
{ CLEAR_CMD_DONE },
/* 0x210 kcclLastMsgo - wait for REQ signal before dropping ATN: */
{ MESH_REG( kMeshInterruptMask, 0 ) }, // inhibit MESH interrupt
{ MESH_REG_WAIT( kMeshSequence, kMeshStatusCmd | kMeshSeqAtn ) }, // gen PhaseMM
{ CLEAR_INT_REG }, // clear PhaseMM & CmdDone
{ MESH_REG( kMeshInterruptMask, kMeshIntrException | kMeshIntrError ) }, // re-enable ERR/EXC Ints
/* 0x250 - put out the last or only byte of Message-Out phase: */
{ MESH_REG( kMeshTransferCount1, 0x00 ) },
{ MESH_REG( kMeshTransferCount0, 0x01 ) },
{ MESH_REG( kMeshSequence, kMeshMessageOutCmd | kMeshSeqDMA ) },// no more ATN
{ MSGO( kcclMSGOLast, 1 ) },
/* 0x290 kcclCmdoStage - Command Out: */
{ STAGE( kcclStageCmdO ) },
{ CLEAR_CMD_DONE },
{ MESH_REG( kMeshTransferCount1, 0x00 ) },
/**/{ MESH_REG( kMeshTransferCount0, 0x06 ) }, // kcclCmdoMTC - Set MESH xfer count to 6
{ MESH_REG( kMeshSequence, kMeshCommandCmd | kMeshSeqDMA )}, // Command phase with DMA on
/**/{ CMDO( 6 ) }, // kcclCmdoDTC - output the CDB
/* 0x2F0 - DATA XFER - branch to the built CCL @ 0x05D0: */
/* also, kcclReselect - reselect code enters here: */
{ CLEAR_CMD_DONE },
{ STAGE( kcclStageXfer ) },
{ BRANCH( kcclDataXfer ) }, // go do Xfer CCL
/* 0x320 kcclOverrun - dump excess data in the bit bucket: */
/* Exc and Err are still disabled. */
{ STAGE( kcclStageBucket ) },
{ MESH_REG( kMeshTransferCount1, 0x00 ) }, // set MESH Transfer Count to max
{ MESH_REG( kMeshTransferCount0, 0x00 ) },
{ CLR_PHASEMM },
{ MESH_REG( kMeshInterruptMask, kMeshIntrException | kMeshIntrError ) }, // re-enable ERR/EXC Ints
/**/{ MESH_REG( kMeshSequence, kMeshDataInCmd | kMeshSeqDMA ) }, // set Seq Reg
/**/{ BUCKET }, // OUT/INPUT_LAST the bits
{ BR_NO_PROBLEM( kcclOverrunDBDMA ) }, // loop til PhaseMismatch
{ BR_IF_PROBLEM }, // take the interrupt now
/* 0x3B0 kcclSyncCleanUp - clean up after Sync xfer: */
{ CLEAR_INT_REG }, // clear PhaseMM & CmdDone (& Err?)
{ MESH_REG( kMeshInterruptMask, kMeshIntrException | kMeshIntrError ) }, // re-enable ERR/EXC Ints
/* 0x3D0 kcclGetStatus - setup CCL for status, command complete and bus free: */
{ STAGE( kcclStageStat ) },
{ MESH_REG( kMeshTransferCount1, 0x00 ) },
{ MESH_REG( kMeshTransferCount0, 0x01 ) }, // set MESH xfer count to 1
{ MESH_REG( kMeshSequence, kMeshStatusCmd | kMeshSeqDMA )},// Status-in phase with DMA on
{ STATUS_IN }, // input the status byte
/* 0x420 - Message In: */
{ STAGE( kcclStageMsgI ) },
{ CLEAR_CMD_DONE },
{ MESH_REG( kMeshTransferCount1, 0x00 ) },
{ MESH_REG( kMeshTransferCount0, 0x01 ) }, // set MESH xfer count to 1
{ MESH_REG( kMeshSequence, kMeshMessageInCmd | kMeshSeqDMA )}, // Status-in phase with DMA on
{ MSGI( 1 ) }, // get the Message-In byte
/* 0x480 - Bus Free: */
{ STAGE( kcclStageFree ) },
{ CLEAR_CMD_DONE },
{ MESH_REG( kMeshSequence, kMeshEnableReselect ) }, // Enable Reselect
{ MESH_REG( kMeshSequence, kMeshBusFreeCmd ) }, // Bus Free phase
{ BR_IF_PROBLEM }, // branch if failed
/* 0x4D0 kcclMESHintr - Good completion: */
{ STAGE( kcclStageGood ) },
{ MESH_REG( kMeshInterruptMask, kMeshIntrMask ) }, // latch MESH interrupt
{ STOP( kcclStageStop ) }, // Stop
/* The rest of the Channel Program area is used for autosense */
/* and data transfer channel commands: */
/* kcclSenseBuffer Autosense area */
/* kcclDataXfer Start of data transfer channel commands */
/* kcclSenseResult Autosense result stored here */
}; /* end gDescriptorList structure */
const UInt32 gDescriptorListSize = sizeof( gDescriptorList );
enum /* values for g.intLevel: */
{
kLevelISR = 0x80, /* In Interrupt Service Routine */
kLevelLocked = 0x40, /* MESH interrupts locked out */
kLevelSIH = 0x20, /* In Secondary Interrupt Handler */
kLevelLatched = 0x10 /* Interrupt latched */
};
// IONamedValue scsiChipRegisterStrings[] = { { 0, NULL, } };
static int getConfigParam( id configTable, const char *paramName );
static unsigned int GetSCSICommandLength( const cdb_t *cdbPtr, unsigned int defaultLength );
/* MAX_DMA_XFER is set so that we don't have to worry about the */
/* ambiguous "zero" value in the MESH and DBDMA transfer registers */
/* that can mean either 65536 bytes or zero bytes. */
#define MAX_DMA_XFER 0x0000F000
#define ONE_SECOND 1 /* for IOScheduleFunc and serviceTimeoutInterrupt */
/* Template for command message sent to the IO thread: */
static const msg_header_t cmdMessageTemplate =
{
0, /* msg_unused */
1, /* msg_simple */
sizeof( msg_header_t ), /* msg_size */
MSG_TYPE_NORMAL, /* msg_type */
PORT_NULL, /* msg_local_port */
PORT_NULL, /* msg_remote_port - filled in */
IO_COMMAND_MSG /* msg_id */
};
/* Template for timeout message. */
static const msg_header_t gTimeoutMsgTemplate =
{
0, /* msg_unused */
1, /* msg_simple */
sizeof( msg_header_t ), /* msg_size */
MSG_TYPE_NORMAL, /* msg_type */
PORT_NULL, /* msg_local_port */
PORT_NULL, /* msg_remote_port - filled in */
IO_TIMEOUT_MSG /* msg_id */
};
static port_t gKernelInterruptPort; /* for int/timeout msgs */
static void serviceTimeoutInterrupt( void *arg );
static AppleMesh_SCSI *gInstance;
#if USE_ELG && !CustomMiniMon
void AllocateEventLog( UInt32 size )
{
if ( !g.evLogBuf ) g.evLogBuf = (UInt8*)kalloc( size );
if ( !g.evLogBuf )
kprintf( "probe - MESH evLog allocation failed " );
g.evLogFlag = 0; /* assume insufficient memory */
g.evLogBufp = g.evLogBuf;
if ( g.evLogBuf )
{
g.evLogBufe = g.evLogBufp + kEvLogSize - 0x20; // ??? overran buffer?
g.evLogFlag = 0xFEEDBEEF;
// g.evLogFlag = 0x0333;
}
return;
}/* end AllocateEventLog */
void EvLog( UInt32 a, UInt32 b, UInt32 ascii, char* str )
{
register UInt32 *lp; /* Long pointer */
ns_time_t time;
if ( g.evLogFlag == 0 )
return;
IOGetTimestamp( &time );
lp = (UInt32*)g.evLogBufp;
g.evLogBufp += 0x10;
if ( g.evLogBufp >= g.evLogBufe ) /* handle buffer wrap around if any */
{ g.evLogBufp = g.evLogBuf;
if ( g.evLogFlag != 0xFEEDBEEF ) // make 0xFEEDBEEF a symbolic ???
g.evLogFlag = 0; /* stop tracing if wrap undesired */
}
/* compose interrupt level with 3 byte time stamp: */
*lp++ = (g.intLevel << 24) | ((time >> 10) & 0x003FFFFF); // ~ 1 microsec resolution
*lp++ = a;
*lp++ = b;
*lp = ascii;
if( g.evLogFlag == 'step' )
kprintf( str );
return;
}/* end EvLog */
void Pause( UInt32 a, UInt32 b, UInt32 ascii, char* str )
{
char work [256 ];
char name[] = "AppleMeshSCSI:";
char *bp = work;
UInt8 x;
int i;
EvLog( a, b, ascii, str );
EvLog( '****', '** P', 'ause', "*** Pause" );
bcopy( name, bp, sizeof( name ) );
bp += sizeof( name ) - 1;
*bp++ = '{'; // prepend p1 in hex:
for ( i = 7; i >= 0; --i )
{
x = a & 0x0F;
if ( x < 10 )
x += '0';
else x += 'A' - 10;
bp[ i ] = x;
a >>= 4;
}
bp += 8;
*bp++ = ' '; // prepend p2 in hex:
for ( i = 7; i >= 0; --i )
{
x = b & 0x0F;
if ( x < 10 )
x += '0';
else x += 'A' - 10;
bp[ i ] = x;
b >>= 4;
}
bp += 8;
*bp++ = '}';
*bp++ = ' ';
for ( i = sizeof( work ) - (int)(bp - work); i && (*bp++ = *str++); --i ) ;
kprintf( work );
// call_kdp(); // ??? use kdp=3 in boot parameters
return;
}/* end Pause */
#endif /* not CustomMiniMon */
/* serviceTimeoutInterrupt - Handle timeouts. */
/* This function is invoked in kernel context on a DriverKit thread. */
/* Just send a timeout message to the IO thread to wake it up. */
static void serviceTimeoutInterrupt( void *arg )
{
msg_header_t msg = gTimeoutMsgTemplate;
ELG( 0, 0, 'Tick', "serviceTimeoutInterrupt\n" );
/* roll me another one: */
IOScheduleFunc( serviceTimeoutInterrupt, (void*)0x333, ONE_SECOND );
/* Tell the IO thread: */
msg.msg_remote_port = gKernelInterruptPort;
msg_send_from_kernel( &msg, MSG_OPTION_NONE, 0 );
return;
}/* end serviceTimeoutInterrupt */
/* Used in timeoutOccurred to determine if specified cmdBuf has timed out. */
/* Returns YES if timeout, else NO. */
static Boolean isCmdTimedOut( CommandBuffer *cmdBuf )
{
IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
ns_time_t now, expire;
Boolean result;
IOGetTimestamp( &now );
expire = cmdBuf->startTime +
(1000000000ULL * (unsigned long long)scsiReq->timeoutLength);
result = (now > expire);
if ( result ) ELG( cmdBuf, cmdBuf->scsiReq->timeoutLength, 'Tim-', "isCmdTimedOut" );
return result;
}/* end isCmdTimedOut */
/* Implement the public methods for the MESH controller. */
@implementation AppleMesh_SCSI
/* Create and initialize one instance of AppleMesh_SCSI. */
/* The work is done by architecture- and chip-specific modules. */
+ (Boolean) probe : deviceDescription
{
Boolean result;
gInstance = [ self alloc ]; /* Instantiate yourself */
g.intLevel = 0;
MakeTimestampRecord( 512 ); /* conditionally compiled */
#if USE_ELG
AllocateEventLog( kEvLogSize );
ELG( g.evLogBufp, &g.evLogFlag, 'Prob', "probe - event logging set up.\n" );
#endif /* USE_ELG */
/* Perform device-specific initialization. */
/* Free the instance on failure. */
if ( [ gInstance InitializeHardware : deviceDescription ] == nil )
result = NO;
else result = YES;
return result;
}/* end probe */
/* The driver is shutting down. Kill everything worth killing. */
- free
{
CommandBuffer cmdBuf;
/* First kill the IO thread if running. */
if ( gFlagIOThreadRunning )
{
cmdBuf.op = kCommandAbortRequest;
cmdBuf.scsiReq = NULL;
[ self executeCmdBuf : &cmdBuf ];
}
if ( incomingCmdLock )
[ incomingCmdLock free ];
dbdma_stop( DBDMA_MESH_SCSI );
if ( cclLogAddr )
{
IOFree( cclLogAddr, cclLogAddrSize );
cclLogAddr = NULL;
}
/* ??? Unmap physical address mapping to registers. */
return [ super free ];
}/* end free */
/* Return required DMA alignment for current architecture. */
/* We specify 8-byte alignment to avoid a bug in the Grand Central chip:*/
/* if (Reading */
/* && (kdbdmaSetFlush || kdbdmaClrRun) */
/* && no bytes transferred yet */
/* && buffer not 8-byte aligned) */
/* { */
/* THEN memory in front of buffer will be trashed. */
/* } */
- (void) getDMAAlignment : (IODMAAlignment*)alignment
{
alignment->readStart = DBDMA_ReadStartAlignment;
alignment->writeStart = DBDMA_WriteStartAlignment;
alignment->readLength = 0;
alignment->writeLength = 0;
return;
}/* end getDMAAlignment */
/* Statistics support. */
- (unsigned int) numQueueSamples
{
return gTotalCommands;
}/* end numQueueSamples */
- (unsigned int) sumQueueLengths
{
return gQueueLenTotal;
}/* end sumQueueLengths */
- (unsigned int) maxQueueLength
{
return gMaxQueueLen;
}/* end maxQueueLength */
- (void) resetStatistics
{
gMaxQueueLen = 0;
gQueueLenTotal = 0;
gMaxQueueLen = 0;
return;
}/* resetStatistics */
/* Do a SCSI command, as specified by an IOSCSIRequest. */
/* All the work is done by the IO thread. */
/* @param scsiReq The request to execute */
/* @param buffer The data buffer to transfer to/from, if any */
/* @param client The data buffer owner task (for VM munging) */
/* */
/* This method is called from IOSCSIDevice */
- (sc_status_t) executeRequest : (IOSCSIRequest*)scsiReq
buffer : (void*)buffer
client : (vm_task_t)client
{
IOMemoryDescriptor *mem = NULL;
sc_status_t scsiStatus = SR_IOST_GOOD; /* Fool compiler */
ELG( scsiReq->lun<<16 | scsiReq->target, scsiReq, 'sReq', "executeRequest (buffer)" );
ELG( buffer, scsiReq->maxTransfer, 'Buff', "executeRequest" );
/* Create a simple IO memory descriptor for this client, */
/* then toss it to the common method. */
if ( buffer )
{
if ( scsiReq->read && ((UInt32)buffer & (DBDMA_ReadStartAlignment - 1)) )
{
ELG( scsiReq->maxTransfer, buffer, 'Aln-', "executeRequest/simple buffer - unaligned read buffer." );
return SR_IOST_ALIGN;
}
mem = [ [ IOSimpleMemoryDescriptor alloc ]
initWithAddress : (void*)buffer
length : scsiReq->maxTransfer ];
[ mem setClient : client ];
}
scsiStatus = [ self executeRequest : scsiReq ioMemoryDescriptor : mem ];
if ( mem )
[ mem release ];
return scsiStatus;
}/* end executeRequest buffer */
/* Execute a SCSI request using an IOMemoryDescriptor. */
/* This allows callers to provide (kernel-resident) logical */
/* scatter-gather lists. For compatibility with existing */
/* implementations, the low-level SCSI device driver must */
/* first ensure that executeRequestWithIOMemoryDescriptor */
/* is supported by executing: */
/* [ controller respondsToSelector : executeRequestWithIOMemoryDescriptor ] */
- (sc_status_t) executeRequest : (IOSCSIRequest*)scsiReq
ioMemoryDescriptor : (IOMemoryDescriptor*)ioMemoryDescriptor
{
CommandBuffer commandBuffer;
ELG( scsiReq->lun<<16 | scsiReq->target, scsiReq, 'dReq', "executeRequest (IOMemoryDescriptor)" );
ELG( 0, ioMemoryDescriptor, 'iomd', "executeRequest" );
scsiReq->driverStatus = SR_IOST_INVALID; /* "In progress" */
if ( ioMemoryDescriptor )
{
[ ioMemoryDescriptor setMaxSegmentCount : MAX_DMA_XFER ];
[ ioMemoryDescriptor state : &commandBuffer.savedDataState ];
}
bzero( &commandBuffer, sizeof( CommandBuffer ) );
commandBuffer.op = kCommandExecute;
commandBuffer.scsiReq = scsiReq;
commandBuffer.mem = ioMemoryDescriptor;
[ self executeCmdBuf : &commandBuffer ];
#if TIMESTAMP_AT_IOCOMPLETE
[ self logTimestamp : "IO complete" ]; /* After RESULT macro */
#endif
return commandBuffer.scsiReq->driverStatus;
}/* end executeRequest IOMemoryDescriptor */
/* Reset the SCSI bus. All the work is done by the IO thread. */
- (sc_status_t) resetSCSIBus
{
CommandBuffer commandBuffer;
commandBuffer.op = kCommandResetBus;
commandBuffer.scsiReq = NULL;
[ self executeCmdBuf : &commandBuffer ];
return SR_IOST_GOOD; /* can not fail */
}/* end resetSCSIBus */
/* The following 6 methods, */
/* interruptOccurred, interruptOccurredAt, otherOccurred, */
/* receiveMsg, timeoutOccurred, commandRequestOccurred, */
/* are all called from the IO thread in IODirectDevice. */
/* Called from the IO thread when it receives an interrupt message. */
/* Currently all work is done by chip-specific module; maybe we should */
/* put this method there.... */
- (void) interruptOccurred
{
g.intLevel |= kLevelISR; /* set ISR flag */
g.intLevel &= ~kLevelLatched; /* clear latched */
ELG( dbdmaAddr->d_status, dbdmaAddr->d_cmdptrlo, 'Int+', "interruptOccurred." );
// ELG( *(UInt32*)0xF3000024, *(UInt32*)0xF300002C, 'Int ', "interruptOccurred." );
[ self DoHardwareInterrupt ]; /**** HANDLE THE INTERRUPT ****/
// ELG( gActiveCommand, *(UInt32*)0xF300002C, 'Intx', "interruptOccurred." );
g.intLevel &= ~kLevelISR; /* clear ISR flag */
return;
}/* end interruptOccurred */
/* These three should not occur; they are here as error traps. */
/* All three are called out from the IO thread upon receipt of */
/* messages which it should not be seeing. */
- (void) interruptOccurredAt : (int)localNum
{
PAUSE( 0, localNum, 'int@', "interruptOccurredAt.\n" );
return;
}/* end interruptOccurredAt */
- (void) otherOccurred : (int)id
{
PAUSE( 0, id, 'Othr', "otherOccurred.\n" );
return;
}/* end otherOccurred */
- (void) receiveMsg
{
PAUSE( 0, 0, 'RcvM', "receiveMsg.\n" );
/* We have to let IODirectDevice take care of this (i.e., */
/* dequeue the bogus message). */
[ super receiveMsg ];
return;
}/* end receiveMsg */
/* This method is invoked by DriverKit when it receives a message */
/* generated by the function serviceTimeoutInterrupt() which was called */
/* by the kernel on some DriverKit thread. */
- (void) timeoutOccurred
{
CommandBuffer *cmdBuf = gActiveCommand;
CommandBuffer *nextCmdBuf;
if ( g.intLevel & kLevelLatched )
{
ELG( cmdBuf, 0, 'TocL', "timeoutOccurred - interrupt already latched; do nothing" );
return;
}
g.intLevel |= kLevelISR; /* set IOthread-running-flag */
ELG( CCLWord( kcclStageLabel ), dbdmaAddr->d_cmdptrlo, 'Tock', "timeoutOccurred - tick.\n" );
[ self GetHBARegsAndClear : FALSE ]; /* get the MESH registers */
/* If gActiveCommand timed out: */
if ( cmdBuf )
{ if ( isCmdTimedOut( cmdBuf )
&& ((CCLWord( kcclStageLabel ) != kcclStageFree)
|| (CCLWord( kcclStageLabel ) != kcclStageGood) ) )
{
dbdma_flush( DBDMA_MESH_SCSI ); /* DBDMA may be hung in */
dbdma_stop( DBDMA_MESH_SCSI ); /* middle of transfer. */
// invalidate_cache_v( (vm_offset_t)cclLogAddr, cclLogAddrSize );
cmdBuf->scsiReq->driverStatus = SR_IOST_IOTO;
[ self ioComplete : cmdBuf ];
[ self AbortActiveCommand ];
g.intLevel &= ~kLevelISR; /* clear IOthread-running-flag */
return;
}
}
else /* Move any/all timed-out disconnected commands to abortCmdQ: */
{
cmdBuf = (CommandBuffer*)queue_first( &disconnectedCmdQ );
while ( !queue_end( &disconnectedCmdQ, (queue_entry_t)cmdBuf ) )
{
nextCmdBuf = (CommandBuffer*)queue_next( &cmdBuf->link );
if ( isCmdTimedOut( cmdBuf ) )
{ /* Move cmdBuf from disconnectQ to abortQ: */
queue_remove( &disconnectedCmdQ, cmdBuf, CommandBuffer*, link );
queue_enter( &abortCmdQ, cmdBuf, CommandBuffer*, link );
cmdBuf->scsiReq->driverStatus = SR_IOST_IOTO;
}
cmdBuf = nextCmdBuf;
}/* end WHILE scanning commands in the disconnected queue */
[ self AbortDisconnectedCommand ];
}
g.intLevel &= ~kLevelISR; /* clear IOthread-running-flag */
return;
}/* end timeoutOccurred */
/* Process all commands in incomingCmdQ. At most one of these */
/* will become gActiveCommand. The remainder of kCommandExecute commands*/
/* go to pendingCmdQ. Other types of commands (such as bus reset) */
/* are executed immediately. */
/* This method is called from IODirectDevice. */
/* */
/* Note that we don't have a concept of frozen queue. */
- (void) commandRequestOccurred
{
CommandBuffer *cmdBuf, *pendCmd;
[ incomingCmdLock lock ];
while ( !queue_empty( &incomingCmdQ ) )
{
cmdBuf = (CommandBuffer*)queue_first( &incomingCmdQ );
queue_remove( &incomingCmdQ, cmdBuf, CommandBuffer*, link );
[ incomingCmdLock unlock ];
ELG( gActiveCommand, cmdBuf, 'CRO+', "commandRequestOccurred" );
switch ( cmdBuf->op )
{
case kCommandResetBus:
/* Note all active and disconnected commands will be terminated.*/
[ self threadResetBus : "Reset Command Received" ];
[ self ioComplete : cmdBuf ];
break;
case kCommandAbortRequest:
/* 1. Abort all active, pending, and disconnected commands. */
/* 2. Notify caller of completion. */
/* 3. Self-terminate. */
[ self abortAllCommands : SR_IOST_INT ];
pendCmd = (CommandBuffer*)queue_first( &pendingCmdQ );
while ( !queue_end( &pendingCmdQ, (queue_entry_t)pendCmd ) )
{
pendCmd->scsiReq->driverStatus = SR_IOST_INT;
[ self ioComplete : pendCmd ];
pendCmd = (CommandBuffer*)queue_next( &pendCmd->link );
}
[ cmdBuf->cmdLock lock ];
[ cmdBuf->cmdLock unlockWith : CMD_COMPLETE ];
IOExitThread();
/***** not reached *****/
case kCommandExecute:
[ self threadExecuteRequest : cmdBuf ];
break;
}/* end SWITCH */
[ incomingCmdLock lock ];
}/* end WHILE queue not empty */
[ incomingCmdLock unlock ];
return;
}/* end commandRequestOccurred */
/* Power management methods. All we care about is power off, when */
/* we must reset the SCSI bus due to the Compaq BIOS's lack of a */
/* SCSI reset, which causes a hang if we have set up targets for */
/* sync data transfer mode. */
- (IOReturn) getPowerState : (PMPowerState*)state_p
{
return IO_R_UNSUPPORTED;
}/* end getPowerState */
- (IOReturn) setPowerState : (PMPowerState) state
{
ELG( 0, state, 'sPwr', "setPowerState.\n" );
if ( state == PM_OFF )
{
// [ self scsiReset ];
// ** ** ** TBS: [ self powerDown ];
return IO_R_SUCCESS;
}
return IO_R_UNSUPPORTED;
}/* end setPowerState */
- (IOReturn) getPowerManagement : (PMPowerManagementState*)state_p
{
return IO_R_UNSUPPORTED;
}/* end getPowerManagement */
- (IOReturn) setPowerManagement : (PMPowerManagementState)state
{
return IO_R_UNSUPPORTED;
}/* end setPowerManagement */
#if APPLE_MESH_ENABLE_GET_SET
- (IOReturn) setIntValues : (unsigned*) parameterArray
forParameter : (IOParameterName) parameterName
count : (unsigned int) count
{
int target;
PerTargetData *perTargetPtr;
IOReturn ioReturn = IO_R_INVALID_ARG;
if ( strcmp( parameterName, APPLE_MESH_AUTOSENSE ) == 0 )
{
if ( count == 1 )
{
autoSenseEnable = parameterArray[0] ? 1 : 0;
ELG( 0, autoSenseEnable, 'sVas', "setIntValues - autoSense\n" );
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_CMD_QUEUE ) == 0 )
{
if ( count == 1 )
{
cmdQueueEnable = parameterArray[0] ? 1 : 0;
ELG( 0, cmdQueueEnable, 'sVqe', "setIntValues - cmdQueueEnable\n" );
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_SYNC ) == 0 )
{
if ( count == 1 )
{
syncModeEnable = parameterArray[0] ? 1 : 0;
ELG( 0, syncModeEnable, 'sVse', "setIntValues - syncModeEnable\n" );
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_FAST_SCSI ) == 0 )
{
if ( count == 1 )
{
fastModeEnable = parameterArray[0] ? 1 : 0;
ELG( 0, fastModeEnable, 'sVfe', "setIntValues - fastModeEnable\n" );
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_RESET_TARGETS ) == 0 )
{
if ( count == 0 )
{
/* Re-enable sync and command queuing. */
/* The disable bits persist after a reset. */
for ( target = 0; target < SCSI_NTARGETS; target++ )
{
perTargetPtr = &gPerTargetData[ target ];
perTargetPtr->syncDisable = FALSE;
perTargetPtr->maxQueue = 0;
perTargetPtr->inquiry_7 = 0;
}
ELG( 0, 0, 'sVrt', "setIntValues - reset targets\n" );
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_RESET_TIMESTAMP ) == 0 )
{
ResetTimestampIndex();
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_ENABLE_TIMESTAMP ) == 0 )
{
EnableTimestamp( TRUE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_DISABLE_TIMESTAMP ) == 0 )
{
EnableTimestamp( FALSE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_PRESERVE_FIRST_TIMESTAMP ) == 0 )
{
PreserveTimestamp( TRUE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_PRESERVE_LAST_TIMESTAMP ) == 0 )
{
PreserveTimestamp( FALSE );
ioReturn = IO_R_SUCCESS;
}
else
{
ioReturn [ super setIntValues : parameterArray
forParameter : parameterName
count : count ];
}
return ioReturn;
}/* end setIntValues */
- (IOReturn) getIntValues : (unsigned*) parameterArray
forParameter : (IOParameterName) parameterName
count : (unsigned*) count /* in/out */
{
IOReturn ioReturn = IO_R_INVALID_ARG;
if ( strcmp( parameterName, APPLE_MESH_AUTOSENSE) == 0 )
{
if ( *count == 1 )
{
parameterArray[0] = autoSenseEnable;
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_CMD_QUEUE ) == 0 )
{
if ( *count == 1 )
{
parameterArray[0] = cmdQueueEnable;
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_SYNC ) == 0 )
{
if ( *count == 1 )
{
parameterArray[0] = syncModeEnable;
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_FAST_SCSI ) == 0 )
{
if ( *count == 1 )
{
parameterArray[0] = fastModeEnable;
ioReturn = IO_R_SUCCESS;
}
}
else if ( strcmp( parameterName, APPLE_MESH_RESET_TIMESTAMP ) == 0 )
{
ResetTimestampIndex();
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_ENABLE_TIMESTAMP ) == 0 )
{
EnableTimestamp( TRUE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_DISABLE_TIMESTAMP ) == 0 )
{
EnableTimestamp( FALSE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_PRESERVE_FIRST_TIMESTAMP ) == 0 )
{
PreserveTimestamp( TRUE );
ioReturn = IO_R_SUCCESS;
}
else if ( strcmp( parameterName, APPLE_MESH_PRESERVE_LAST_TIMESTAMP ) == 0 )
{
PreserveTimestamp( FALSE );
ioReturn = IO_R_SUCCESS;
}
else
{
ioReturn = [ super getIntValues : parameterArray
forParameter : parameterName
count : count ];
}
return ioReturn;
}/* end getIntValues */
#endif APPLE_MESH_ENABLE_GET_SET
@end /* AppleMesh_SCSI */
@implementation AppleMesh_SCSI( Hardware )
/* Perform MESH-specific initialization. */
/* Fetch the device's bus address and interrupt port number. */
/* Also, allocate one page of memory for the channel program. */
- InitializeHardware : deviceDescription
{
IOReturn ioReturn = IO_R_SUCCESS;
id result = self;
kern_return_t kernelReturn;
UInt8 target, lun;
id configTable;
const char *configValue;
UInt8 deviceNumber;
UInt8 functionNumber;
UInt8 busNumber;
configTable = [ deviceDescription configTable ];
ASSERT( configTable );
configValue = [ configTable valueForStringKey: "Bus Type" ];
if ( configValue == NULL || strcmp( configValue, "PPC" ) )
{
PAUSE( 0, 'init', 'Hdw-', "InitializeHardware - bus type NG.\n" );
ioReturn = IO_R_NO_DEVICE;
}
if ( ioReturn == IO_R_SUCCESS )
{
#if 0 // ** ** ** Need correct definition ** ** **
ioReturn = [ deviceDescription getPCIDevice
: &deviceNumber
function : &functionNumber
bus : &busNumber ];
#else
deviceNumber = 0;
functionNumber = 0;
busNumber = 0;
kernelReturn = 0;
#endif
if ( ioReturn != IO_R_SUCCESS )
PAUSE( 0, ioReturn, 'iHd-', "InitializeHardware - Can't get PCI device information.\n" );
}
if ( configValue )
{
[ configTable freeString : configValue ];
configValue = NULL;
}
if ( ioReturn == IO_R_SUCCESS )
ioReturn = [ self AllocHdwAndChanMem : deviceDescription ];
if ( ioReturn == IO_R_SUCCESS )
{
for ( target = 0; target < SCSI_NTARGETS; target++ )
{
gPerTargetData[ target ].syncParms = kSyncParmsAsync;
gPerTargetData[ target ].negotiateSDTR = kSyncParmsFast; // negotiate Fast
gPerTargetData[ target ].inquiry_7 = 0;
}
/* All of the addresses are established. */
/* Check that the hardware is present and working. */
ioReturn = [ self DoHBASelfTest ];
}
if ( ioReturn == IO_R_SUCCESS )
{
/* Tell the superclass to initialize our IO thread. */
/* After this, we should be able to execute SCSI requests. */
if ( [ super initFromDeviceDescription : deviceDescription ] == NULL )
{
PAUSE( 0, 0, 'i h-', "InitializeHardware - Host Adaptor was not initialized. Fatal.\n" );
ioReturn = IO_R_NO_DEVICE;
}
}
if ( ioReturn == IO_R_SUCCESS )
{
gFlagIOThreadRunning = 1;
/* Initialize local variables. Note that activeArray and */
/* perTarget arrays are zeroed by objc runtime. */
queue_init( &disconnectedCmdQ );
queue_init( &incomingCmdQ );
queue_init( &pendingCmdQ );
queue_init( &abortCmdQ );
incomingCmdLock = [ [ NXLock alloc ] init ];
gActiveCommand = NULL;
[ self resetStatistics ];
gNextQueueTag = QUEUE_TAG_NONTAGGED + 1;
gInitiatorID = kInitiatorIDDefault;
gInitiatorIDMask = 1 << gInitiatorID; /* BusID bitmask for selection. */
gFlagReselecting = FALSE;
/* Reserve the initiator ID for all LUNs: */
for ( lun = 0; lun < SCSI_NLUNS; lun++ )
[ self reserveTarget : gInitiatorID lun : lun forOwner : self ];
/* Get tagged command queueing, sync mode, */
/* fast mode enables from configTable. */
gOptionCmdQueueEnable = getConfigParam( configTable, CMD_QUEUE_ENABLE );
gOptionSyncModeEnable = getConfigParam( configTable, SYNC_ENABLE );
gOptionFastModeEnable = getConfigParam( configTable, FAST_ENABLE );
gOptionExtendTiming = getConfigParam( configTable, EXTENDED_TIMING );
gOptionAutoSenseEnable = AUTO_SENSE_ENABLE; // from bringup.h
gOptionCmdQueueEnable = 1; /* Temp for testing??? */
/* Get internal version of interruptPort; */
/* set the port queue length to the maximum size. */
/* It is not clear if we want to do this. */
gKernelInterruptPort = IOConvertPort( [ self interruptPort ],
IO_KernelIOTask,
IO_Kernel );
#if 0 /***** Need correct header file *****/
kernelReturn = port_set_backlog( task_self(),
[ self interruptPort ],
PORT_BACKLOG_MAX );
if ( kernelReturn != KERN_SUCCESS )
PAUSE( 0, kernelReturn, 'i H-', "InitializeHardware - warning, port_set_backlog error.\n" );
#endif
/* Initialize the chip and reset the bus: */
ioReturn = [ self ResetHardware : TRUE ];
meshAddr->sourceID = gInitiatorID; // mlj ??? fix this
}
if ( ioReturn == IO_R_SUCCESS )
{
/* OK, we're ready to roll. */
[ self enableInterrupt : 0 ];
[ self registerDevice ];
IOScheduleFunc( serviceTimeoutInterrupt, (void*)0x333, ONE_SECOND );
}
else
{ /* Do we need to free the locks and similar? */
[ self free ];
result = NULL;
}
return result;
}/* end InitializeHardware */
/* This includes a SCSI reset. */
/* Handling of ioComplete of active and disconnected commands */
/* must be done elsewhere. Returns IO_R_SUCCESS if successful. */
/* This is called from a Task thread. It will disable and */
/* re-enable interrupts. Reason is for error logging. */
- (IOReturn) ResetHardware : (Boolean)resetSCSIBus
{
ELG( 0, resetSCSIBus, 'RstH', "ResetHardware - Bus Reset.\n" );
[ self abortAllCommands : SR_IOST_RESET ];
[ self ResetMESH : resetSCSIBus ];
return IO_R_SUCCESS;
}/* end ResetHardware */
/* Start a SCSI transaction for the specified command. */
/* ActiveCmd must be NULL. A return of kHardwareStartRejected */
/* indicates that caller may try again with another command; */
/* kHardwareStartBusy indicates a condition other than */
/* (activeCmd != NULL) which prevents the processing of the command. */
- (HardwareStartResult) hardwareStart : (CommandBuffer*) cmdBuf
{
IOSCSIRequest *scsiReq;
HardwareStartResult result = kHardwareStartOK;
cdb_t *cdbp;
Boolean okToDisconnect = TRUE;
Boolean okToQueue = gOptionCmdQueueEnable;
UInt8 msgByte;
ASSERT( cmdBuf && cmdBuf->scsiReq );
scsiReq = cmdBuf->scsiReq;
gCurrentTarget = scsiReq->target;
gCurrentLUN = scsiReq->lun;
cdbp = &scsiReq->cdb;
gMsgOutFlag = 0;
cmdBuf->cdbLength = GetSCSICommandLength( cdbp, scsiReq->cdbLength );
if ( cmdBuf->cdbLength == 0 )
{
/* Failure: we can't determine the length of this command. */
scsiReq->driverStatus = SR_IOST_CMDREJ;
[ self ioComplete : cmdBuf ];
result = kHardwareStartRejected;
}
{ UInt8 *bp = (UInt8*)cdbp;
ELG( ( bp[0]<<24) | (bp[1]<<16) | (bp[2]<<8) | bp[3],
( bp[4]<<24) | (bp[5]<<16) | (bp[6]<<8) | bp[7],
'=CDB', "hardwareStart - CDB" );
}
if ( result == kHardwareStartOK )
{
/* Peek at the control byte (the last byte in the command). */
msgByte = ((UInt8*)cdbp)[ cmdBuf->cdbLength - 1 ];
if ( (msgByte & CTRL_LINKFLAG) != CTRL_NOLINK )
{
/* Failure: we don't support linked commands. */
scsiReq->driverStatus = SR_IOST_CMDREJ;
[ self ioComplete : cmdBuf ];
result = kHardwareStartRejected;
}
}
if ( result == kHardwareStartOK )
{
/* Autosense always renegotiates synchronous transfer mode. */
/* This is necessary as the target might have been reset */
/* or hit with a power-cycle. Autosense is never issued */
/* with a queue tag. */
cmdBuf->queueTag = QUEUE_TAG_NONTAGGED; /* No tag just yet */
if ( cmdBuf->flagIsAutosense )
{
okToDisconnect = FALSE;
gPerTargetData[ gCurrentTarget ].negotiateSDTR = gPerTargetData[ gCurrentTarget ].syncParms;
}
else
{
/* This is a real command. Setup the user data pointers */
/* and counters and build a SCSI request CCL. */
/* First, peek at the command for some special cases. */
switch ( cdbp->cdb_opcode )
{
case kScsiCmdInquiry:
/* The first command SCSIDisk sends us is an Inquiry. */
/* This never gets retried, so avoid a possible */
/* reject of a command queue tag. Avoid this hack if */
/* there are any other commands outstanding for this */
/* Target/LUN. */
if ( gActiveArray[ scsiReq->target ][ scsiReq->lun ] == 0 )
scsiReq->cmdQueueDisable = TRUE;
okToDisconnect = FALSE; /* no disconnect, no queuing */
break;
case kScsiCmdRequestSense:
/* Always force sync renegotiation on any Request Sense */
/* to catch independent target power cycles. */
/* (Sync renegotiation needed should be set after all */
/* target-detected errors -- fix needed in MessageIn). */
/* Sense is always issued with disconnect disabled to */
/* maintain T/L/Q nexus. */
/* Watch it: request sense from a client is incompatible*/
/* with tagged queuing. */
gPerTargetData[ gCurrentTarget ].negotiateSDTR
= gPerTargetData[ gCurrentTarget ].syncParms;
okToDisconnect = FALSE;
break;
case kScsiCmdTestUnitReady:
case kScsiCmdReadCapacity:
okToDisconnect = FALSE;
break;
}/* end SWITCH on opcode */
}/* end ELSE not auto sense */
}/* end IF kHardwareStartOK */
okToDisconnect &= scsiReq->disconnect;
okToQueue &= okToDisconnect
&& (scsiReq->cmdQueueDisable == FALSE)
&& (gPerTargetData[ scsiReq->target ].inquiry_7 & 0x02);
cmdBuf->flagActive = 0; /* Initialize flags for this command. */
/* Make sure that the HBA is stable before we */
/* try to start a request. */
if ( result == kHardwareStartOK )
{
if ( gActiveCommand )
{
/* This should never happen. It ensures that there are */
/* no race conditions that reselect us between the time */
/* threadExecuteRequest looked at gActiveCommand and */
/* the time we disabled interrupts. */
queue_enter( &pendingCmdQ, cmdBuf, CommandBuffer*, link );
result = kHardwareStartBusy;
}
}
if ( result == kHardwareStartOK )
{
/* Activate this command - if we fail later, we'll de-activate it. */
ASSERT( gActiveCommand == NULL );
[ self activateCommand : cmdBuf ];
ASSERT( scsiReq->target == gCurrentTarget && scsiReq->lun == gCurrentLUN );
[ self ClearCPResults ];
/* Reset the message-out buffer pointer for the */
/* Identify, SDTR, and queue tag messages. */
msgOutPtr = (UInt8*)CCLAddress( kcclMSGOdata );
msgByte = kScsiMsgIdentify | scsiReq->lun;
if ( okToDisconnect )
msgByte |= kScsiMsgEnableDisconnectMask;
*msgOutPtr++ = msgByte;
/* According to the SCSI Spec, the tag command */
/* immediately follows the selection. */
/* Note that autosense is never tagged.. */
/* The command was initialized with QUEUE_TAG_NONTAGGED. */
/***** Driver Kit only supports simple queue tags. *****/
if ( okToQueue )
{
/* Avoid using tag QUEUE_TAG_NONTAGGED (zero). */
cmdBuf->queueTag = gNextQueueTag;
if ( ++gNextQueueTag == QUEUE_TAG_NONTAGGED )
gNextQueueTag++;
*msgOutPtr++ = kScsiMsgSimpleQueueTag;
*msgOutPtr++ = cmdBuf->queueTag;
gMsgOutFlag |= kFlagMsgOut_Queuing;
}
/* Do we need to negotiate SDTR for this target? */
msgByte = gPerTargetData[ scsiReq->target ].negotiateSDTR;
ELG( scsiReq->target << 16 | gPerTargetData[ scsiReq->target ].inquiry_7,
gPerTargetData[ scsiReq->target ].syncParms << 16 | msgByte,
'SYN?', "Sync" );
if ( !(gPerTargetData[ scsiReq->target ].inquiry_7 & 0x10) )
{
msgByte = 0; /* if Inquiry data doesn't permit Synchronous */
}
if ( msgByte )
{
// gPerTargetData[ scsiReq->target ].negotiateSDTR = 0;
*msgOutPtr++ = kScsiMsgExtended; /* Extended Message */
*msgOutPtr++ = 0x03; /* Message Length */
*msgOutPtr++ = kScsiMsgSyncXferReq;
if ( msgByte == kSyncParmsAsync )
{
*msgOutPtr++ = 200 >> 4; /* Period? used? */
*msgOutPtr++ = 0; /* Offset (async) */
}
else
{
*msgOutPtr++ = 100 >> 2; /* 100 nSec period */
*msgOutPtr++ = msgByte >> 4; /* FIFO size */
}
gMsgOutFlag |= kFlagMsgOut_SDTR;
}/* end IF need to negotiate (a)sync */
if ( cmdBuf->flagIsAutosense )
{
[ self InitAutosenseCCL ];
}
else
{
cmdBuf->currentDataIndex = 0;
cmdBuf->savedDataIndex = 0;
if ( cmdBuf->mem )
{
[ cmdBuf->mem setPosition : 0 ];
[ cmdBuf->mem state : &cmdBuf->savedDataState ];
}
scsiReq->driverStatus = SR_IOST_INVALID;
scsiReq->totalTime = 0;
scsiReq->latentTime = 0;
[ self UpdateCP : FALSE ]; /* Update the DBDMA Channel Program */
}
/***** Can a caller override the default timeout? *****/
meshAddr->selectionTimeOut = gSelectionTimeout;
meshAddr->destinationID = scsiReq->target;
meshAddr->syncParms = gPerTargetData[ scsiReq->target ].syncParms;
SynchronizeIO();
[ self RunDBDMA : kcclStart stageLabel : kcclStageInit ];
IOGetTimestamp( &cmdBuf->startTime );
}
return result;
}/* end hardwareStart */
@end /* AppleMesh_SCSI( Hardware ) */
/* Obtain a YES/NO type parameter from the config table. */
/* @param configTable The table to examine. */
/* @param paramName The parameter to look for. */
/* @result Zero if missing from the table or the table */
/* value is not YES. One if present in the table and the */
/* table value is YES. */
static int getConfigParam( id configTable, const char *paramName )
{
const char *value;
int rtn = 0; // default if not present in table
value = [ configTable valueForStringKey : paramName ];
if ( value )
{
if ( strcmp( value, "YES" ) == 0 )
rtn = 1;
[ configTable freeString : value ];
}
return rtn;
}/* end getConfigParam */
static unsigned int GetSCSICommandLength( const cdb_t *cdbPtr, unsigned int defaultLength )
{
unsigned int result;
/* Warning: don't use sizeof here - the compiler rounds */
/* the value up to the next word boundary. */
switch ( ((UInt8*)cdbPtr)[0] & 0xE0 )
{
case (0 << 5): result = 6; break;
case (1 << 5):
case (2 << 5): result = 10; break;
case (5 << 5): result = 12; break;
case (6 << 5): result = (defaultLength != 0) ? defaultLength : 6; break;
case (7 << 5): result = (defaultLength != 0) ? defaultLength : 10; break;
default: result = 0; break;
}
return result;
}/* end GetSCSICommandLength */
/* These are the hardware-specific methods that are not */
/* explicitly tied to Mesh and DBDMA. */
@implementation AppleMesh_SCSI( HardwarePrivate )
/* Fetch the device's bus address and allocate one page of memory */
/* for the channel command. (Strictly speaking, we don't need an */
/* entire page, but we can use the rest of the page for a permanent */
/* status log). */
/* @param deviceDescription Specify the device to initialize. */
/* @return IO_R_SUCCESS if successful, else an error status. */
- (IOReturn) AllocHdwAndChanMem : deviceDescription
{
IOReturn ioReturn = IO_R_SUCCESS;
enum
{ kMESHRegisterBase = 0,
kDBDMARegisterBase = 1,
kNumberRegisters = 2
};
meshAddr = (MeshRegister*)gMESHPhysAddr = 0;
dbdmaAddr = (dbdma_regmap_t*)dbdmaAddrPhys = 0;
/* Set the default selection timeout to the MESH value (10 msec units). */
gSelectionTimeout = 250 / 10; // ??? symbolic
/* Allocate a page of wired-down memory in the kernel. Although */
/* Driver Kit provides a memory allocator, IOMalloc, it does */
/* not guarantee page alignment. Thus, we call the Mach kernel */
/* routine. According to the description of kalloc(), 8192 is */
/* the smallest amount of memory we can allocate. The channel */
/* command area will fit into the start of this area. */
cclLogAddrSize = page_size;
cclLogAddr = (DBDMADescriptor*)kalloc( cclLogAddrSize );
if ( !cclLogAddr )
{ PAUSE( 0, cclLogAddrSize, 'CCA-', "AllocHdwAndChanMem - can't allocate channel command area.\n" );
ioReturn = IO_R_NO_MEMORY;
}
if ( ioReturn == IO_R_SUCCESS )
{
if ( IOIsAligned( cclLogAddr, page_size ) == 0 )
{
PAUSE( 0, cclLogAddr, 'cca-', "AllocHdwAndChanMem - not page-aligned.\n" );
ioReturn = IO_R_NO_MEMORY;
}
}
if ( ioReturn == IO_R_SUCCESS )
{
/* Remember the number of DBDMA descriptors that */
/* can be used for data transfer channel commands. */
gDBDMADescriptorMax = (cclLogAddrSize - kcclDataXfer)
/ sizeof( DBDMADescriptor );
#if 0
/* Fetch the logical and physical addresses */
/* to access the MESH and DBDMA hardware. */
memoryRangeList = [ deviceDescription memoryRangeList ];
numMemoryRanges = [ deviceDescription numMemoryRanges ];
for ( i = 0; i < numMemoryRanges; i++ )
ELG( memoryRangeList[ i ].start, memoryRangeList[ i ].size, 'Rang', "AllocHdwAndChanMem - range start & size.\n" );
if ( numMemoryRanges != kNumberRegisters )
{ PAUSE( memoryRangeList[ i ].start, memoryRangeList[ i ].size, 'Rng-', "AllocHdwAndChanMem - numMemoryRanges != kNumberRegisters.\n" );
ioReturn = IO_R_INVALID; /* This "can't happen" */
}
#endif
}
#if 0
if ( ioReturn == IO_R_SUCCESS )
{
/* We know that the first range describes the MESH chip, */
/* and the second range describes the DBDMA chip. */
gMESHPhysAddr = (PhysicalAddress)memoryRangeList[ kMESHRegisterBase ].start;
dbdmaAddrPhys = (PhysicalAddress)memoryRangeList[ kDBDMARegisterBase ].start;
/* Weave together the logical and physical addresses. */
/* First, map the MESH and DBDMA chips into our address space. */
ioReturn = IOMapPhysicalIntoIOTask( (UInt32)gMESHPhysAddr,
sizeof( MeshRegister ),
(vm_address_t*)&meshAddr );
if ( ioReturn != IO_R_SUCCESS )
PAUSE( 0, ioReturn, 'map-', "AllocHdwAndChanMem - MESH mapping err.\n" );
}
if ( ioReturn == IO_R_SUCCESS )
{
ioReturn = IOMapPhysicalIntoIOTask( (UInt32)dbdmaAddrPhys,
sizeof( dbdma_regmap_t ),
(vm_address_t*)&dbdmaAddr );
if ( ioReturn != IO_R_SUCCESS )
PAUSE( 0, ioReturn, 'Map-', "AllocHdwAndChanMem - DBDMA mapping err.\n" );
}
#else
if ( ioReturn == IO_R_SUCCESS )
{
meshAddr = (MeshRegister*)gMESHPhysAddr = (PhysicalAddress)PCI_MESH_BASE_PHYS;
// dbdmaAddr = (dbdma_regmap_t*)PCI_MESH_DMA_BASE_PHYS;
dbdmaAddr = (dbdma_regmap_t*)DBDMA_REGMAP( DBDMA_MESH_SCSI );
// dbdmaAddrPhys = (PhysicalAddress)KVTOPHYS( (vm_offset_t)dbdmaAddr );
dbdmaAddrPhys = (PhysicalAddress)dbdmaAddr;
ELG( dbdmaAddrPhys, dbdmaAddr, 'DBDM',
"AllocHdwAndChanMem - DBDMA phys/logical addresses." );
g.meshAddr = (UInt32)meshAddr; // for debugging, miniMon ...
#if CustomMiniMon
gMESH_DBDMA = (UInt32)dbdmaAddr;
gMESH_DBDMA_Phys = (UInt32)dbdmaAddrPhys;
#endif /* CustomMiniMon */
}
#endif
if ( ioReturn == IO_R_SUCCESS )
{
/* Ensure that the addresses are valid: */
ASSERT( probe_rb( meshAddr ) == 0 );
ASSERT( probe_rb( dbdmaAddr ) == 0 );
/* Get the physical address corresponding the DBDMA channel area: */
ioReturn = IOPhysicalFromVirtual( IOVmTaskSelf(),
(UInt32)cclLogAddr,
(vm_offset_t*)&cclPhysAddr );
g.cclPhysAddr = (UInt32)cclPhysAddr; // for debugging ease
g.cclLogAddr = (UInt32)cclLogAddr;
if ( ioReturn != IO_R_SUCCESS )
PAUSE( 0, ioReturn, 'MAP-', "AllocHdwAndChanMem - DBDMA mapping err.\n" );
}
if ( ioReturn == IO_R_SUCCESS)
{
ELG( cclPhysAddr, cclLogAddr, '=CCL',
"AllocHdwAndChanMem - CCL phys/logical addresses." );
[ self InitCP ];
}
/* What do we do on failure? Should we try to deallocate */
/* the stuff we created, or will the system do this for us? */
return ioReturn;
}/* end AllocHdwAndChanMem */
/* Perform one-time-only channel command program initialization. */
- (void) InitCP
{
register DBDMADescriptor *dst = cclLogAddr;
register const DBDMADescriptor *src = gDescriptorList;
UInt32 i;
UInt8 *bp;
/* Set the interrupt, branch, and wait DBDMA registers. */
/* Caution: the following MESH interrupt register bits are */
/* EndianSwapped, reverse polarity and in a different position. */
/* The pattern is: 0xvv00mm00, where mm is a mask byte */
/* and vv is a value byte to match. (After EndianSwapping). */
/* 0x80 means NO errors (kMeshIntrError) */
/* 0x40 means NO exceptions (kMeshIntrException) */
/* 0x20 means NO command done (kMeshIntrCmdDone) */
/* Branch Select is used with BRANCH_FALSE */
// DBDMASetInterruptSelect( 0x00000000 ); /* Never let DBDMA interrupt */
// DBDMASetWaitSelect( 0x00200020 ); /* Wait until command done */
// DBDMASetBranchSelect( 0x00C000C0 ); /* Branch if exception or error */
*(volatile UInt32*)&dbdmaAddr->d_intselect = 0x00000000; /* Never let DBDMA interrupt */
*(volatile UInt32*)&dbdmaAddr->d_wait = 0x20002000; /* Wait until command done */
*(volatile UInt32*)&dbdmaAddr->d_branch = 0xC000C000; /* Br if Exc or Err */
SynchronizeIO();
/* Relocate and EndianSwap the global channel command list */
/* into the page that is shared with the DBDMA device. */
for ( i = 0; i < gDescriptorListSize; i += sizeof( DBDMADescriptor ) )
{
dst->operation = SWAP( src->operation ); /* copy command with count */
switch ( src->result & kRelAddress )
{
case kRelAddressMESH:
dst->address = SWAP( src->address + (UInt32)gMESHPhysAddr );
break;
case kRelAddressCP:
dst->address = SWAP( src->address + (UInt32)cclPhysAddr );
break;
case kRelAddressPhys:
dst->address = SWAP( src->address );
break;
default:
dst->address = SWAP( src->address );
break;
}
switch ( src->result & kRelCmdDep )
{
case kRelCmdDepCP:
dst->cmdDep = SWAP( src->cmdDep + (UInt32)cclPhysAddr );
break;
case kRelCmdDepLabel:
dst->cmdDep = src->cmdDep;
break;
default:
dst->cmdDep = SWAP( src->cmdDep );
break;
}
dst->result = 0;
src++;
dst++;
} /* FOR all elements in the descriptor list */
/* Build a SCSI CDB for the autosense Request Sense command. */
bp = (UInt8*)CCLAddress( kcclSenseCDB );
*bp++ = kScsiCmdRequestSense; /* Command */
*bp++ = 0; /* LUN to be filled in */
*bp++ = 0; /* reserved */
*bp++ = 0; /* reserved */
*bp++ = kMaxAutosenseByteCount; /* Allocation length - to be filled in */
*bp++ = 0; /* Control (flag) */
return;
}/* end InitCP */
/* Initialize the data transfer channel command list for a normal SCSI */
/* command. The channel command list has a complex structure of */
/* transfer groups and items, where: */
/* transfer group The number of bytes transferred by a single */
/* MESH operation. This will be from 1 to */
/* kMaxDMATransferLength (65536 - 4096). */
/* transfer item The number of bytes transferred by a single */
/* DBDMA operation. These bytes are guaranteed */
/* to be physically-contiguous. */
/* Thus, the data transfer CCL looks like the following: */
/* Prolog 1: Load MESH with the first group count. */
/* Item 1.1: Load DBDMA with the first physical address and */
/* item count. */
/* Item 1.2 etc: Load DBDMA with the next physical address and */
/* item count. */
/* Prolog 2, etc. Load MESH with the next group count. */
/* Item 2.1, etc. Load DBDMA with the next group of physical */
/* addresses. */
/* Stop/Branch If all of the data transfer commands fit in the */
/* channel command list, branch to the Status phase*/
/* channel command. Otherwise, stop transfer */
/* (which stops in Data phase) and re-build the */
/* command list for the next set of data. */
/* Note that the last DBDMA command must be INPUT_LAST or OUTPUT_LAST */
/* to handle synchronous transfer odd-byte disconnect. */
- (void) UpdateCP : (Boolean) reselecting
{
CommandBuffer *cmdBuf;
IOSCSIRequest *scsiReq;
DBDMADescriptor *descProto = CCLDescriptor( kcclPrototype );
IOReturn ioReturn = IO_R_SUCCESS;
DBDMADescriptor *descriptorPtr; /* current data descriptor */
DBDMADescriptor *descriptorMax; /* beyond the last data descriptor */
DBDMADescriptor *preamblePtr; /* current prolog descriptor */
UInt32 dbdmaOpProto; /* prototype Opcode for DBDMA */
UInt32 dbdmaOp; /* Opcode for DBDMA */
UInt32 meshSeq; /* Opcode for MESH request */
SInt32 transferLength; /* Number of bytes left to transfer */
UInt32 totalXferLen = 0; /* Total length of this transfer */
UInt32 groupLength; /* Number of bytes in this group */
UInt8 syncParms; /* Fast synchronous param value */
ByteCount bc;
PhysicalRange range;
ItemCount rangeByteCount;
DBDMADescriptor *dp;
ASSERT( gActiveCommand && gActiveCommand->scsiReq );
cmdBuf = gActiveCommand;
scsiReq = cmdBuf->scsiReq;
ASSERT( scsiReq->target == gCurrentTarget && scsiReq->lun == gCurrentLUN );
/* How many descriptors can we store (need some slop for the */
/* terminator commands). Get a pointer to the first free */
/* descriptor and the total number of bytes left to transfer in */
/* this IO request. */
descriptorPtr = CCLDescriptor( kcclDataXfer );
descriptorMax = &descriptorPtr[ gDBDMADescriptorMax - 16 ];
transferLength = scsiReq->maxTransfer - cmdBuf->currentDataIndex;
ELG( cmdBuf, transferLength, 'UpCP', "UpdateCP" );
if ( reselecting == FALSE )
{
[ self SetupMsgO ]; /* Setup for Message Out phase. */
/* Setup for Command phase: */
CCLByte( kcclCmdoMTC ) = cmdBuf->cdbLength; /* MESH transfer count */
CCLByte( kcclCmdoDTC ) = cmdBuf->cdbLength; /* DBDMA count */
bcopy( &scsiReq->cdb, CCLAddress( kcclCMDOdata ), cmdBuf->cdbLength );
}
/* Generate MESH "sequence" & DBDMA "operation" for Input or Output: */
if ( scsiReq->read )
{ dbdmaOpProto = INPUT_MORE | kBranchIfFalse;
meshSeq = kMeshDataInCmd | kMeshSeqDMA;
}
else
{ dbdmaOpProto = OUTPUT_MORE | kBranchIfFalse;
meshSeq = kMeshDataOutCmd | kMeshSeqDMA;
}
CCLWord( kcclBatchSize ) = 0;
while ( ioReturn == IO_R_SUCCESS
&& transferLength > 0
&& descriptorPtr < descriptorMax )
{
/* Do one group, ie, enough CCs to fill a MESH transfer count. */
/* There are more data to be transferred, and CCL space to store*/
/* another group of data. First, leave space for the preamble. */
preamblePtr = descriptorPtr;
groupLength = 0;
descriptorPtr += 4; /* Preamble takes 4 descriptors */
while ( transferLength > 0 /* more to xfer */
&& descriptorPtr < descriptorMax ) /* room in CCL */
{
/* Do one physically contiguous segment: */
bc = MAX_DMA_XFER - groupLength; /* calc room left in group */
if ( bc < page_size )
break;
rangeByteCount = [ cmdBuf->mem getPhysicalRanges : (ItemCount) 1
maxByteCount : bc
newPosition : NULL
actualRanges : NULL
physicalRanges : &range ];
if ( rangeByteCount == 0 )
break;
ASSERT( range.length > 0 );
groupLength += range.length;
transferLength -= range.length;
dbdmaOp = dbdmaOpProto | range.length;
if ( transferLength <= 0 )
dbdmaOp |= (OUTPUT_MORE ^ OUTPUT_LAST); /* add LAST to cmd */
descriptorPtr->operation = SWAP( dbdmaOp );
descriptorPtr->address = SWAP( (UInt32)range.address );
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclProblem );
descriptorPtr->result = 0; // for debugging
descriptorPtr++;
}/* end inner WHILE */
if ( groupLength == 0 )
{
/* Nothing was built - we apparently failed to get */
/* a physical address. Note: there is a potential problem with */
/* the following sequence as the *previous* DBDMA command, if */
/* any, should be changed to set xxPUT_LAST. */
ELG( 0, 0, 'Grp-', "UpdateCP - groupLength is 0" );
preamblePtr->operation = SWAP(
NOP_CMD | kBranchIfFalse | kWaitIfTrue );
preamblePtr->address = 0;
preamblePtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclProblem );
preamblePtr->result = 0;
descriptorPtr = preamblePtr + 1;
ioReturn = IO_R_INVALID; /* Exit the outer loop */
}
else
{
totalXferLen += groupLength;
/* This group is complete. Fill in the preamble. */
/* The preamble consists of the following commands: */
/* [0] Move <totalXferLen> to kcclBatchSize */
/* [1] Store group length high-byte in MESH */
/* transfer count 1 register */
/* [2] Store group length low-byte in MESH */
/* transfer count 1 register */
/* [3] Store the input/output command in the MESH */
/* sequence register. */
/* If the command finishes prematurely (perhaps the */
/* device wants to disconnect), the interrupt service */
/* routine will use totalXferLen - the residual byte */
/* count to determine the number of bytes xferred. */
descProto[0].cmdDep = totalXferLen; // update batch size
descProto[1].cmdDep = SWAP( groupLength >> 8 );
descProto[2].cmdDep = SWAP( groupLength & 0xFF );
descProto[3].cmdDep = SWAP( meshSeq );
bcopy( descProto, preamblePtr, sizeof( DBDMADescriptor ) * 4 );
ELG( preamblePtr, totalXferLen, '=Tot', "UpdateCP - set preamble" );
/* If there is another group, wait for */
/* cmdDone and clear it: */
if ( transferLength > 0 )
{ /* Wait for CmdDone: */
bcopy( CCLDescriptor( kcclBrProblem ), descriptorPtr, sizeof( DBDMADescriptor ) );
++descriptorPtr;
/* Clear CmdDone: */
/* HACK - if we reached the end of the CCL page, */
/* we don't want to clear cmdDone because we will lose */
/* an interrupt. So, this instruction may be deleted */
/* down below. (Radar 2298440) */
descriptorPtr->operation = SWAP( STORE_QUAD | KEY_SYSTEM | 1 );
descriptorPtr->address = SWAP( (UInt32)gMESHPhysAddr + kMeshInterrupt );
descriptorPtr->cmdDep = SWAP( kMeshIntrCmdDone );
descriptorPtr->result = 0;
++descriptorPtr;
}/* end IF not last group */
}/* end if/ELSE a group was built */
}/* end outer WHILE */
/* All of the data have been transferred (or we ran off the end */
/* of the CCL). Update the transfer start index to reflect on */
/* what we *think* we will transfer in this DATA operation. If */
/* we completed DATA phase, branch to the Status Phase CCL; */
/* if not, stop the channel command so we can reload the CCL */
/* with the next big chunk. */
/* When the transfer completes, the last prolog will have stored*/
/* the total number of bytes transferred in a known location in */
/* the CCL area. */
/* Now, append the data transfer postamble to handle */
/* synchronous odd-byte disconnect and jump to status phase */
/* (or just stop if there's more DMA) */
#define kMaxPostamble 9
#define kDBDMADescriptorEnd (CCLDescriptor(kcclDataXfer) + gDBDMADescriptorMax)
ASSERT( descriptorPtr + kMaxPostamble < kDBDMADescriptorEnd );
/* Do some synchronous data transfer cleanup: */
syncParms = gPerTargetData[ scsiReq->target ].syncParms;
meshAddr->syncParms = syncParms;
SynchronizeIO();
ELG( gMsgOutFlag, syncParms, 'SynP', "UpdateCP - sync parms" );
if ( ((syncParms & 0xF0) || (gMsgOutFlag & kFlagMsgOut_SDTR)) // Sync?
&& (totalXferLen > 0) // any data moving?
&& (transferLength == 0) ) // end of xfer?
{
gFlagIncompleteDBDMA = FALSE; /* indicate complete xfer */
/* MESH has a problem at the end of Synchronous transfers. */
/* If the target is fast enough, it can move from data phase to */
/* Status phase while MESH still has ACKed bytes in its FIFO and */
/* the DBDMA is still running. MESH raises PhaseMismatch Exception */
/* causing an interrupt in which we must empty the FIFO and move */
/* the bytes to the user's buffer by programmed IO. */
/* If the target is not fast enough, we can save the interrupt and */
/* bypass the mess. */
/* So, we do the following: */
/* 1) Enable only MESH Err interrupts; disable Exc and CmdDone. */
/* 2) Don't Wait; Branch if an interrupt may have already occurred.*/
/* 3) Wait for cmdDone at least for TC = FIFO count = 0 and */
/* maybe including PhaseMismatch. Branch to SyncCleanup if PMM. */
/* 4) Assume an interphase condition as opposed to an */
/* overrun condition and Branch Always to get Status. */
/* If the Channel Program gets this far, the OUTPUT_LAST */
/* has finished writing its data to the FIFO and MESH may still */
/* be putting bytes on the bus OR the INPUT_LAST has read all */
/* its data from the FIFO and MESH has already ACKed them. */
/* There may be or not some time before REQ appears again, */
/* either for data overrun or the next phase. */
/* Disable Exc and CmdDone (leave Err enabled): */
descriptorPtr->operation = SWAP( STORE_QUAD | KEY_SYSTEM | 1 );
descriptorPtr->address = SWAP( (UInt32)gMESHPhysAddr + kMeshInterruptMask );
descriptorPtr->cmdDep = SWAP( kMeshIntrError );
descriptorPtr->result = 0;
++descriptorPtr;
/* Take the interrupt if PhaseMismatch not definitely caught. */
/* Branch (don't wait for cmdDone) if Exc may have already occurred: */
descriptorPtr->operation = SWAP( NOP_CMD | kBranchIfFalse );
descriptorPtr->address = 0;
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclProblem );
descriptorPtr->result = 0;
/* Radar 2281306 ( and 2272931 ): */
/* Output may completely fit in the FIFO and not make it out */
/* to the SCSI bus if the target disconnects after the command. */
/* If that's possible, wait here for cmdDone and */
/* take the PhaseMismatch interrupt. This situation occurred on a */
/* Mode Select with an output of 12 bytes. Do this to prevent */
/* the Stage from advancing from kcclStageXfer so that proper */
/* cleanup can take place. */
if ( (totalXferLen < 16) && !scsiReq->read )
descriptorPtr->operation= SWAP( NOP_CMD | kWaitIfTrue | kBranchIfFalse );
++descriptorPtr;
/* Possible PhaseMisMatch caught after FIFO emptied. */
/* Wait for cmdDone. If Exc, branch to SyncCleanUp: */
descriptorPtr->operation = SWAP( NOP_CMD | kWaitIfTrue | kBranchIfFalse );
descriptorPtr->address = 0;
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclSyncCleanUp );
descriptorPtr->result = 0;
descriptorPtr++;
/* Interphase condition or possible overrun. */
/* 29sep98 PhaseMismatch occurred even after */
/* CmdDone was set. */
/* Branch Always to assume we will bit bucket some data: */
descriptorPtr->operation = SWAP( NOP_CMD | kBranchAlways );
descriptorPtr->address = 0;
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclOverrun );
descriptorPtr->result = 0;
descriptorPtr++;
/* Fix up the DataOverrun code just in case: */
dp = CCLDescriptor( kcclOverrunMESH );
if ( scsiReq->read )
{ dp->cmdDep = SWAP( kMeshDataInCmd | kMeshSeqDMA );
dp = CCLDescriptor( kcclOverrunDBDMA );
dp->operation = SWAP( INPUT_LAST | kBranchIfFalse | 8 );
}
else
{ dp->cmdDep = SWAP( kMeshDataOutCmd | kMeshSeqDMA );
dp = CCLDescriptor( kcclOverrunDBDMA );
dp->operation = SWAP( OUTPUT_LAST | kBranchIfFalse | 8 );
}
}/* end IF last of Synchronous transfer */
else
{
/* Async or incomplete Sync. Append Branches to finish this process: */
/* If this is a partial transfer, set 'incomplete' flag. */
if ( transferLength > 0 )
gFlagIncompleteDBDMA = TRUE; /* set incomplete */
else gFlagIncompleteDBDMA = FALSE; /* assume complete xfer */
if ( gFlagIncompleteDBDMA )
{ /* Delete the ccl to clear cmdDone: */
--descriptorPtr; /* see HACK note above. */
}
else if ( totalXferLen > 0 )
{ /* If something moved AND (Radar 2298440) xfer completed, */
/* Wait & Branch if problem: */
/* Radar 2272931 - If entire output fits in FIFO, then */
/* the OUTPUT_LAST completes OK without a PhaseMismatch if */
/* the target disconnects right after the command phase. */
bcopy( CCLDescriptor( kcclBrProblem ), descriptorPtr, sizeof( DBDMADescriptor ) );
descriptorPtr++;
}
/* Assume all's well - Branch to get status: */
descriptorPtr->operation = SWAP( NOP_CMD | kBranchAlways );
descriptorPtr->address = 0;
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclGetStatus );
descriptorPtr->result = 0;
/* If this is a partial transfer, set 'incomplete' flag and */
/* change the Branch from GetStatus to Good: */
if ( gFlagIncompleteDBDMA )
{ /* change last Branch from Status to Good: */
descriptorPtr->cmdDep = SWAP( (UInt32)cclPhysAddr + kcclMESHintr );
ELG( descriptorPtr, transferLength, 'Part', "UpdateCP - built partial CCL." );
}
descriptorPtr++;
}/* end if/ELSE Async or partial xfer */
ASSERT( descriptorPtr < kDBDMADescriptorEnd );
return;
}/* end UpdateCP */
/* StartBucket - Start the channel commands to run the bit bucket. */
- (void) StartBucket
{
CommandBuffer *cmdBuf;
IOSCSIRequest *scsiReq;
DBDMADescriptor *dp; /* current data descriptor */
UInt32 dbdmaOp; /* Opcode for DBDMA */
UInt32 meshSeq; /* Opcode for MESH request */
cmdBuf = gActiveCommand;
scsiReq = cmdBuf->scsiReq;
ELG( cmdBuf, scsiReq, 'Bkt-', "StartBucket" );
/* Generate MESH "sequence" & DBDMA "operation" for Input or Output: */
if ( scsiReq->read )
{ dbdmaOp = INPUT_MORE | kBranchIfFalse | 8;
meshSeq = kMeshDataInCmd | kMeshSeqDMA;
}
else
{ dbdmaOp = OUTPUT_MORE | kBranchIfFalse | 8;
meshSeq = kMeshDataOutCmd | kMeshSeqDMA;
}
dp = CCLDescriptor( kcclOverrunMESH ); dp->cmdDep = meshSeq;
dp = CCLDescriptor( kcclOverrunDBDMA ); dp->operation = dbdmaOp;
[ self RunDBDMA : kcclDataXfer stageLabel : kcclStageBucket ];
return;
}/* end StartBucket */
/* Set up the channel commands for MsgO phase. */
- (void) SetupMsgO
{
UInt8 msgoSize;
msgOutPtr--; /* treat the last or only byte special (drop ATN) */
msgoSize = msgOutPtr - CCLAddress( kcclMSGOdata );
if( msgoSize == 0 )
{ /* Identify byte only: */
CCLWord( kcclMsgoBranch ) = SWAP( NOP_CMD | kBranchAlways );
}
else /* multibyte message - set counts for all but last byte: */
{ CCLByte( kcclMsgoMTC ) = msgoSize;
CCLByte( kcclMsgoDTC ) = msgoSize;
/* NOP the BRANCH: */
CCLWord( kcclMsgoBranch ) = SWAP( NOP_CMD );
}
CCLByte( kcclMSGOLast )= *msgOutPtr; /* position last byte */
return;
}/* end SetupMsgO */
/* Initialize the autosense area and build the autosense channel command. */
- (void) InitAutosenseCCL
{
ELG( 0, 0, 'Auto', "InitAutosenseCCL" );
/* Make sure we've allocated enough space in the CCL area. */
ASSERT( kcclSenseBuffer + kMaxAutosenseByteCount < kcclSenseResult );
bzero( CCLAddress( kcclSenseBuffer ), kMaxAutosenseByteCount );
/* Copy the Sense CDB to the CDB area and */
/* copy the Sense CCL to the Xfer area */
/* Copy the Sense CDB & Sense CCL */
bcopy( CCLAddress( kcclSenseCDB ), CCLAddress( kcclCMDOdata ), 6 );
bcopy( CCLAddress( kcclSense ), CCLAddress( kcclDataXfer ), 5 * sizeof( DBDMADescriptor ) );
/* Set the MESH and DBDMA transfer counts for the command. */
CCLByte( kcclCmdoMTC ) = 6;
CCLByte( kcclCmdoDTC ) = 6;
/* Set the data transfer count (use a hard-wired value). */
CCLWord( kcclBatchSize ) = kMaxAutosenseByteCount;
[ self SetupMsgO ];
return;
}/* end InitAutosenseCCL */
- (void) ClearCPResults
{
register DBDMADescriptor *dp = CCLDescriptor( kcclStart );
register int i;
/* Don't clear the reserved areas or prototypes */
for ( i = (gDescriptorListSize - kcclStart) / sizeof ( DBDMADescriptor ); i; --i )
{
dp->result = 0;
dp++;
}
return;
}/* end ClearCPResults */
@end /* AppleMesh_SCSI(HardwarePrivate) */
@implementation AppleMesh_SCSI( MeshInterrupt )
/* DoHardwareInterrupt - Handle an Interrupt Service message */
- (void) DoHardwareInterrupt /* called from interruptOccurred */
{
[ self GetHBARegsAndClear : TRUE ]; /* get the MESH registers */
[ self SetIntMask : 0 ]; /* Disable MESH interrupts */
gFlagReselecting = FALSE;
if ( g.shadow.mesh.interrupt == 0 )
{ /* Interrupts can occur with no bits set in the */
/* interrupt register one way: */
/* - Eating interrupts in the driver (the ASIC */
/* latches the interrupt even though the */
/* driver or Channel Program clears the MESH */
/* interrupt register). */
PAUSE( dbdmaAddr->d_cmdptrlo,
(g.shadow.mesh.busStatus0 << 8) | g.shadow.mesh.busStatus1,
'ISR?',
"DoHardwareInterrupt - spurious interrupt" );
if ( !gActiveCommand )
{
[ self selectNextRequest ];
if ( !gActiveCommand && queue_empty( &abortCmdQ ) )
/* if neither new request nor aborting: */
[ self SetIntMask : kMeshIntrMask ];/* Enable interrupts */
}
return;
}/* end IF no bit set in interrupt register */
dbdma_flush( DBDMA_MESH_SCSI ); /* DBDMA may be hung in */
dbdma_stop( DBDMA_MESH_SCSI ); /* middle of transfer. */
// invalidate_cache_v( (vm_offset_t)cclLogAddr, cclLogAddrSize );
/* If the DBDMA was running a channel command, handle this */
/* (this could be done at a lower priority level). */
if ( CCLWord( kcclStageLabel ) )
{
[ self ProcessInterrupt ];
return;
}
/* This was not a DBDMA completion. */
/* See if the last MESH operation completed */
/* without errors or exceptions. */
if ( g.shadow.mesh.interrupt == kMeshIntrCmdDone )
{
/* This was presumably a Programmed IO completion. */
if ( gActiveCommand )
{ /* The command has not completed yet. */
/* We need to wait for a phase stabilizing interrupt. */
PAUSE( 0, 0, 'dhi-', "DoHardwareInterrupt - MESH interrupt problem: need phase stabilizing wait.\n" );
return;
}
else
{ /* There is no active command. */
/* This is presumably a bus-free completion. */
[ self selectNextRequest ]; /* Try to start another request.*/
if ( !gActiveCommand && queue_empty( &abortCmdQ ) )
/* If still nothing to do: */
[ self SetIntMask : kMeshIntrMask ];/* Re-enable ints */
return;
}
}/* end IF CmdDone without Err or Exc */
/* None of the above "completion" states occurred. */
/* Either a command completed unsuccessfully, or we */
/* were reselected. First, check for phase mismatch. */
if ( g.shadow.mesh.interrupt == (kMeshIntrCmdDone | kMeshIntrException)
&& g.shadow.mesh.exception == kMeshExcPhaseMM )
{
PAUSE( 0, 0, 'DHI-', "DoHardwareInterrupt - MESH interrupt problem: phase mismatch interrupt.\n" );
}
else
{ /* Handle reselection and all other problems separately. */
/* (This can be done at a lower priority.) */
[ self ProcessInterrupt ];
}
return;
}/* end DoHardwareInterrupt */
/* Respond to a DBDMA channel command completion interrupt */
/* or some error or exception condition. */
- (void) ProcessInterrupt
{
register CommandBuffer *cmdBuf;
register IOSCSIRequest *scsiReq;
UInt32 stage; /* Stage in the Channel Program */
UInt32 cclIndex; /* Index of CCL descriptor */
UInt32 count; /* transfer count */
UInt8 phase; /* Current bus phase */
IOReturn rc;
if ( gActiveCommand == NULL )
{
if ( g.shadow.mesh.exception & kMeshExcResel )
{
[ self HandleReselectionInterrupt ];
}
else
{ /* There is no active request and we are not reselecting. */
/* Can get here if Reject/Abort occurs or after a BusFree */
/* command is put in the Sequence register and we exit the */
/* interrupt. */
if ( !queue_empty( &abortCmdQ ) )
{
[ self SetSeqReg : kMeshFlushFIFO ]; /* flush the FIFO */
queue_remove_first( &abortCmdQ, cmdBuf, CommandBuffer*, link );
[ self ioComplete : cmdBuf ];
}/* end IF Aborting disconnected commands */
else
{ /* This should be a Bus Free interrupt: */
ELG( 0, 0, 'Int0', "Process interrupt with no active request\n" );
}
[ self selectNextRequest ];
if ( !gActiveCommand && queue_empty( &abortCmdQ ) )
/* Re-enable ints for reselect */
[ self SetIntMask : (kMeshIntrException | kMeshIntrError) ];
}
return;
}/* end IF had no active command */
/* There is an active request: */
/* get the stage of the CCL and Switch on it. */
stage = CCLWord( kcclStageLabel );
cmdBuf = gActiveCommand;
scsiReq = cmdBuf->scsiReq;
cclIndex = SWAP( dbdmaAddr->d_cmdptrlo )
- (UInt32) cclPhysAddr;
CCLWord( kcclStageLabel ) = 0;
ASSERT( scsiReq->target == gCurrentTarget && scsiReq->lun == gCurrentLUN );
/* Analyse where the DBDMA ended up: */
switch ( stage )
{
case kcclStageGood: /* Normal completion */
[ self DoInterruptStageGood ];
break;
case kcclStageInit: /* Value before DBDMA runs */
case kcclStageArb: /* Arbitration anomaly */
[ self DoInterruptStageArb ];
break;
case kcclStageSelA: /* Selection anomaly */
[ self DoInterruptStageSelA ];
break;
case kcclStageMsgO: /* Message Out */
[ self DoInterruptStageMsgO ];
break;
case kcclStageCmdO: /* Command stage anomaly */
[ self DoInterruptStageCmdO ];
break;
case kcclStageXfer:
if ( cmdBuf->flagIsAutosense )
[ self DoInterruptStageXferAutosense ];
else [ self DoInterruptStageXfer ]; /* DMA transfer complete */
break;
case kcclStageStat: /* Synchronous, odd transfer, data-out */
/* OR no data, disconnect */
/* Don't use UpdateCurrentIndex here because */
/* kcclStageStat destroys TC with a 1. */
count = CCLWord( kcclBatchSize ); /* Our transfer count */
cmdBuf->currentDataIndex += count; /* Increment data index */
if ( cmdBuf->mem )
[ cmdBuf->mem setPosition : cmdBuf->currentDataIndex ];
ELG( count, cmdBuf->currentDataIndex, 'Uidx', "ProcessInterrupt" );
CCLWord( kcclBatchSize ) = 0; /* Clear our count */
/* Analyze the current bus signals: */
if ( !(g.shadow.mesh.busStatus0 & kMeshReq) )
{ /* Get here if Sync Read or Write is too short as */
/* in reading 512 bytes from a 2K block of CD-ROM. */
[ self StartBucket ];
return;
}/* end IF no REQ signal */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
switch ( phase )
{
case kBusPhaseMSGI:
rc = [ self DoMessageInPhase ];
// if ( rc == IO_R_SUCCESS && gActiveCommand )
// break; /* msg processed ok & not disconnect */
/* Enable Exc (for Reselect) and Err interrupts (not CmdDone)*/
break;
case kBusPhaseDATO:
case kBusPhaseDATI:
/* Get here if Async Read or Write is too short as */
/* in reading 512 bytes from a 2K block of CD-ROM */
[ self StartBucket ];
break;
default:
PAUSE( scsiReq->target, phase, 'pmm-',
"ProcessInterrupt - expected Status phase.\n" );
break;
}/* end SWITCH on phase */
break;
case kcclStageBucket:
count = CCLWord( kcclBatchSize ); /* Our transfer count */
cmdBuf->currentDataIndex += count; /* Increment data index */
if ( cmdBuf->mem )
[ cmdBuf->mem setPosition : cmdBuf->currentDataIndex ];
CCLWord( kcclBatchSize ) = 0; /* Clear our count */
ELG( count, cmdBuf->currentDataIndex, 'Buck', "ProcessInterrupt - bit bucket done.\n" );
// scsiReq->driverStatus = SR_IOST_DMAOR; /* set DMA OverRun error */
[ self SetSeqReg : kMeshFlushFIFO ]; /* flush the FIFO */
[ self RunDBDMA : kcclGetStatus stageLabel : kcclStageStat ];
break;
case kcclStageMsgI: /* Message-in: */
case kcclStageFree: /* Bus free: */
default: /* Can't happen? */
PAUSE( cclIndex, stage, 'P i-', "ProcessInterrupt - strange or unknown interrupt for device.\n" );
break;
}/* end SWITCH on Channel Program stage */
return;
}/* end ProcessInterrupt */
/* The channel command (and, hence, the IO request) ran to */
/* completion without problems. Complete this IO request */
/* and try to start another. */
- (void) DoInterruptStageGood
{
register CommandBuffer *cmdBuf;
register IOSCSIRequest *scsiReq;
UInt32 totalXferLen;
IOMemoryDescriptorState state;
UInt8 byte;
ASSERT( gActiveCmd && gActiveCmd->scsiReq );
// [ self SetSeqReg : kMeshEnableReselect ]; // done by CCL
cmdBuf = gActiveCommand;
scsiReq = cmdBuf->scsiReq;
/* Retrieve the total number of bytes transferred */
/* in the last data phase. */
cmdBuf->flagRequestSelectOK = FALSE;
totalXferLen = CCLWord( kcclBatchSize );
CCLWord( kcclBatchSize ) = 0;
ELG( scsiReq, totalXferLen, 'Good', "DoInterruptStageGood" );
if ( cmdBuf->flagIsAutosense )
{
/* We are completing an autosense command. */
/* Copy the status byte (which had better be */
/* "good" ) from the CCL to the autosense status*/
/* and complete the IO request. The autosense */
/* data itself was copied into the user buffer */
/* by a previous 'Xfer' interrupt. */
cmdBuf->autosenseStatus = CCLByte( kcclStatusData );
[ self deactivateCmd : cmdBuf ];
[ self ioComplete : cmdBuf ];
}
else
{ /* We are completing a normal command. */
/* Update the transfer count and current data pointer. */
cmdBuf->currentDataIndex += totalXferLen;
if ( cmdBuf->mem )
[ cmdBuf->mem setPosition : cmdBuf->currentDataIndex ];
if ( gFlagIncompleteDBDMA == FALSE )
{
/* Yes, the IO is really complete: */
scsiReq->scsiStatus = CCLByte( kcclStatusData );
scsiReq->driverStatus = SR_IOST_GOOD;
/* If this was an Inquiry, peek at the data */
/* for Synchronous and Queuing support: */
if ( (*(UInt8*)&scsiReq->cdb == kScsiCmdInquiry)
&& (cmdBuf->currentDataIndex > 7) )
{
[ cmdBuf->mem state : &state ]; /* save context */
[ cmdBuf->mem setPosition : 7 ];
if ( [ cmdBuf->mem readFromClient : &byte count : 1 ] == 1 )
{
gPerTargetData[ scsiReq->target ].inquiry_7 = byte;
ELG( scsiReq->target, byte, 'Inq+',
"DoInterruptStageGood - peek at Inquiry data" );
}
[ cmdBuf->mem setState : &state ]; /* restore context */
}
[ self deactivateCmd : cmdBuf ];
[ self ioComplete : cmdBuf ];
}
else
{ /* The CCL ended, but the caller expected more data. */
/* Restart the CCL. */
/* Don't regenerate arbitration or command stuff. */
[ self UpdateCP : TRUE ];
[ self RunDBDMA : kcclDataXfer stageLabel : kcclStageXfer ];
return;
}/* end ELSE need to continue Channel Program */
}/* end ELSE not AutoSense */
/* Since IO completed (otherwise, we would have exited in the */
/* "return" above), check whether a reselection attempt */
/* is piggy-backed on top of the good DBDMA completion. */
if ( g.shadow.mesh.exception & kMeshExcResel )
[ self HandleReselectionInterrupt ];
else /* Nothing happening. Try to start another request. */
{
[ self selectNextRequest ];
if ( !gActiveCommand && queue_empty( &abortCmdQ ) )
/* Re-enable ints for reselect */
[ self SetIntMask : (kMeshIntrException | kMeshIntrError) ];
}
return;
}/* end DoInterruptStageGood */
/* Process the autosense data transfer phase. IO is not complete. */
/* There are several reasons why we might get here: */
/* -- autosense completion (which could be a separate stage) */
/* -- DMA completion with more DMA to do */
/* -- Bus phase mismatch (short transfer or disconnect) */
- (void) DoInterruptStageXferAutosense
{
register CommandBuffer *cmdBuf = gActiveCommand;
IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
UInt32 residual;
UInt32 count;
cmdBuf->flagRequestSelectOK = FALSE;
/* An autosense Data In transfer is complete. Copy the */
/* autosense data from our private buffer to the */
/* caller's sense_data area and restart IO to get the */
/* status and command-complete message byte. */
residual = SWAP( CCLWord( kcclSenseResult ) ) & 0xFF;
count = kMaxAutosenseByteCount - residual;
ASSERT( count <= sizeof( esense_reply_t ) );
bcopy( CCLAddress( kcclSenseBuffer ), &scsiReq->senseData, count );
/* Driver Kit does not return "sense valid" or */
/* the actual sense transfer count. */
/* Restart the channel command to fetch the */
/* status byte and Command Completion byte. */
[ self RunDBDMA : kcclGetStatus stageLabel : kcclStageStat ];
return;
}/* end DoInterruptStageXferAutosense */
/* Process a normal data phase interrupt. IO is not complete. */
/* There are several reasons why we might get here: */
/* -- autosense completion (which could be a separate stage) */
/* -- DMA completion with more DMA to do */
/* -- Bus phase mismatch (short transfer or disconnect, MsgIn) */
/* Note that we know that we are not in autosense. */
- (void) DoInterruptStageXfer
{
register CommandBuffer *cmdBuf = gActiveCommand;
UInt32 count; /* DMA transfer count */
UInt8 phase; /* Current bus phase */
IOReturn rc;
int goAround;
cmdBuf->flagRequestSelectOK = FALSE;
count = cmdBuf->currentDataIndex;
[ self UpdateCurrentIndex ];
do
{ goAround = FALSE; /* assume loop not repeated */
[ self SetSeqReg : kMeshFlushFIFO ];
/* We've cleaned up the mess from the previous data transfer. */
/* Look at the current bus phase. The channel command waited */
/* for REQ to be set before interrupting the processor. */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
ASSERT( g.shadow.mesh.busStatus0 & kMeshReq ); /* REQ is set, right? */
switch ( phase )
{
case kBusPhaseSTS:
gFlagIncompleteDBDMA = FALSE; /* indicate no-more-data */
[ self RunDBDMA : kcclGetStatus stageLabel : kcclStageStat ];
break;
case kBusPhaseMSGI:
rc = [ self DoMessageInPhase ];
if ( rc == IO_R_SUCCESS && gActiveCommand )
goAround = TRUE; /* msg ok & not disconnect */
break;
case kBusPhaseDATO:
case kBusPhaseDATI:
if ( count != cmdBuf->currentDataIndex )
{ /* Data phase had already started: */
PAUSE( 0, phase, 'dat-', "DoInterruptStageXfer - unexpected Data phase.\n" );
}
else
{ /* try starting data phase again */
[ self RunDBDMA : kcclDataXfer stageLabel : kcclStageXfer ];
}
break;
default:
PAUSE( cmdBuf->scsiReq->target, phase, 'Phs-', "DoInterruptStageXfer - bogus phase.\n" );
break;
}/* end SWITCH on phase */
} while ( goAround );
return;
}/* end DoInterruptStageXfer */
/* DoInterruptStageArb - Process an anomaly during arbitration. */
- (void) DoInterruptStageArb
{
ASSERT( gActiveCommand );
ELG( 0, 0, 'Arb-', "DoInterruptStageArb - Lost arbitration.\n" );
[ self pushbackCurrentRequest : gActiveCommand ];
ASSERT( gActiveCommand == NULL );
if ( g.shadow.mesh.exception & kMeshExcResel )
{ if ( g.shadow.mesh.error & kMeshErrDisconnected )
{
/* 18sep98 - Sometimes MESH gets real confused when its */
/* arbitration loses to a target's reselect arbitration. */
/* The registers show Exc:ArbLost, Resel and Err:UnExpDisc. */
/* The FIFO count is 1 (should be SCSI ID bits) while the */
/* BusStatus0,1 registers show IO and Sel both of which are */
/* set by the reselecting Target. */
/* The SCSI bus anaylzer shows the following events occcurring */
/* within a few microseconds of BSY being set by the target: */
/* bus free for at least hundreds of microseconds */
/* Target raises BSY along with its ID bit */
/* Target raises SEL */
/* Target raises IO to indicate reselection */
/* Target adds MESH's ID bit */
/* Target drops BSY */
/* MESH raises BSY to accept reselection */
/* **** MESH drops BSY **** here is where MESH is confused */
/* Target stays on bus for 250 milliseconds. */
/* To solve this problem, whack MESH with a RstMESH. */
ELG( ' Rst', 'MESH', 'UEP-', "DoInterruptStageArb - Resel/Unexpected Disconnect.\n" );
[ self SetSeqReg : kMeshResetMESH ]; /* completes quickly */
[ self GetHBARegsAndClear : TRUE ]; /* clear cmdDone */
[ self SetSeqReg : kMeshEnableReselect ];
[ self SetIntMask : kMeshIntrMask ]; /* Enable Interrupts */
return; /* now wait for another reselect interrupt */
}
[ self HandleReselectionInterrupt ];
}
else
{ /* 22sep97 - lost arbitration without reselection. */
/* Probably lost the reselect condition processing an */
/* error or something. */
ELG( 0, 0, 'ARB-', "DoInterruptStageArb - Lost arbitration without reselect.\n" );
}
return;
}/* end DoInterruptStageArb */
/* Process an anomaly during target selection. */
- (void) DoInterruptStageSelA
{
ASSERT( gActiveCommand );
[ self SetSeqReg : kMeshEnableReselect ];
[ self SetSeqReg : kMeshBusFreeCmd ]; /* clear ATN signal MESH left on */
[ self killCurrentRequest ];
[ self GetHBARegsAndClear : FALSE ]; /* check MESH registers */
[ self SetIntMask : kMeshIntrMask ]; /* Enable Interrupts */
return;
}/* end DoInterruptStageSelA */
/* Process an anomaly during Message-Out phase. */
/* Target probably doing Message Reject (0x07). */
- (void) DoInterruptStageMsgO
{
UInt8 phase;
IOReturn rc;
ASSERT( gActiveCommand );
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask; /* phase me */
PAUSE( gActiveCommand, phase, 'Mgo-',
"DoInterruptStageMsgO - error during msg-out phase.\n" );
switch ( phase )
{
case kBusPhaseMSGI:
rc = [ self DoMessageInPhase ];
if ( rc != IO_R_SUCCESS )
{
PAUSE( 0, rc, ' MI-',
"DoInterruptStageMsgO - MsgIn during MsgOut phase.\n" );
// ??? need to get to bus-free from here
// ??? need to blow off the IO
}
else
{ ELG( 0, gMsgInFlag, 'rej?', "DoInterruptStageMsgO - got MsgIn.\n" );
if ( gMsgInFlag & kFlagMsgIn_Reject )
[ self AbortActiveCommand ];
}
break;
default:
PAUSE( gMsgInFlag, phase, 'mgo-',
"DoInterruptStageMsgO - unknown phase during MsgOut phase.\n" );
break;
}
return;
}/* end DoInterruptStageMsgO */
/* DoInterruptStageCmdO - Process an anomaly during command stage. */
- (void) DoInterruptStageCmdO
{
register CommandBuffer *cmdBuf;
UInt8 phase;
IOReturn rc;
/* See if this is part of the normal AbortTag/BusDeviceReset process: */
if ( !queue_empty( &abortCmdQ ) )
{
[ self SetSeqReg : kMeshFlushFIFO ]; /* flush the FIFO */
cmdBuf = (CommandBuffer*)queue_first( &abortCmdQ );
ELG( cmdBuf, 0, 'Abo-', "DoInterruptStageCmdO - Aborting." );
queue_remove( &abortCmdQ, cmdBuf, CommandBuffer*, link );
[ self ioComplete : cmdBuf ];
[ self AbortDisconnectedCommand ]; /* do the next, if any */
return;
}
/* Not aborting - something bad happened: */
ASSERT( gActiveCommand );
cmdBuf = gActiveCommand;
cmdBuf->flagRequestSelectOK = FALSE;
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask; /* phase me */
ELG( cmdBuf, phase, 'CMD?', "DoInterruptStageCmdO - anomaly during Cmd phase.\n" );
if ( phase == kBusPhaseMSGI )
{ /* We are probably negotiating SDTR or */
/* getting rejected on a nonzero LUN. */
rc = [ self DoMessageInPhase ];
if ( rc != IO_R_SUCCESS )
{
PAUSE( 0, rc, ' mi-',
"DoInterruptStageCmdO - MsgIn during Cmd phase.\n" );
}
else
{ /* Message processed - where do we go from here? */
if ( !gActiveCommand ) /* if Rejected, */
return; /* return */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
switch ( phase )
{
case kBusPhaseSTS:
[ self RunDBDMA : kcclCmdoStage stageLabel : kcclStageInit ];
break;
case kBusPhaseMSGO:
msgOutPtr = (UInt8*)CCLAddress( kcclMSGOdata );
[ self SetupMsgO ];
[ self RunDBDMA : kcclMsgoStage stageLabel : kcclStageInit ];
break;
case kBusPhaseCMD:
[ self RunDBDMA : kcclCmdoStage stageLabel : kcclStageInit ];
break;
}
}
}
else if ( phase == kBusPhaseSTS ) /* Probably Check Condition */
{ /* Perhaps block # invalid */
gFlagIncompleteDBDMA = FALSE; /* indicate no-more-data */
[ self RunDBDMA : kcclGetStatus stageLabel : kcclStageStat ];
}
else
{
PAUSE( 0, phase, 'Phs?', "DoInterruptStageCmdO - error during Command phase.\n" );
}
return;
}/* end DoInterruptStageCmdO */
/* We are in MSGI phase. Read the bytes. Return TRUE if an entire */
/* message was read (we may still be in MSGI phase). Note that this */
/* is done by programmed IO, which will fail (logging the error) if */
/* the target sets MSGI but does not send us a message quickly enough. */
/* This method is called from the normal data transfer interrupt when */
/* the target enters message in phase, and from the reselection */
/* interrupt handler when we read a valid reselection target ID. */
/* Note that MESH interrupts are disabled on exit. */
- (IOReturn) DoMessageInPhase
{
register UInt8 messageByte;
UInt32 index = 0;
IOReturn ioReturn = IO_R_SUCCESS;
/* We do not necessarily have a valid command in this method. */
/* While we're processing Message-In bytes, we don't want any */
/* MESH hardware interrupts. */
[ self SetIntMask : 0 ]; /* no MESH interrupt latching */
[ self SetSeqReg : kMeshFlushFIFO ]; /* Flush the FIFO */
gMsgInCount = 0;
gMsgInState = kMsgInInit;
while ( gMsgInState != kMsgInReady /* Disconnect makes gActiveCommand */
&& ioReturn == IO_R_SUCCESS ) /* go away */
{
meshAddr->transferCount1 = 0;
meshAddr->transferCount0 = 1; /* get single byte */
[ self SetSeqReg : kMeshMessageInCmd ]; /* issue MsgIn */
ioReturn = [ self WaitForMesh : TRUE ]; /* wait for cmdDone */
if ( ioReturn != IO_R_SUCCESS )
{
PAUSE( gCurrentTarget, ioReturn, 'Mgi-', "DoMessageInPhase - Target hung: message in timeout.\n" );
break; /* Bus reset here? */
}
if ( (g.shadow.mesh.exception & kMeshExcPhaseMM)
|| (g.shadow.mesh.busStatus0 & kMeshPhaseMask) != kBusPhaseMSGI )
{
break; /* exit loop if no longer in Msg-In phase */
}
if ( g.shadow.mesh.FIFOCount == 0 )
{
PAUSE( gCurrentTarget, 0, 'mgi-', "DoMessageInPhase - no message byte.\n" );
break;
}
messageByte = meshAddr->xFIFO;
ASSERT( index < 256 );
gMsgInBuffer[ index++ ] = messageByte;
switch ( gMsgInState )
{
case kMsgInInit:
/* This is the first message byte. Check for 1-byte codes. */
if ( messageByte == kScsiMsgCmdComplete
|| (messageByte >= kScsiMsgOneByteMin && messageByte <= kScsiMsgOneByteMax)
|| messageByte >= kScsiMsgIdentify )
{
gMsgInState = kMsgInReady;
}
else if ( messageByte >= kScsiMsgTwoByteMin
&& messageByte <= kScsiMsgTwoByteMax )
{
/* This is a two-byte message. */
/* Set the count and read the next byte. */
gMsgInState = kMsgInReading; /* Need one more */
gMsgInCount = 1;
}
else
{ /* This is an extended message. */
/* The next byte has the count. */
gMsgInState = kMsgInCounting;
}
break;
case kMsgInCounting: /* Count byte of multi-byte message: */
gMsgInCount = messageByte;
gMsgInState = kMsgInReading;
break;
case kMsgInReading: /* Body of multi-byte message: */
if ( --gMsgInCount <= 0 )
gMsgInState = kMsgInReady;
break;
default:
ASSERT( gMsgInState ); /* Bogus state */
PAUSE( 0, 0, 'Msg-', "DoMessageInPhase - Bogus MSGI state!\n" );
gMsgInState = kMsgInReady;
break;
}/* end SWITCH on MSGI state */
if ( gMsgInState == kMsgInReady )
{
[ self ProcessMSGI ];
gMsgInState = kMsgInInit;
index = 0;
if ( gMsgInBuffer[0] == kScsiMsgDisconnect )
ioReturn = IO_R_IO; /* break out of WHILE loop */
if ( gMsgInFlag & kFlagMsgIn_Reject )
{
[ self AbortActiveCommand ];
break;
}
if ( gFlagReselecting )
break; /* Take Identify only - leave +ACK */
}/* end IF have a complete message-in to process */
}/* end WHILE there are more message bytes */
/***** If the target switches out of MSGI phase without *****/
/***** sending a complete message, we should do some *****/
/***** sort of error recovery. *****/
if ( gMsgInState != kMsgInInit )
{
PAUSE( gCurrentTarget, gMsgInState, 'MGI-', "DoMessageInPhase - incomplete message.\n" );
if ( ioReturn == IO_R_SUCCESS )
ioReturn = IO_R_IO; /* General IO error */
}
return ioReturn;
}/* end DoMessageInPhase */
/* ProcessMSGI - DoMessageInPhase has read a complete message. */
/* Process it (this will probably change our internal state). */
- (void) ProcessMSGI
{
/* Note that, during reselection, we may not have */
/* a current target or LUN, nor possibly a valid command */
register CommandBuffer *cmdBuf;
register IOSCSIRequest *scsiReq;
UInt8 sdtr;
UInt8 currentTarget, currentLUN;
UInt8 period, offset;
UInt8 targetResponse; /* responding or requesting? */
cmdBuf = gActiveCommand; /* May be NULL */
scsiReq = (cmdBuf == NULL) ? NULL : cmdBuf->scsiReq;
if ( scsiReq )
{ currentTarget = scsiReq->target;
currentLUN = scsiReq->lun;
ASSERT( currentTarget == gCurrentTarget && currentLUN == gCurrentLUN );
}
else
{ currentTarget = gCurrentTarget;
currentLUN = gCurrentLUN;
}
ELG( 0, *(UInt32*)gMsgInBuffer, '<Msg', "ProcessMSGI" );
switch ( gMsgInBuffer[0] )
{
case kScsiMsgCmdComplete:
if ( cmdBuf )
{
/* This command is complete. Clear interrupts and */
/* allow subsequent MESH interrupts. Then tell the */
/* MESH to wait for the target to release the bus. */
[ self SetSeqReg : kMeshEnableReselect ];
[ self SetSeqReg : kMeshBusFreeCmd ]; /* cause Int */
if ( cmdBuf->flagIsAutosense == FALSE )
{
if ( scsiReq )
scsiReq->scsiStatus = CCLByte( kcclStatusData );
/* Driver Kit does not return the command-complete byte. */
}
[ self ioComplete : cmdBuf ];
}
goto exit; /* Don't exit through the SWITCH end */
case kScsiMsgLinkedCmdComplete:
case kScsiMsgLinkedCmdCompleteFlag:
PAUSE( gCurrentTarget, 0, 'pmi-', "ProcessMSGI - linked command complete not supported.\n" );
[ self AbortActiveCommand ];
break;
case kScsiMsgNop:
break;
case kScsiMsgRestorePointers:
if ( cmdBuf )
{
cmdBuf->currentDataIndex = cmdBuf->savedDataIndex;
if ( cmdBuf->mem )
[ cmdBuf->mem setState : &cmdBuf->savedDataState ];
}
break;
case kScsiMsgSaveDataPointers:
if ( cmdBuf )
{
cmdBuf->savedDataIndex = cmdBuf->currentDataIndex;
if ( cmdBuf->mem )
[ cmdBuf->mem state : &cmdBuf->savedDataState ];
}
break;
case kScsiMsgDisconnect:
/* Driver Kit does not support automatic Save Data Pointers on */
/* Disconnect. */
/* Move this request to the disconnect queue, enable reselection, */
/* re-enable MESH interrupts, and wait (here) for bus free, but */
/* don't eat the interrupt. */
gMsgInFlag |= kFlagMsgIn_Disconnect;
[ self disconnect ]; /* requeue active */
[ self SetSeqReg : kMeshEnableReselect ]; /* enable reselect */
[ self SetIntMask : kMeshIntrMask ]; /* Enable Ints */
[ self SetSeqReg : kMeshBusFreeCmd ]; /* issue BusFree */
/* wait for Bus Free command to complete: */
[ self WaitForMesh : FALSE ]; /* don't clear possible reselect */
/* Interrupt for bus-free now latched. Prevent a double interrupt, */
/* 1 from bus-free + 1 from reselect from occurring. */
/* This fixes the following BADNESS: */
/* Issue bus-free for disconnect. */
/* Interrupt occurs in microseconds - even before exiting */
/* "interruptOccurred" routine. */
/* Mach queues message to driverKit. */
/* Exit "interruptOccurred" routine. */
/* DriverKit dequeues and starts handling 1st Mach message. */
/* Interrupt occurs for reselect while driverKit running. */
/* Mach queues 2nd message to driverKit. */
/* DriverKit invokes MESH driver for 1st msg. */
/* MESH driver sees cmdDone fm bus-free AND reselect exception.*/
/* MESH driver handles reselect by setting up and running */
/* DBDMA. MESH driver exits. */
/* DriverKit invokes MESH driver with 2nd Mach message. */
/* MESH driver handles this as a DBDMA completion and royally */
/* screws up. */
g.intLevel |= kLevelLatched; /* set latched-interrupt flag */
[ self SetIntMask : 0 ]; /* prevent multiple MESH ints */
break;
case kScsiMsgRejectMsg:
ELG( currentTarget, gMsgOutFlag, 'Rej-', "ProcessMSGI - Reject." );
gMsgInFlag |= kFlagMsgIn_Reject;
break;
case kScsiMsgSimpleQueueTag:
msgInTagType = gMsgInBuffer[0];
msgInTag = gMsgInBuffer[1];
ELG( 0, msgInTag, '=Tag', "Simple Queue Tag" );
break;
case kScsiMsgExtended:
/* Multi-byte message, presumably Synchronous Negotiation: */
switch ( gMsgInBuffer[ 2 ] ) /* switch on the msg code byte */
{
case kScsiMsgSyncXferReq: /* handle sync negotiation: */
if ( scsiReq == NULL ) // ??? can this happen?
{
PAUSE( currentTarget, 0, 'pMI-', "ProcessMSGI - attempted to negotiate SDTR without a nexus.\n" );
[ self AbortActiveCommand ];
}
else
{ /* Get period in nanoseconds */
period = gMsgInBuffer[ 3 ] * 4; /* SCSI uses 4ns granularity */
/* determine target responding or initiating? */
targetResponse = gPerTargetData[ scsiReq->target ].negotiateSDTR;
gPerTargetData[ scsiReq->target ].negotiateSDTR = 0;
if ( targetResponse )
{
if ( gMsgInBuffer[ 4 ] == 0 ) /* check offset */
{
sdtr = kSyncParmsAsync; /* Offset == 0 implies async */
}
else /* synchronous: */
{
if ( period == 100 ) /* special-case 100=FAST */
{
sdtr = kSyncParmsFast & 0x0F;
}
else /* Older CD-ROMs get here. */
{ /* The MESH manual says: */
/* period = 4 * clk + 2 * clk * P */
/* where: */
/* period is the target nanoseconds */
/* clk is the MESH clock rate which is */
/* 20 nanoseconds for a 50 MHz clock */
/* P is the 1-nibble period code we stuff */
/* in the syncParms register */
/* So: */
/* period = 4 * 20 + 2 * 20 * P */
/* period = 80 + 40 * P */
/* P = (period - 80) / 40 */
/* Since P must round up for safety: */
/* P = ((period - 80) + 39) / 40 */
/* P = (period - 41) / 40 */
/* A value of P == 3 results in 5 MB/s */
sdtr = (UInt8)((period - 41) / 40);
}
#ifdef CRAP
/* If period is longer than 200 ns resulting */
/* in less than 5 MB/s, renegotiate async later.*/
if ( period >= 200 )
gPerTargetData[ scsiReq->target ].negotiateSDTR = kSyncParmsAsync;
#endif /* CRAP */
}/* end ELSE have offset ergo Synchronous */
/* OR in the offset. */
sdtr |= (gMsgInBuffer[ 4 ] << 4);
}/* end IF Target is responding to negotiation */
else /* target is initiating negotiation: */
{
msgOutPtr = (UInt8*)CCLAddress( kcclMSGOdata );
*msgOutPtr++ = kScsiMsgExtended; /* 0x01 Ext Msg */
*msgOutPtr++ = 0x03; /* 0x03 Message Len */
*msgOutPtr++ = kScsiMsgSyncXferReq; /* 0x01 SDTR code */
offset = gMsgInBuffer[ 4 ];
if ( offset == 0 ) /* Offset == 0 means async: */
{
*msgOutPtr++ = 0; /* clear period byte */
*msgOutPtr++ = 0; /* offset byte = 0 for async*/
sdtr = kSyncParmsAsync; /* set value for MESH reg */
}
else /* have offset ergo sync: */
{
if ( offset > 15 )
offset = 15; /* MESH can only handle 15 */
if ( period <= 100 ) /* special-case 100=FAST */
period = 100;
else
{ /* round up to MESH's 40 ns granularity */
period = ((period + 39) / 40) * 40;
}
*msgOutPtr++ = period / 4; /* SCSI 4ns granularity */
*msgOutPtr++ = offset;
sdtr = (offset << 8) | (UInt8)((period - 41) / 40);
}/* end target is negotiating Sync */
/* respond to target: */
[ self RunDBDMA : kcclMsgoStage stageLabel : kcclStageInit ];
gPerTargetData[ scsiReq->target ].negotiateSDTR = 0;
}/* end ELSE target is initiating negotiation */
meshAddr->syncParms = sdtr;
SynchronizeIO();
gPerTargetData[ scsiReq->target ].syncParms = sdtr;
ELG( *(UInt32*)&gMsgInBuffer[0], gMsgInBuffer[4]<<24 | sdtr, 'SDTR', "ProcessMSGI - SDTR" );
} /* end ELSE have a nexus */
break;
default:
PAUSE( currentTarget, gMsgInBuffer[0], 'PMi-', "ProcessMSGI - unsupported extended message.\n" );
[ self AbortActiveCommand ];
break;
}/* end SWITCH on extended message code */
break;
default:
if ( gMsgInBuffer[0] >= kScsiMsgIdentify )
{
ASSERT( gCurrentTarget != kInvalidTarget );
ASSERT( gCurrentLUN == kInvalidLUN );
gCurrentLUN = gMsgInBuffer[0] & kScsiMsgIdentifyLUNMask;
currentLUN = gCurrentLUN;
}
else
{
PAUSE( currentTarget, gMsgInBuffer[0], 'mi -', "ProcessMSGI - unsupported message: rejected.\n" );
[ self AbortActiveCommand ];
}
}/* end SWITCH on message selection */
exit:
return;
}/* end ProcessMSGI */
/* Process a reselection interrupt. */
- (void) HandleReselectionInterrupt
{
IOReturn ioReturn;
ASSERT( gActiveCommand == NULL );
gFlagReselecting = TRUE;
/* Sometimes MESH gives a bogus Disconnected error during Reselection. */
/* 31mar98 - Issuing an Abort message, causes "unexpected disconnect". */
/* When Err:UnexpDisc and Exc:Resel are simultaneously set, the */
/* busStatus0,1 registers may not be current. */
if ( g.shadow.mesh.error & kMeshErrDisconnected )
{
[ self SetSeqReg : kMeshBusFreeCmd ];
[ self WaitForMesh : TRUE ]; // now maybe busStatus0,1 are live
PAUSE( 0, 0, 'Dsc-',
"HandleReselectionInterrupt: Caught disconnected glitch\n" );
}/* End IF bus disconnect error */
/* Read the target ID (which should be our initiator ID OR'd with the */
/* Target and the Identify byte with the reselecting LUN. Store this */
/* in gTargetID and gTargetLUN. Note that, during reselection, we will */
/* have a NULL gCurrentCommand and a valid gCurrentTarget and gCurrentLUN. */
/* If we get a valid reselection target, call the message in phase */
/* directly to read the LUN byte. */
/* @return TRUE if successful. */
msgInTag = 0;
if ( meshAddr->FIFOCount == 0 )
{
PAUSE( 0, 0, 'HRI-', "HandleReselectionInterrupt - Empty FIFO in reselection.\n" );
return;
}
else /* get the Target ID bit from the bus out of the FIFO */
{ /* then, get the msg-in Identify byte for the LUN. */
if ( [ self getReselectionTargetID ] )
{
if ( [ self DoMessageInPhase ] != IO_R_SUCCESS ) /* get Identify */
{
PAUSE( 0, 0, 'Id -', "HandleReselectionInterrupt - Expected Identify byte after reselection.\n" );
}
}
else return;
}
/* Try to find an untagged command for this Target/LUN: */
ioReturn = [ self reselectNexus : gCurrentTarget
lun : gCurrentLUN
queueTag : 0 ];
if ( ioReturn != IO_R_SUCCESS )
{ /* No untagged command, try to get a Tag. Hope that */
/* you're still in Message-In phase at this point. */
if ( [ self DoMessageInPhase ] != IO_R_SUCCESS ) /* get Tag msg */
{
PAUSE( 0, 0, 'tag-', "HandleReselectionInterrupt - Expected tag message.\n" );
}
ioReturn = [ self reselectNexus : gCurrentTarget
lun : gCurrentLUN
queueTag : msgInTag ];
}
if ( ioReturn == IO_R_SUCCESS )
{
ELG( gActiveCommand,
(msgInTag<<16) | (gCurrentLUN<<8) | gCurrentTarget,
'Resl', "HandleReselectionInterrupt" );
/* If reselectNexus succeeded, gActiveCommand is set to the command.*/
/* Clear out the channel command results and build the channel */
/* command to continue operation. The TRUE flag prevents */
/* constructing an arbitrate/select/command sequence. */
[ self ClearCPResults ];
[ self UpdateCP : TRUE ];
[ self RunDBDMA : kcclReselect stageLabel : kcclStageInit ];
}
else
{ /* There is no associated command. */
/* Reject the reselection attempt. */
/* This should cycle back to selectNextRequest. */
PAUSE( gCurrentTarget, msgInTag, 'Rsl-',
"HandleReselectionInterrupt - No command for reselection attempt.\n" );
[ self AbortActiveCommand ];
}
return;
}/* end HandleReselectionInterrupt */
/* Validate the target's reselection byte (put on the bus before */
/* reselecting us). Erase the initiator ID and convert the other */
/* bit into an index. The algorithm should be faster than a */
/* sequential search, but it probably doesn't matter much. */
/* @return TRUE if successful (gCurrentTarget is now valid). */
/* This function does not check whether there actually */
/* is a command pending for this target. */
- (Boolean) getReselectionTargetID
{
Boolean success = FALSE;
register UInt8 targetID = 0;
register UInt8 bitValue = 0; /* Suppress warning */
register UInt8 targetBits;
targetBits = meshAddr->xFIFO; /***** Read the FIFO *****/
targetBits &= ~gInitiatorIDMask; /* Remove our bit */
if ( targetBits )
{ /* Is there another bit? */
bitValue = targetBits;
if ( bitValue > 0x0F )
{
targetID += 4;
bitValue >>= 4;
}
if ( bitValue > 0x03 )
{
targetID += 2;
bitValue >>= 2;
}
if ( bitValue > 0x01 )
{
targetID += 1;
}
targetBits &= ~(1 << targetID); /* Remove the target mask */
if ( targetBits == 0 )
{ /* Was exactly one set? */
success = TRUE; /* Yes: success! */
gCurrentTarget = targetID; /* Save the current target */
}
}
if ( !success )
PAUSE( targetID, targetBits, 'rsl-', "getReselectionTargetID - Expected Identify byte after reselection.\n" );
return success;
}/* end getReselectionTargetID */
@end /* AppleMesh_SCSI(MeshInterrupt) */
@implementation AppleMesh_SCSI( Mesh )
/* Reusable hardware initializer function. if resetSCSIBus is TRUE, */
/* this includes a SCSI reset. Handling of ioComplete of active and */
/* disconnected commands must be done elsewhere. Returns IO_R_SUCCESS. */
- (IOReturn) ResetMESH : (Boolean)resetSCSIBus
{
IOReturn ioReturn = IO_R_SUCCESS;
UInt8 defaultSelectionTimeout = 25; // mlj ??? fix this value
UInt8 target;
/* Reset interrupts, the MESH Hardware Bus Adapter, and the DMA engine. */
// [ self SetIntMask : 0 ]; /* ResetMESH clrs interruptMask */
[ self SetSeqReg : kMeshResetMESH ]; /* completes quickly */
[ self GetHBARegsAndClear : TRUE ]; /* clear cmdDone */
dbdma_reset( DBDMA_MESH_SCSI );
/* Init state variables: */
gFlagIncompleteDBDMA = FALSE;
// gBusState = SCS_DISCONNECTED;
/* Smash all active command state (just in case): */
gActiveCommand = NULL;
gCurrentTarget = kInvalidTarget;
gCurrentLUN = kInvalidLUN;
gMsgInState = kMsgInInit;
msgOutPtr = (UInt8*)CCLAddress( kcclMSGOdata );
if ( resetSCSIBus )
{
ASSERT( gInterruptNestingLevel > 0 );
meshAddr->busStatus1 = kMeshRst; /***** ASSERT RESET SIGNAL *****/
SynchronizeIO();
IODelay( 25 ); /* leave asserted for 25 mikes */
meshAddr->busStatus1 = 0; /***** CLEAR RESET SIGNAL *****/
SynchronizeIO();
/* Delay for 250 msec after resetting the bus. */
/* This serves two purposes: it gives the MESH time to */
/* stabilize (about 10 msec is sufficient) and gives */
/* some devices time to re-initialize themselves. */
IOSleep( APPLE_SCSI_RESET_DELAY ); /* Give Targets time to clean up */
[ self SetSeqReg : kMeshResetMESH ]; /* clear Err condition */
[ self GetHBARegsAndClear : TRUE ]; /* check regs */
for ( target = 0; target < SCSI_NTARGETS; target++ )
{
gPerTargetData[ target ].syncParms = kSyncParmsAsync;
gPerTargetData[ target ].negotiateSDTR = kSyncParmsFast; // negotiate Fast
}
}/* end IF resetSCSIBus */
meshAddr->selectionTimeOut = defaultSelectionTimeout;
SynchronizeIO();
return ioReturn;
}/* end ResetMESH */
/* Wait for an immediate (non-interrupting) command to complete. */
/* Note that it spins while waiting. It is timed to prevent a buggy */
/* chip or target from hanging the system. */
- (IOReturn) WaitForMesh : (Boolean) clearInterrupts
{
ns_time_t startTime, endTime;
IOReturn ioReturn = IO_R_SUCCESS;
#if USE_ELG
UInt8 *logp = g.evLogBufp;
#endif /* USE_ELG */
//#define WAIT_TIME (1000000000ULL)
//#define WAIT_TIME (3000000ULL) // mlj - make it 3 milliseconds
//#define WAIT_TIME 19000000 // mlj - make it 19 milliseconds for ZIP
#define WAIT_TIME 250000000 // mlj - make it 250 milliseconds for SONY CD-ROM
IOGetTimestamp( &startTime );
for ( g.shadow.mesh.interrupt = 0; g.shadow.mesh.interrupt == 0; )
{
#if USE_ELG
g.evLogBufp = logp; /* set back the log pointer */
#endif /* USE_ELG */
[ self GetHBARegsAndClear : clearInterrupts ];
IOGetTimestamp( &endTime );
if ( (endTime - startTime) >= WAIT_TIME )
{ /* It took too long! We're dead. */
PAUSE( 0, 0, 'WFM-', "WaitForMesh - MESH chip does not respond to command.\n" );
ioReturn = IO_R_INTERNAL;
break;
}
}/* end FOR */
return ioReturn;
}/* end WaitForMesh */
/* WaitForReq - spins while waiting. It is timed to prevent a buggy */
/* chip or target from hanging the system. */
- (IOReturn) WaitForReq /* This method is currently unused. */
{
ns_time_t startTime, endTime;
IOReturn ioReturn = IO_R_SUCCESS;
IOGetTimestamp( &startTime );
g.shadow.mesh.busStatus0 = 0;
while ( (g.shadow.mesh.busStatus0 & kMeshReq) == 0 )
{
[ self GetHBARegsAndClear : FALSE ];
IOGetTimestamp( &endTime );
if ( (endTime - startTime) >= 1000000000L )
{ /* It took too long! */
PAUSE( endTime, startTime, 'WFR-', "WaitForReq - Target not in valid phase.\n" );
ioReturn = IO_R_INTERNAL;
break;
}
if ( (endTime - startTime) >= 1000000L
&& (g.shadow.mesh.busStatus0 & kMeshReq) == 0 )
{
IODelay( 1000 ); /* After 1 ms, start yielding time */
}
}/* end WHILE REQ not set */
return ioReturn;
}/* end WaitForReq */
/* Send a command to the MESH chip. This may cause an interrupt. */
- (void) SetSeqReg : (MeshCommand) meshCommand
{
ELG( (meshAddr->interruptMask<<16) | meshAddr->interrupt, meshCommand, '=Seq', "SetSeqReg" );
if ( meshAddr->interruptMask & kMeshIntrCmdDone
&& meshCommand <= kMeshBusFreeCmd )
ELG( meshAddr->interrupt, meshAddr->interruptMask, 'Trig',
"SetSeqReg - may trigger interrupt.\n" );
meshAddr->sequence = (UInt8)meshCommand; /***** DO IT *****/
SynchronizeIO();
IODelay( 1 ); /* G3 is too fast */
return;
}/* end SetSeqReg */
/* MESH chip self-test. (Minimal: it could be extended.) */
/* @return IO_R_SUCCESS if successful. */
- (IOReturn) DoHBASelfTest
{
IOReturn ioReturn = IO_R_SUCCESS;
UInt8 tempByte;
ELG( gMESHPhysAddr, meshAddr, 'MESH', "DoHBASelfTest" );
#ifdef CRAP
if ( probe_rb( ((void*)((UInt32)gMESHPhysAddr) + kMeshMESHID) ) == 0 )
{
PAUSE( 0, gMESHPhysAddr, 'HBA-', "DoHBASelfTest - Invalid MESH physical address.\n" );
ioReturn = IO_R_NO_DEVICE;
}
#else
ASSERT( probe_rb( ((void*)((UInt32)gMESHPhysAddr) + kMeshMESHID) ) == 0 );
#endif /* CRAP */
if ( ioReturn == IO_R_SUCCESS )
{
tempByte = meshAddr->MESHID & 0x1f;
if ( tempByte < kMeshMESHID_Value )
{
PAUSE( 0, tempByte, 'hba-', "DoHBASelfTest - Invalid MESH chip ID .\n" );
ioReturn = IO_R_NO_DEVICE;
}
}
return ioReturn;
}/* end DoHBASelfTest */
/* Start a Channel Program at the given offset */
/* with the specified stage label. */
- (void) RunDBDMA : (UInt32) offset stageLabel : (UInt32) stageLabel
{
register UInt8 intReg;
ns_time_t arbEndTime, curTime;
gMsgInFlag = 0; /* clear message-in flags. */
CCLWord( kcclStageLabel ) = stageLabel; /* set the stage */
/* Let MESH interrupt only for errors or exceptions, but not cmdDone */
[ self SetIntMask : (kMeshIntrException | kMeshIntrError) ];
intReg = meshAddr->interrupt;
switch ( intReg )
{
case kMeshIntrCmdDone:
if ( !gFlagReselecting ) // ??? Don't drop ACK fm MSG-IN or Sync data flows
/* clear any pending command interrupts (but not reselect et al) */
meshAddr->interrupt = kMeshIntrCmdDone; SynchronizeIO();
/***** fall through *****/
case 0:
/* This is a Go: */
/* Flush any CCL and related data to the CCL physical page */
/* that may still be sitting in cache: */
flush_cache_v( (vm_offset_t)cclLogAddr, cclLogAddrSize );
//ELG( *(UInt32*)0xF3000020, 0, 'G C+', "RunDBDMA." );
//ELG( 0, *(UInt32*)0xF300002C, 'G C ', "RunDBDMA." );
if ( offset == kcclStart )
{
gFlagReselecting = FALSE;
[ self SetSeqReg : kMeshArbitrateCmd ]; /* ARBITRATE */
/* wait 50 mikes or cmdDone, whichever comes first: */
IOGetTimestamp( &arbEndTime );
arbEndTime += 50000;
do
{
[ self GetHBARegsAndClear : FALSE ]; /* get regs without hosing */
IOGetTimestamp( &curTime );
}while ( !(g.shadow.mesh.interrupt & kMeshIntrCmdDone) && curTime < arbEndTime );
if ( g.shadow.mesh.interrupt == kMeshIntrCmdDone )
{ /* No err, no exc: Arbitration won: */
meshAddr->interrupt = kMeshIntrCmdDone;
SynchronizeIO();
[ self SetSeqReg : kMeshDisableReselect ]; /* disable reselect */
offset = 0x150; // ??? fix this. Point to Select/Atn
CCLWord( kcclStageLabel ) = kcclStageArb; /* set stage to Arbitrate */
}/* end IF won Arbitration */
else /* Arbitration not won - CAUTION - HACK AHEAD: */
{ /* Sometimes, MESH does not return ArbLost as it says in */
/* the documentation. Instead, it waits for the winner to */
/* get off the bus (usually after the 250 ms timeout) and */
/* then MESH continues its arbitration. This wastes 250 ms */
/* of valuable bus time. Further, IOmega's Zip drive has a */
/* nasty bug whereby if its reselection is snubbed and it */
/* times out, it leaves the I/O signal asserted on the bus */
/* even as other activity on the bus unrelated to the Zip */
/* is ongoing. */
/* We don't need to hack if ArbLost is indicated correctly */
/* or Reselect is indicated. If either is true, don't bother*/
/* starting the DBDMA; rather, let the interrupt already */
/* latched handle the situation. */
if ( !(g.shadow.mesh.exception & (kMeshExcArbLost | kMeshExcResel)) )
{
ELG( '****', '****', 'HACK', "RunDBDMA - Arbitrate HACK." );
[ self SetSeqReg : kMeshResetMESH ]; /* hack it: whack it*/
[ self GetHBARegsAndClear : TRUE ]; /* get regs/preserve*/
[ self SetSeqReg : kMeshEnableReselect ]; /* Let reselect again*/
[ self GetHBARegsAndClear : FALSE ]; /* get regs/preserve*/
if ( g.shadow.mesh.interrupt == 0 )
PAUSE( 0, 0, 'Arb*', "RunDBDMA - Arbitrate/Reselect problem." );
}
if ( g.shadow.mesh.interrupt ) /* If Err or Exc set: */
{ g.intLevel |= kLevelLatched; /* set latched-interrupt flag. */
return; /* let pending Int clean up. */
}
}/* end ELSE lost Arbitration */
}/* end IF DBDMA to start at Arbitrate */
[ self GetHBARegsAndClear : FALSE ]; // ??? debug: see if ACK still set
ELG( 0, offset<<16 | stageLabel, 'DMA+', "RunDBDMA" );
dbdma_start( DBDMA_MESH_SCSI, (dbdma_command_t*)((UInt32)cclPhysAddr + offset) );
break;
default: /* Err or Exc or both are set */
ELG( 'Err ', 'Exc ', 'Pnd-', "RunDBDMA - interrupt probably pending (reselect?)." );
[ self GetHBARegsAndClear : FALSE ]; // display without hosing
}/* end SWITCH on interrupt register */
return;
}/* end RunDBDMA */
/* Retrieve the MESH volatile register contents, */
/* storing them in the global register shadow. */
/* @param clearInts YES to clear MESH interrupts. */
- (void) GetHBARegsAndClear : (Boolean) clearInts
{
register MeshRegister *mesh = meshAddr;
g.shadow.mesh.interrupt = mesh->interrupt;
g.shadow.mesh.error = mesh->error;
g.shadow.mesh.exception = mesh->exception;
g.shadow.mesh.FIFOCount = mesh->FIFOCount;
g.shadow.mesh.busStatus0 = mesh->busStatus0;
g.shadow.mesh.busStatus1 = mesh->busStatus1;
g.shadow.mesh.transferCount1 = mesh->transferCount1;
g.shadow.mesh.transferCount0 = mesh->transferCount0;
g.shadow.mesh.sequence = mesh->sequence; // debugging
g.shadow.mesh.interruptMask = mesh->interruptMask; // debugging
g.shadow.mesh.syncParms = mesh->syncParms; // debugging
g.shadow.mesh.destinationID = mesh->destinationID; // debugging
ELG( g.shadow.longWord[ 0 ], g.shadow.longWord[ 1 ], clearInts ? 'Regs' : 'regs', "GetHBARegsAndClear." );
if ( g.shadow.mesh.error ) // this occurs when dbdma -> Seq while reselect
ELG( g.shadow.mesh.interruptMask, g.shadow.mesh.sequence, 'Err-',
"GetHBARegsAndClear - MESH error detected" );
/* It is possible to have the Reselected bit set in the Exception */
/* register without an Exception bit in the interrupt register. */
/* This may be caused by timing window where we clear the interrupt */
/* register with the interrupt register instead of 0x07. */
/* Handle this by faking an exception. */
/* 04may98 - it is also possible to have PhaseMisMatch set in the */
/* Exception register without Exception indicated in the Interrupt */
/* register. This happened when a Synchronous output finished and */
/* the target went to Message-In phase with Save-Data-Pointer. */
if ( g.shadow.mesh.exception )
g.shadow.mesh.interrupt |= kMeshIntrException;
if ( clearInts && g.shadow.mesh.interrupt )
{
mesh->interrupt = g.shadow.mesh.interrupt;
SynchronizeIO();
}
return;
}/* end GetHBARegsAndClear */
- (void) SetIntMask : (UInt8) mask
{
ELG( (meshAddr->interrupt<<16) | meshAddr->interruptMask, mask, 'Mask', "SetIntMask" );
meshAddr->interruptMask = mask; /* enable whatever */
SynchronizeIO();
return;
}/* end SetIntMask */
- (void) AbortActiveCommand
{
IOReturn ioReturn;
ELG( gActiveCommand, 0, '-AB*', "AbortActiveCommand" );
[ self GetHBARegsAndClear : TRUE ]; /* clear possible cmdDone et al */
[ self SetIntMask : 0 ]; /* Disable MESH interrupts */
gMsgInFlag = 0; /* clear kFlagMsgIn_Reject et al */
meshAddr->busStatus0 = kMeshAtn; /***** Raise ATN signal *****/
SynchronizeIO();
[ self SetSeqReg : kMeshBusFreeCmd ]; /* clear ACK */
[ self WaitForMesh : TRUE ]; /* wait for PhaseMM */
if ( (g.shadow.mesh.busStatus0 & (kMeshPhaseMask | kMeshReq))
== (kBusPhaseMSGO | kMeshReq) )
{ /* this is what we want: */
[ self SetSeqReg : kMeshFlushFIFO ]; /* Flush the FIFO */
meshAddr->transferCount0 = 1; /* set TC low = 1 */
meshAddr->transferCount1 = 0;
meshAddr->busStatus0 = 0; /***** clear ATN signal *****/
SynchronizeIO();
/* Issue the Message Out sending the Abort on its way. */
/* Note that this will cause an Unexpected-Disconnect. */
[ self SetSeqReg : kMeshMessageOutCmd ]; /* drop ATN signal */
meshAddr->xFIFO = kScsiMsgAbort; /* put out the Abort byte */
ioReturn = [ self WaitForMesh : TRUE ]; /* wait for cmdDone */
if ( ioReturn == IO_R_SUCCESS )
{
[ self SetSeqReg : kMeshEnableReselect ];/* bus about to go free */
[ self SetIntMask : kMeshIntrMask ]; /* Enable interrupts */
[ self SetSeqReg : kMeshBusFreeCmd ]; /* Clr ACK & go Bus-Free */
g.intLevel |= kLevelLatched; /* set latched-int flag */
return;
}
}/* end IF MSGO phase and REQ is set */
/***** USE THE HAMMER - NUKE THE BUS: *****/
ELG( 0, 0, '-AB-', "AbortActiveCommand - target refused to enter MSGO phase" );
[ self ResetHardware : TRUE ];
return;
}/* end AbortActiveCommand */
- (void) AbortDisconnectedCommand
{
CommandBuffer *cmdBuf;
IOSCSIRequest *scsiReq;
UInt8 msgByte;
if ( !queue_empty( &abortCmdQ ) )
{
cmdBuf = (CommandBuffer*)queue_first( &abortCmdQ );
scsiReq = cmdBuf->scsiReq;
meshAddr->destinationID = scsiReq->target;
msgOutPtr = (UInt8*)CCLAddress( kcclMSGOdata );
msgByte = kScsiMsgIdentify | scsiReq->lun;
*msgOutPtr++ = msgByte;
if ( cmdBuf->queueTag )
{ /* Tagged command: */
*msgOutPtr++ = kScsiMsgSimpleQueueTag;
*msgOutPtr++ = cmdBuf->queueTag;
*msgOutPtr++ = kScsiMsgAbortTag;
ELG( cmdBuf, cmdBuf->queueTag, 'AbT-', "AbortDisconnectedCommand - Tag" );
}
else
{ /* Untagged command: */
*msgOutPtr++ = kScsiMsgAbort;
ELG( cmdBuf, 0, 'AbU-', "AbortDisconnectedCommand - Abort (untagged)" );
}
[ self SetupMsgO ]; /* Setup for Message Out phase. */
[ self RunDBDMA : kcclStart stageLabel : kcclStageInit ];
}
return;
}/* end AbortDisconnectedCommand */
- (void) logTimestamp : (const char*) reason
{
#if DEBUG
/* kMaxTimestamp should be greater than twice the expected method depth */
/* since, if we dump the timestamp after it has wrapped around, we expect */
/* to lose earlier entries and, hence, the shallower method starts. */
#ifndef kMaxTimestampStack
#define kMaxTimestampStack 64
#endif
TimestampDataRecord stack[ kMaxTimestampStack + 1 ]; /* Allocate one extra */
UInt32 index = 0;
int start;
UInt32 count = 0;
UInt32 maxDepth = 0;
Boolean wasEnabled, unused;
char work[ 8 ];
struct timeval tv;
ns_time_t lastEventTime;
UInt32 elapsed;
UInt32 sinceMethodStart;
if ( reason )
{
IOLog( "%s: *** Log timestamp: %s\n", [ self name ], reason );
}
/* In case something we call causes timestamping, */
/* we want to avoid getting into an infinite loop. */
wasEnabled = EnableTimestamp( FALSE );
lastEventTime = 0;
while ( ReadTimestamp( &stack[ index ] ) )
{
++count;
work[0] = stack[ index ].timestampTag >> 24 & 0xFF;
work[1] = stack[ index ].timestampTag >> 16 & 0xFF;
work[2] = stack[ index ].timestampTag >> 8 & 0xFF;
work[3] = stack[ index ].timestampTag >> 0 & 0xFF;
work[4] = '\0';
elapsed = (unsigned)stack[ index ].eventTime - lastEventTime;
lastEventTime = stack[ index ].eventTime;
ns_time_to_timeval( stack[ index ].eventTime, &tv );
switch ( work[0] )
{
case '+': /* Entering a method */
IOLog( "%s: '%s' %u.%06u %u.%03u 0.0 %d\n",
[ self name ], work,
tv.tv_sec, tv.tv_usec,
elapsed / 1000, elapsed - ((elapsed / 1000) * 1000),
stack[ index ].timestampValue );
if ( index < kMaxTimestampStack )
{ if ( ++index > maxDepth )
maxDepth = index;
}
break;
case '=': /* Intermediate tag: find the method start */
case '-': /* End of method: find the method start */
sinceMethodStart = 0;
for ( start = index - 1; start >= 0; --start )
{
if ( (stack[ start ].timestampTag & 0x00FFFFFF)
== (stack[ index ].timestampTag & 0x00FFFFFF) )
{
sinceMethodStart = (unsigned)stack[ index ].eventTime
- stack[ start ].eventTime;
break;
}
}
IOLog( "%s: '%s' %u.%06u %u.%03u %u.%03u %d\n",
[ self name ], work,
tv.tv_sec, tv.tv_usec,
elapsed / 1000, elapsed - ((elapsed / 1000) * 1000),
sinceMethodStart / 1000,
sinceMethodStart - ((sinceMethodStart / 1000) * 1000),
stack[ index ].timestampValue );
if ( start >= 0 && work[0] == '-' )
index = start; /* Pop the stack */
break;
default:
IOLog( "%s: '%s' %u.%06u %u.%03u 0.0 %d _NoNestMark_\n",
[ self name ],
work,
tv.tv_sec,
tv.tv_usec,
elapsed / 1000,
elapsed - ((elapsed / 1000) * 1000),
stack[ index ].timestampValue );
break;
}
}/* end WHILE */
IOLog( "%s: *** %d timestamps, %d max method depth\n",
[ self name ], count, maxDepth );
unused = EnableTimestamp( wasEnabled );
#endif /* DEBUG */
return;
}/* end logTimestamp */
@end /* AppleMesh_SCSI(Mesh) */
@implementation AppleMesh_SCSI( Private )
/* Private chip- and architecture-independent methods. */
/* Pass one CommandBuffer to the IO thread; wait for completion. */
/* (We are called on the client's execution thread.) */
/* Normal completion status is in cmdBuf->scsiReq->driverStatus; */
/* a non-zero return from this function indicates a Mach IPC error. */
/* This method allocates and frees cmdBuf->cmdLock. */
- (IOReturn) executeCmdBuf : (CommandBuffer*) cmdBuf
{
msg_header_t msg = cmdMessageTemplate;
kern_return_t kernelReturn;
IOReturn ioReturn = IO_R_SUCCESS;
cmdBuf->flagActive = 0;
cmdBuf->cmdLock = [ [ NXConditionLock alloc ] initWith : CMD_PENDING ];
[ incomingCmdLock lock ];
queue_enter( &incomingCmdQ, cmdBuf, CommandBuffer*, link );
[ incomingCmdLock unlock ];
ELG( cmdBuf, *(UInt32*)&incomingCmdQ, 'ExeC', "executeCmdBuf" );
/* Create a Mach message and send it in order to wake up the IO thread: */
msg.msg_remote_port = gKernelInterruptPort;
kernelReturn = msg_send_from_kernel( &msg, MSG_OPTION_NONE, 0 );
if ( kernelReturn != KERN_SUCCESS )
{
PAUSE( 0, kernelReturn, 'exe-', "executeCmdBuf - msg_send_from_kernel() error status .\n" );
ioReturn = IO_R_IPC_FAILURE;
}
else /* Wait for IO complete: */
{
[ cmdBuf->cmdLock lockWhen : CMD_COMPLETE ];
}
[ cmdBuf->cmdLock free ];