Source to iokit/Drivers/scsi/drvMESH/mesh.cpp
/*
* Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* The contents of this file constitute Original Code as defined in and
* are subject to the Apple Public Source License Version 1.1 (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.
*
* This 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 1997-2000 Apple Computer Inc. All Rights Reserved.
* author Mike Johnson
*
* Set tabs every 4 characters.
*
* Edit History
* 25feb99 mlj Initial conversion from banana sources.
*/
#include <IOKit/scsi/IOSCSIParallelInterface.h>
#include <IOKit/pci/IOPCIDevice.h>
#include <mach/clock_types.h>
#include <IOKit/IOInterruptEventSource.h>
#include <IOKit/ppc/IODBDMA.h>
#include "mesh.h"
extern void IOGetTime( mach_timespec_t *clock_time );
extern void kprintf( const char *, ... );
extern void call_kdp(); // for debugging
#define super IOSCSIParallelController
OSDefineMetaClassAndStructors( meshSCSIController, IOSCSIParallelController ) ;
static globals g; /**** Instantiate the globals ****/
/* Channel Program. Note that this script must match the offsets */
/* specified in mesh.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. */
static 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 }, // kcclReadBuf8
{ 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 );
/* 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 // round down to nearest page
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 */
};
#if USE_ELG
static void AllocateEventLog( UInt32 size )
{
if ( g.evLogBuf )
return;
g.evLogFlag = 0; /* assume insufficient memory */
g.evLogBuf = (UInt8*)IOMalloc( size );
if ( !g.evLogBuf )
{
kprintf( "probe - MESH evLog allocation failed " );
return;
}
bzero( g.evLogBuf, size );
g.evLogBufp = g.evLogBuf;
g.evLogBufe = g.evLogBufp + kEvLogSize - 0x20; // ??? overran buffer?
g.evLogFlag = 0xFEEDBEEF; // continuous wraparound
// g.evLogFlag = 'step'; // stop at each ELG
// g.evLogFlag = 0x0333; // any nonzero - don't wrap - stop logging at buffer end
return;
}/* end AllocateEventLog */
static void EvLog( UInt32 a, UInt32 b, UInt32 ascii, char* str )
{
register UInt32 *lp; /* Long pointer */
mach_timespec_t time;
if ( g.evLogFlag == 0 )
return;
IOGetTime( &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.tv_nsec >> 10) & 0x003FFFFF); // ~ 1 microsec resolution
*lp++ = a;
*lp++ = b;
*lp = ascii;
if( g.evLogFlag == 'step' )
{ static char code[ 5 ] = {0,0,0,0,0};
*(UInt32*)&code = ascii;
// kprintf( "%8x mesh: %8x %8x %s %s\n", time.tv_nsec>>10, a, b, code, str );
kprintf( "%8x mesh: %8x %8x %s\n", time.tv_nsec>>10, a, b, code );
}
return;
}/* end EvLog */
static void Pause( UInt32 a, UInt32 b, UInt32 ascii, char* str )
{
char work [ 256 ];
char name[] = "meshSCSIController:";
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 );
panic( work );
// call_kdp(); // ??? use kdp=3 in boot parameters
return;
}/* end Pause */
#endif // USE_ELG
bool meshSCSIController::configure( IOService *provider,
SCSIControllerInfo *controllerInfo )
{
IOReturn ioReturn = kIOReturnInternalError;
g.intLevel = 0;
g.meshInstance = this;
#if USE_ELG
AllocateEventLog( kEvLogSize );
ELG( g.evLogBufp, &g.evLogFlag, 'MESH', "configure - event logging set up." );
#endif /* USE_ELG */
ELG( this, provider, 'Cnfg', "configure" );
fProvider = (IOPCIDevice*)provider;
ioReturn = initializeHardware();
if ( ioReturn != kIOReturnSuccess )
return false;
/* Register our interrupt handler routine: */
fInterruptEvent = IOInterruptEventSource::interruptEventSource(
(OSObject*)this,
(IOInterruptEventAction)&meshSCSIController::interruptOccurred,
provider,
0 );
if ( fInterruptEvent == NULL )
{
PAUSE( 0, 0, 'IES-', "registerMESHInterrupt - can't register interrupt action" );
return false;
}
getWorkLoop()->addEventSource( fInterruptEvent );
fInterruptEvent->enable();
/* allocate a big-endian memory cursor: */
fMemoryCursor = IOBigMemoryCursor::withSpecification( kMaxDMATransfer, kMaxDMATransfer );
if ( fMemoryCursor == NULL )
{
PAUSE( 0, kMaxDMATransfer, 'Mem-', "mesh::start - IOBigMemoryCursor::withSpecification NG" );
return false;
}
/* Fill in the SCSIControllerInfo structure and return: */
controllerInfo->initiatorId = 7;
controllerInfo->maxTargetsSupported = 8;
controllerInfo->maxLunsSupported = 8;
controllerInfo->minTransferPeriodpS = 100000; /* picoSecs for 10 MHz */
controllerInfo->maxTransferOffset = 15;
controllerInfo->maxTransferWidth = 1;
controllerInfo->maxCommandsPerController= 0;
controllerInfo->maxCommandsPerTarget = 8; // 0 is unlimited
controllerInfo->maxCommandsPerLun = 0;
controllerInfo->tagAllocationMethod = kTagAllocationPerLun;
controllerInfo->maxTags = 256;
controllerInfo->commandPrivateDataSize = sizeof( PrivCmdData );
controllerInfo->disableCancelCommands = false;
return true;
}/* end configure */
void meshSCSIController::executeCommand( IOSCSICommand *scsiCommand )
{
SCSICDBInfo scsiCDB;
SCSITargetParms targetParms;
UInt8 msgByte;
UInt8 rc;
if ( fCmd || (g.intLevel & kLevelLatched) )
{
disableCommands();
rescheduleCommand( scsiCommand );
ELG( fCmd, scsiCommand, 'Busy', "executeCommand - bus busy so bounce this cmd" );
return;
}
fCmd = scsiCommand;
fCmdData = (PrivCmdData*)scsiCommand->getCommandData();
bzero( fCmdData, sizeof( fCmdData ) ); /// ??? unnecessary?
scsiCommand->getTargetLun( &fCurrentTargetLun );
scsiCommand->getCDB( &scsiCDB );
scsiCommand->getDevice( kIOSCSIDevice )->getTargetParms( &targetParms );
ELG( scsiCommand, *(UInt16*)&fCurrentTargetLun<<16 | (scsiCDB.cdbTag & 0xFFFF), 'Exec', "meshSCSIController::executeCommand" );
ELG( *(UInt32*)&scsiCDB.cdb[0], *(UInt32*)&scsiCDB.cdb[4] , '=CDB', "executeCommand - CDB" );
fMsgOutFlag = 0;
fMsgOutPtr = &fCCL[ kcclMSGOdata ];
/* Identify byte: */
msgByte = kSCSIMsgIdentify | kSCSIMsgEnableDisconnectMask | fCurrentTargetLun.lun;
if ( scsiCDB.cdbFlags & kCDBFlagsNoDisconnect )
msgByte &= ~kSCSIMsgEnableDisconnectMask;
*fMsgOutPtr++ = msgByte;
/* Tag msg: */
if ( scsiCDB.cdbTagMsg )
{
*fMsgOutPtr++ = scsiCDB.cdbTagMsg;
*fMsgOutPtr++ = scsiCDB.cdbTag;
ELG( 0, scsiCDB.cdbTagMsg<<16 | scsiCDB.cdbTag, ' tag', "meshSCSIController::executeCommand - tag" );
}
/* Abort msg: */
if ( scsiCDB.cdbAbortMsg )
{
ELG( scsiCommand->getOriginalCmd(), scsiCDB.cdbAbortMsg, 'Abor', "meshSCSIController::executeCommand - abort msg." );
*fMsgOutPtr++ = scsiCDB.cdbAbortMsg;
}
/* Sync negotiation msg: */
fNegotiatingSDTR = false;
if ( scsiCDB.cdbFlags & kCDBFlagsNegotiateSDTR )
{ fNegotiatingSDTR = true;
*fMsgOutPtr++ = kSCSIMsgExtended;
*fMsgOutPtr++ = 3;
*fMsgOutPtr++ = kSCSIMsgSyncXferReq;
*fMsgOutPtr++ = targetParms.transferPeriodpS / 4000;
*fMsgOutPtr++ = targetParms.transferOffset;
}
/***** Try to start the command on the hardware: *****/
rc = startCommand(); /* Call the hardware layer. */
if ( rc != kHardwareStartOK )
{ /* Hardware can't start now */
ELG( fCmd, 0, 'Exe-', "meshSCSIController::executeCommand - command bounced back" );
rescheduleCommand( fCmd );
fCmd = NULL;
}
return;
}/* end executeCommand */
void meshSCSIController::cancelCommand( IOSCSICommand *scsiCommand )
{
IOSCSICommand *origCmd;
PrivCmdData *origCmdData;
SCSIResults results;
origCmd = scsiCommand->getOriginalCmd();
ELG( scsiCommand, origCmd, 'Can-', "meshSCSIController::cancelCommand" );
if ( origCmd ) /* if original command still around, complete it: */
{
origCmd->getResults( &results );
origCmdData = (PrivCmdData*)origCmd->getCommandData();
results.bytesTransferred = origCmdData->results.bytesTransferred;
origCmd->setResults( &results );
origCmd->complete();
}
scsiCommand->complete();
return;
}/* end cancelCommand */
void meshSCSIController::resetCommand( IOSCSICommand *scsiCommand )
{
ELG( scsiCommand, 0, 'Rst-', "meshSCSIController::resetCommand" );
resetBus();
fCmdData = (PrivCmdData*)scsiCommand->getCommandData();
bzero( &fCmdData->results, sizeof( fCmdData->results ) );
scsiCommand->setResults( &fCmdData->results );
scsiCommand->complete();
return;
}/* end resetCommand */
/* Fetch the device's bus address and interrupt port number. */
/* Also, allocate one page of memory for the channel program. */
IOReturn meshSCSIController::initializeHardware()
{
IOReturn ioReturn;
int i;
ELG( 0, 0, 'IniH', "initializeHardware" );
fInitiatorID = kInitiatorIDDefault;
fInitiatorIDMask = 1 << kInitiatorIDDefault; /* BusID bitmask for reselection. */
for ( i = 0; i < 8; ++i)
fSyncParms[ i ] = kSyncParmsAsync;
ioReturn = getHardwareMemoryMaps();
if ( ioReturn == kIOReturnSuccess )
ioReturn = allocHdwAndChanMem();
if ( ioReturn == kIOReturnSuccess )
ioReturn = doHBASelfTest();
if ( ioReturn == kIOReturnSuccess )
{
ioReturn = resetBus();
fMESHAddr->sourceID = fInitiatorID;
}
return ioReturn;
}/* end initializeHardware */
IOReturn meshSCSIController::getHardwareMemoryMaps()
{
if ( !fSCSIMemoryMap )
{
fSCSIMemoryMap = fProvider->mapDeviceMemoryWithIndex( kMESHRegisterBase );
if ( !fSCSIMemoryMap )
{
ELG( 0, 0, 'Map-', "getHardwareMemoryMaps - can't map MESH." );
return kIOReturnInternalError;
}
fMESHPhysAddr = fSCSIMemoryMap->getPhysicalAddress();
fMESHAddr = (MeshRegister*)fSCSIMemoryMap->getVirtualAddress();
ELG( fMESHPhysAddr, fMESHAddr, '=MSH', "getHardwareMemoryMaps - MESH regs" );
g.meshAddr = (UInt32)fMESHAddr; // for debugging, miniMon ...
}
if ( !fDBDMAMemoryMap )
{
fDBDMAMemoryMap = fProvider->mapDeviceMemoryWithIndex( kDBDMARegisterBase );
if ( !fDBDMAMemoryMap )
{
ELG( 0, 0, 'map-', "getHardwareMemoryMaps - can't map DBDMA." );
return kIOReturnInternalError;
}
dbdmaAddrPhys = fDBDMAMemoryMap->getPhysicalAddress();
dbdmaAddr = (UInt8*)fDBDMAMemoryMap->getVirtualAddress();
ELG( dbdmaAddrPhys, dbdmaAddr, '=DMA', "getHardwareMemoryMaps - DBDMA regs" );
#if CustomMiniMon
gMESH_DBDMA = (UInt32)dbdmaAddr;
gMESH_DBDMA_Phys = (UInt32)dbdmaAddrPhys;
#endif /* CustomMiniMon */
}
return kIOReturnSuccess;
}/* end getHardwareMemoryMaps */
/* 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 kIOReturnSuccess if successful, else an error status. */
IOReturn meshSCSIController::allocHdwAndChanMem()
{
/* Set the default selection timeout to the MESH value (10 msec units). */
fSelectionTimeout = 250 / 10; // ??? symbolic
/* Allocate a page of wired-down memory in the kernel: */
fCCLSize = page_size;
fCCL = (UInt8*)IOMallocContiguous( fCCLSize, page_size, &fCCLPhysAddr );
if ( !fCCL )
{ PAUSE( 0, fCCLSize, 'CCA-', "allocHdwAndChanMem - can't allocate channel command area.\n" );
return kIOReturnNoMemory;
}
/* Remember the number of DBDMA descriptors that */
/* can be used for data transfer channel commands. */
fDBDMADescriptorMax = (fCCLSize - kcclDataXfer) / sizeof( DBDMADescriptor );
g.cclPhysAddr = (UInt32)fCCLPhysAddr; // for debugging ease
g.cclLogAddr = (UInt32)fCCL;
ELG( fCCLPhysAddr, fCCL, '=CCL', "allocHdwAndChanMem - CCL phys/logical addresses." );
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 kIOReturnSuccess;
}/* end allocHdwAndChanMem */
/* Perform one-time-only channel command program initialization. */
void meshSCSIController::initCP()
{
register DBDMADescriptor *dst = (DBDMADescriptor*)fCCL;
register const DBDMADescriptor *src = gDescriptorList;
UInt32 i;
UInt8 *bp;
DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
ELG( src, dst, 'I CP', "initCP - Init the Channel Program" );
/* 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 */
DBDMARegs->interruptSelect = 0x00000000; /* Never let DBDMA interrupt */
DBDMARegs->waitSelect = 0x20002000; /* Wait until command done */
DBDMARegs->branchSelect = 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 + fMESHPhysAddr );
break;
case kRelAddressCP:
dst->address = SWAP( src->address + (UInt32)fCCLPhysAddr );
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)fCCLPhysAddr );
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 = &fCCL[ 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 */
/* doHBASelfTest - MESH chip self-test. (Minimal: it could be extended.) */
IOReturn meshSCSIController::doHBASelfTest()
{
IOReturn ioReturn = kIOReturnSuccess;
UInt8 tempByte;
ELG( fMESHPhysAddr, fMESHAddr, 'Test', "doHBASelfTest" );
if ( ioReturn == kIOReturnSuccess )
{
tempByte = fMESHAddr->MESHID & 0x1F;
if ( tempByte < kMeshMESHID_Value )
{
PAUSE( 0, tempByte, 'hba-', "doHBASelfTest - Invalid MESH chip ID .\n" );
ioReturn = kIOReturnNoDevice;
}
}
return ioReturn;
}/* end doHBASelfTest */
void meshSCSIController::interruptOccurred( IOInterruptEventSource *ies, int intCount )
{
// DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
// ELG( ies, intCount, 'Int+', "interruptOccurred" );
g.intLevel |= kLevelISR; /* set ISR flag */
g.intLevel &= ~kLevelLatched; /* clear latched */
// ELG( DBDMARegs->channelStatus, DBDMARegs->commandPtrLo, 'Int+', "interruptOccurred." );
ELG( fCmd, *(UInt32*)&fCCL[ kcclStageLabel ], 'Int+', "interruptOccurred." );
// ELG( *(UInt32*)0xF3000024, *(UInt32*)0xF300002C, 'Int ', "interruptOccurred." );
doHardwareInterrupt(); /**** HANDLE THE INTERRUPT ****/
// ELG( fCmd, *(UInt32*)0xF300002C, 'Intx', "interruptOccurred." );
g.intLevel &= ~kLevelISR; /* clear ISR flag */
return;
}/* end interruptOccurred */
/* doHardwareInterrupt - called from Workloop in superclass */
void meshSCSIController::doHardwareInterrupt()
{
DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
getHBARegsAndClear( true ); /* get the MESH registers */
setIntMask( 0 ); /* Disable MESH interrupts */
fFlagReselecting = 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( DBDMARegs->commandPtrLo,
(g.shadow.mesh.busStatus0 << 8) | g.shadow.mesh.busStatus1,
'ISR?',
"doHardwareInterrupt - spurious interrupt" );
if ( !fCmd )
{ /* if no request: */
setIntMask( kMeshIntrMask ); /* Enable interrupts */
enableCommands(); /* let superclass issue another command */
}
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. */
DBDMARegs->channelControl = SWAP( 0x20002000 ); // set FLUSH bit
SynchronizeIO();
DBDMARegs->channelControl = SWAP( 0x80000000 ); // clr RUN bit
SynchronizeIO();
// invalidate_cache_v( (vm_offset_t)fCCL, fCCLSize );
/* If the DBDMA was running a channel command, handle this */
/* (this could be done at a lower priority level). */
if ( *(UInt32*)&fCCL[ kcclStageLabel ] )
{
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 ( fCmd )
{ /* 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. */
setIntMask( kMeshIntrMask ); /* Re-enable interrupts */
enableCommands(); /* let superclass issue another command */
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. */
processInterrupt();
}
return;
}/* end doHardwareInterrupt */
/* Respond to a DBDMA channel command completion interrupt */
/* or some error or exception condition. */
void meshSCSIController::processInterrupt()
{
UInt32 stage; /* Stage in the Channel Program */
UInt32 cclIndex; /* Index of CCL descriptor */
UInt32 count; /* transfer count */
UInt8 phase; /* Current bus phase */
IOReturn rc;
/* Get the state of the DBDMA: */
stage = *(UInt32*)&fCCL[ kcclStageLabel ];
cclIndex = SWAP( ((DBDMAChannelRegisters*)dbdmaAddr)->commandPtrLo ) - (UInt32)fCCLPhysAddr;
*(UInt32*)&fCCL[ kcclStageLabel ] = 0;
/* Check for SCSI Bus Reset Detected: */
if ( g.shadow.mesh.error & kMeshErrSCSIRst )
{
ELG( fCmd, stage, 'BRst', "Process interrupt with no active request\n" );
fCmd = NULL;
super::resetOccurred();
setIntMask( kMeshIntrMask );
enableCommands(); /* let superclass issue another command */
return;
}
if ( fCmd == NULL )
{
if ( g.shadow.mesh.exception & kMeshExcResel )
{
handleReselectionInterrupt();
return;
}
/* 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. */
ELG( 0, 0, 'Int0', "Process interrupt with no active request\n" );
setIntMask( kMeshIntrException | kMeshIntrError );
enableCommands(); /* let superclass issue another command */
return;
}/* end IF had no active command */
/* There is an active request - switch on stage of the DBDMA: */
switch ( stage )
{
case kcclStageGood: /* Normal completion */
doInterruptStageGood();
break;
case kcclStageInit: /* Value before DBDMA runs */
case kcclStageArb: /* Arbitration anomaly */
doInterruptStageArb();
break;
case kcclStageSelA: /* Selection anomaly */
doInterruptStageSelA();
break;
case kcclStageMsgO: /* Message Out */
doInterruptStageMsgO();
break;
case kcclStageCmdO: /* Command stage anomaly */
doInterruptStageCmdO();
break;
case kcclStageXfer:
// if ( cmdBuf->resultFlags & scsiAutosenseStarted )
// doInterruptStageXferAutosense();
// else
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 = *(UInt32*)&fCCL[ kcclBatchSize ]; /* Our transfer count */
fCmdData->results.bytesTransferred += count; /* Increment data index */
if ( fReadAlignmentCount ) // Hack for Radar 1670626
{
fCmdData->mdp->writeBytes( fReadAlignmentIndex, &fCCL[ kcclReadBuf8 ], fReadAlignmentCount );
fReadAlignmentCount = 0;
}
ELG( count, fCmdData->results.bytesTransferred, 'Uidx', "processInterrupt" );
*(UInt32*)&fCCL[ 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. */
startBucket();
return;
}/* end IF no REQ signal */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
switch ( phase )
{
case kBusPhaseMSGI:
rc = DoMessageInPhase();
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 */
startBucket();
break;
default:
PAUSE( 0, phase, 'pmm-', "processInterrupt - expected Status phase.\n" );
break;
}/* end SWITCH on phase */
break;
case kcclStageBucket:
count = *(UInt32*)&fCCL[ kcclBatchSize ]; /* Our transfer count */
fCmdData->results.bytesTransferred += count; /* Increment data index */
*(UInt32*)&fCCL[ kcclBatchSize ] = 0; /* Clear our count */
ELG( count, fCmdData->results.bytesTransferred, 'Buck',
"processInterrupt - bit bucket done.\n" );
/* MESH doesn't always have a phase mismatch when completing a */
/* synchronous data write. It may be that the drive is going to */
/* MsgIn with SDP and/or disconnect instead of Status and the */
/* timing is different. If the current phase is not a data phase, */
/* AND the TC and FIFO count indicate no data overflowed AND */
/* and all the data transferred, don't set an error condition. */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
count = g.shadow.mesh.transferCount1 << 8 | g.shadow.mesh.transferCount0;
count += g.shadow.mesh.FIFOCount;
count &= 0xFFFF;
if ( (count != 0)
|| (fCmdData->xferCount != fCmdData->results.bytesTransferred)
|| !(phase > kBusPhaseDATI) )
{
fCmdData->results.returnCode = kIOReturnOverrun;
}
setSeqReg( kMeshFlushFIFO ); /* flush the FIFO */
runDBDMA( kcclGetStatus, 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 */
/* Start a SCSI transaction for the specified command. */
UInt8 meshSCSIController::startCommand()
{
fCmd->getPointers( &fCmdData->mdp, &fCmdData->xferCount, &fCmdData->isWrite );
clearCPResults(); /* clear the result field in all the Channel Commands */
{
fCmdData->results.bytesTransferred = 0;
fCmdData->savedDataPosition = 0;
updateCP( false ); /* Update the DBDMA Channel Program */
}
/***** Can a caller override the default timeout? *****/
fMESHAddr->selectionTimeOut = fSelectionTimeout;
fMESHAddr->destinationID = fCurrentTargetLun.target;
// fMESHAddr->syncParms = kSyncParmsAsync;
fMESHAddr->syncParms = fSyncParms[ fCurrentTargetLun.target ];
SynchronizeIO();
runDBDMA( kcclStart, kcclStageInit );
if ( g.intLevel & kLevelLatched ) /* return Busy if reselecting */
return kHardwareStartBusy; /* or some other issue. */
return kHardwareStartOK;
}/* end startCommand */
/* 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 */
/* kMaxDMATransfer (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 rebuild 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 meshSCSIController::updateCP( bool reselecting )
{
DBDMADescriptor *descProto = (DBDMADescriptor*)&fCCL[ kcclPrototype ];
DBDMADescriptor *descriptorPtr; /* current data descriptor */
DBDMADescriptor *descriptorMax; /* beyond the last data descriptor */
DBDMADescriptor *preamblePtr; /* current prolog descriptor */
SCSICDBInfo scsiCDB;
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 */
UInt32 actualRanges;
IOPhysicalSegment range[ kMaxMemCursorSegs ];
DBDMADescriptor *dp;
UInt32 rangeLength, rangeLocation;
UInt32 i;
IOReturn ioReturn = kIOReturnSuccess;
fReadAlignmentCount = 0; /* Clear Read misalignment condition */
/* 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 = (DBDMADescriptor*)&fCCL[ kcclDataXfer ];
descriptorMax = &descriptorPtr[ fDBDMADescriptorMax - 16 ];
transferLength = fCmdData->xferCount - fCmdData->results.bytesTransferred;
ELG( descriptorPtr, transferLength, 'UpCP', "updateCP" );
if ( !reselecting )
{
setupMsgO(); /* Setup for Message Out phase. */
fCmd->getCDB( &scsiCDB );
/* Setup for Command phase: */
fCCL[ kcclCmdoMTC ] = scsiCDB.cdbLength; /* MESH transfer count */
fCCL[ kcclCmdoDTC ] = scsiCDB.cdbLength; /* DBDMA count */
bcopy( &scsiCDB.cdb[0], &fCCL[ kcclCMDOdata ], scsiCDB.cdbLength );
}/* end IF not reselecting */
/* Generate MESH "sequence" & DBDMA "operation" for Input or Output: */
if ( fCmdData->isWrite )
{ dbdmaOpProto = OUTPUT_MORE | kBranchIfFalse;
meshSeq = kMeshDataOutCmd | kMeshSeqDMA;
}
else
{ dbdmaOpProto = INPUT_MORE | kBranchIfFalse;
meshSeq = kMeshDataInCmd | kMeshSeqDMA;
}
*(UInt32*)&fCCL[ kcclBatchSize ] = 0;
while ( ioReturn == kIOReturnSuccess
&& 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 */
/* Do one group of pages: */
actualRanges = fMemoryCursor->getPhysicalSegments(
fCmdData->mdp,
fCmdData->results.bytesTransferred + totalXferLen,
range,
kMaxMemCursorSegs );
if ( actualRanges == 0 )
break;
ELG( range[0].length, range[0].location, 'Rng1', "updateCP - 1st range" );
for ( i = 0; i < actualRanges; i++ )
{ rangeLocation = range[i].location;
rangeLength = range[i].length;
groupLength += rangeLength;
// 29apr99 mlj Radar 1670626 - the DBDMAs in Grand Central (and
// either Heathrow or O'Hare) have a bug. On the initial
// data xfer of a Read, if the buffer is not aligned on an
// 8-byte boundary, and the transfer ends before the boundary is
// reached, then the memory in front of the buffer is trashed.
// If these conditions apply, we read the misaligned bytes
// into the CCL at kcclReadBuf8 now and copy them to the
// user buffer later when we get an interrupt. The 1st range
// is split and 2 DBDMA Channel Commands are generated.
if ( (i == 0) // 1st range of group
&& (totalXferLen == 0) // 1st group of batch
&& (!fCmdData->isWrite) // READing
&& (rangeLocation & 0x07) ) // not 8-byte aligned
{
ELG( rangeLength, rangeLocation, 'Rd-8', "updateCP - non-8-byte-aligned read" );
fReadAlignmentIndex = fCmdData->results.bytesTransferred;
fReadAlignmentCount = 8 - (rangeLocation & 0x07);
dbdmaOp = dbdmaOpProto | fReadAlignmentCount;
rangeLength -= fReadAlignmentCount;
transferLength -= fReadAlignmentCount;
if ( transferLength <= 0 )
dbdmaOp |= (INPUT_MORE ^ INPUT_LAST); /* add LAST to cmd */
descriptorPtr->operation = SWAP( dbdmaOp );
descriptorPtr->address = SWAP( (UInt32)fCCLPhysAddr + kcclReadBuf8 );
descriptorPtr->cmdDep = SWAP( (UInt32)fCCLPhysAddr + kcclProblem );
descriptorPtr->result = 0; // for debugging
descriptorPtr++;
if ( rangeLength == 0 )
continue;
rangeLocation += fReadAlignmentCount;
}/* end IF READing and buffer is misaligned at batch start */
dbdmaOp = dbdmaOpProto | rangeLength;
transferLength -= rangeLength;
if ( transferLength <= 0 )
dbdmaOp |= (INPUT_MORE ^ INPUT_LAST); /* add LAST to cmd */
descriptorPtr->operation = SWAP( dbdmaOp );
descriptorPtr->address = SWAP( rangeLocation );
descriptorPtr->cmdDep = SWAP( (UInt32)fCCLPhysAddr + kcclProblem );
descriptorPtr->result = 0; // for debugging
descriptorPtr++;
}/* end FOR each range in this group */
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)fCCLPhysAddr + kcclProblem );
preamblePtr->result = 0;
descriptorPtr = preamblePtr + 1;
ioReturn = kIOReturnInvalid; /* 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( &fCCL[ 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( fMESHPhysAddr + 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 attempt to 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) */
/* Do some synchronous data transfer cleanup: */
syncParms = fSyncParms[ fCurrentTargetLun.target ];
fMESHAddr->syncParms = syncParms;
SynchronizeIO();
ELG( fMsgOutFlag, syncParms, 'SynP', "updateCP - sync parms" );
if ( ((syncParms & 0xF0) || (fMsgOutFlag & kFlagMsgOut_SDTR)) // Sync?
&& (totalXferLen > 0) // any data moving?
&& (transferLength == 0) ) // end of xfer?
{
fFlagIncompleteDBDMA = 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( fMESHPhysAddr + 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)fCCLPhysAddr + 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) && fCmdData->isWrite )
{
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)fCCLPhysAddr + 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)fCCLPhysAddr + kcclOverrun );
descriptorPtr->result = 0;
descriptorPtr++;
/* Fix up the DataOverrun code just in case: */
dp = (DBDMADescriptor*)&fCCL[ kcclOverrunMESH ];
if ( fCmdData->isWrite )
{ dp->cmdDep = SWAP( kMeshDataOutCmd | kMeshSeqDMA );
dp = (DBDMADescriptor*)&fCCL[ kcclOverrunDBDMA ];
dp->operation = SWAP( OUTPUT_LAST | kBranchIfFalse | 8 );
}
else
{ dp->cmdDep = SWAP( kMeshDataInCmd | kMeshSeqDMA );
dp = (DBDMADescriptor*)&fCCL[ kcclOverrunDBDMA ];
dp->operation = SWAP( INPUT_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 )
fFlagIncompleteDBDMA = true; /* set incomplete */
else fFlagIncompleteDBDMA = false; /* assume complete xfer */
if ( fFlagIncompleteDBDMA )
{ /* 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( &fCCL[ 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)fCCLPhysAddr + kcclGetStatus );
descriptorPtr->result = 0;
/* If this is a partial transfer, set 'incomplete' flag and */
/* change the Branch from GetStatus to Good: */
if ( fFlagIncompleteDBDMA )
{ /* change last Branch from Status to Good: */
descriptorPtr->cmdDep = SWAP( (UInt32)fCCLPhysAddr + kcclMESHintr );
ELG( descriptorPtr, transferLength, 'Part', "updateCP - built partial CCL." );
}
descriptorPtr++;
}/* end if/ELSE Async or partial xfer */
return;
}/* end updateCP */
void meshSCSIController::clearCPResults()
{
register DBDMADescriptor *dp = (DBDMADescriptor*)&fCCL[ 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 */
/* Set up the channel commands for MsgO phase. */
void meshSCSIController::setupMsgO()
{
UInt8 msgoSize;
fMsgOutPtr--; /* treat the last or only byte special (drop ATN) */
msgoSize = fMsgOutPtr - &fCCL[ kcclMSGOdata ];
if( msgoSize == 0 )
{ /* Identify byte only: */
*(UInt32*)&fCCL[ kcclMsgoBranch ] = SWAP( NOP_CMD | kBranchAlways );
}
else /* multibyte message - set counts for all but last byte: */
{ fCCL[ kcclMsgoMTC ] = msgoSize;
fCCL[ kcclMsgoDTC ] = msgoSize;
/* NOP the BRANCH: */
*(UInt32*)&fCCL[ kcclMsgoBranch ] = SWAP( NOP_CMD );
}
fCCL[ kcclMSGOLast ] = *fMsgOutPtr; /* position last byte */
return;
}/* end setupMsgO */
/* Start a Channel Program at the given offset */
/* with the specified stage label. */
void meshSCSIController::runDBDMA( UInt32 offset, UInt32 stageLabel )
{
register UInt8 intReg;
mach_timespec_t arbEndTime, curTime;
DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
fMsgInFlag = 0; /* clear message-in flags. */
*(UInt32*)&fCCL[ kcclStageLabel ] = stageLabel; /* set the stage */
/* Let MESH interrupt only for errors or exceptions, but not cmdDone */
setIntMask( kMeshIntrException | kMeshIntrError );
intReg = fMESHAddr->interrupt;
switch ( intReg )
{
case kMeshIntrCmdDone:
if ( !fFlagReselecting ) // ??? Don't drop ACK fm MSG-IN or Sync data flows
/* clear any pending command interrupts (but not reselect et al) */
fMESHAddr->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_dcache( (vm_offset_t)fCCL, fCCLSize, false );
// ELG( *(UInt32*)0xF3000020, *(UInt32*)0xF300002C, 'G C+', "runDBDMA." );
if ( offset == kcclStart )
{
fFlagReselecting = false;
setSeqReg( kMeshArbitrateCmd ); /* ARBITRATE */
/* wait 50 mikes or cmdDone, whichever comes first: */
IOGetTime( &arbEndTime );
arbEndTime.tv_nsec += 50000;
if ( arbEndTime.tv_nsec >= NSEC_PER_SEC )
{ arbEndTime.tv_nsec -= NSEC_PER_SEC;
arbEndTime.tv_sec += 1;
}
while ( true )
{
getHBARegsAndClear( false ); /* get regs without hosing */
IOGetTime( &curTime );
if ( g.shadow.mesh.interrupt & kMeshIntrCmdDone )
break;
if ( curTime.tv_sec < arbEndTime.tv_sec ) continue;
if ( curTime.tv_nsec >= arbEndTime.tv_nsec ) break;
}/* end wait cmdDone or 50 mikes */
if ( g.shadow.mesh.interrupt == kMeshIntrCmdDone )
{ /* No err, no exc: Arbitration won: */
fMESHAddr->interrupt = kMeshIntrCmdDone;
SynchronizeIO();
setSeqReg( kMeshDisableReselect ); /* disable reselect */
offset = 0x150; // ??? fix this. Point to Select/Atn
*(UInt32*)&fCCL[ kcclStageLabel ] = kcclStageSelA; /* set stage to Select */
}/* 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." );
// ??? check FIFO count for presence of Target ID ???
setSeqReg( kMeshResetMESH ); /* hack it: whack it */
getHBARegsAndClear( true ); /* get regs/clear */
setSeqReg( kMeshEnableReselect ); /* Let reselect again */
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, */
break; /* break SWITCH - Don't start the DBDMA. */
}/* end ELSE lost Arbitration */
}/* end IF DBDMA to start at Arbitrate */
getHBARegsAndClear( false ); // ??? debug: see if ACK still set
ELG( 0, offset<<16 | stageLabel, 'DMA+', "runDBDMA" );
/// dbdma_start( DBDMA_MESH_SCSI, (dbdma_command_t*)((UInt32)fCCLPhysAddr + offset) );
DBDMARegs->commandPtrLo = SWAP( (UInt32)fCCLPhysAddr + offset );
SynchronizeIO();
DBDMARegs->channelControl = SWAP( 0x80008000 ); // set RUN bit
SynchronizeIO();
return;
}/* end SWITCH on interrupt register */
ELG( 0, intReg, 'Pnd-', "runDBDMA - interrupt probably pending (reselect?)." );
getHBARegsAndClear( false ); /* display regs without clearing */
*(UInt32*)&fCCL[ kcclStageLabel ] = kcclStageIdle; /* set stage to none */
g.intLevel |= kLevelLatched; /* set latched-interrupt flag. */
setIntMask( kMeshIntrMask ); /* make sure MESH interrupts enabled */
return;
}/* end runDBDMA */
void meshSCSIController::completeCommand()
{
ELG( fCmdData->results.bytesTransferred, fCmdData->results.scsiStatus, ' IOC', "meshSCSIController::completeCommand" );
{
switch ( fCmdData->results.scsiStatus )
{
case kSCSIStatusGood:
if ( fCmdData->results.bytesTransferred != fCmdData->xferCount
&& fCmdData->results.returnCode == kIOReturnSuccess )
{
fCmdData->results.returnCode = kIOReturnUnderrun;
}
break;
case kSCSIStatusCheckCondition:
ELG( 0, 0, 'Chek', "meshSCSIController::completeCommand - Check Condition" );
fCmdData->results.returnCode = kIOReturnError;
break;
case kSCSIStatusQueueFull:
default:
ELG( fCmd, fCmdData->results.scsiStatus, 'Sta?', "meshSCSIController::completeCommand - bad status" );
fCmdData->results.returnCode = kIOReturnError;
break;
}/* end SWITCH on SCSI status */
}/* end IF not autosense */
fCmd->setResults( &fCmdData->results );
fCmd->complete();
fCmd = NULL;
fCmdData = NULL;
fCurrentTargetLun.target = kInvalidTarget;
fCurrentTargetLun.lun = kInvalidLUN;
return;
}/* end completeCommand */
void meshSCSIController::free()
{
DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
if ( fMESHAddr )
{
setSeqReg( kMeshResetMESH );
}
if ( DBDMARegs )
{
// dbdma_stop( DBDMA_MESH_SCSI );
DBDMARegs->channelControl = SWAP( 0x20002000 ); // set FLUSH bit
SynchronizeIO();
DBDMARegs->channelControl = SWAP( 0x80000000 ); // clr RUN bit
SynchronizeIO();
}
if ( fInterruptEvent )
{
fInterruptEvent->disable();
fInterruptEvent->release();
fInterruptEvent = 0;
}
if ( fCCL )
{
IOFreeContiguous( (void*)fCCL, fCCLSize );
fCCL = NULL;
}
if ( fMemoryCursor )
{
fMemoryCursor->release();
fMemoryCursor = 0;
}
if ( g.evLogBuf )
{
IOFree( (void*)g.evLogBuf, kEvLogSize );
g.evLogBuf = 0;
}
super::free();
return;
}/* end free */
/* startBucket - Start the channel commands to run the bit bucket. */
void meshSCSIController::startBucket()
{
DBDMADescriptor *dp; /* current data descriptor */
UInt32 dbdmaOp; /* Opcode for DBDMA */
UInt32 meshSeq; /* Opcode for MESH request */
ELG( fCmd, fCmdData, 'Bkt-', "startBucket" );
/* Generate MESH "sequence" & DBDMA "operation" for Input or Output: */
if ( fCmdData->isWrite )
{ dbdmaOp = OUTPUT_MORE | kBranchIfFalse | 8;
meshSeq = kMeshDataOutCmd | kMeshSeqDMA;
}
else
{ dbdmaOp = INPUT_MORE | kBranchIfFalse | 8;
meshSeq = kMeshDataInCmd | kMeshSeqDMA;
}
dp = (DBDMADescriptor*)&fCCL[ kcclOverrunMESH ]; dp->cmdDep = meshSeq;
dp = (DBDMADescriptor*)&fCCL[ kcclOverrunDBDMA ]; dp->operation = dbdmaOp;
runDBDMA( kcclDataXfer, kcclStageBucket );
return;
}/* end startBucket */
/* The Channel Program ran to completion without problems. */
void meshSCSIController::doInterruptStageGood()
{
UInt32 totalXferLen;
/* Retrieve the total number of bytes transferred */
/* in the last data phase and reset it: */
totalXferLen = *(UInt32*)&fCCL[ kcclBatchSize ];
*(UInt32*)&fCCL[ kcclBatchSize ] = 0;
ELG( fCmd, totalXferLen, 'Good', "doInterruptStageGood" );
/* We are completing a normal command. */
/* Update the transfer count and current data pointer. */
fCmdData->results.bytesTransferred += totalXferLen;
if ( fReadAlignmentCount ) // Hack for Radar 1670626
{
fCmdData->mdp->writeBytes( fReadAlignmentIndex, &fCCL[ kcclReadBuf8 ], fReadAlignmentCount );
fReadAlignmentCount = 0;
}
if ( fFlagIncompleteDBDMA == false )
{
/* Yes, the IO is really complete: */
fCmdData->results.returnCode = kIOReturnSuccess;
fCmdData->results.scsiStatus = fCCL[ kcclStatusData ];
fCmd->setResults( &fCmdData->results );
completeCommand();
}
else
{ /* The CCL ended, but the caller expected more data. */
/* Restart the CCL. */
/* Don't regenerate arbitration or command stuff. */
updateCP( true );
runDBDMA( kcclDataXfer, kcclStageXfer );
return;
}/* end ELSE need to continue Channel Program */
/* 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 )
handleReselectionInterrupt();
else /* Nothing happening. Try to start another request. */
{
setIntMask( kMeshIntrException | kMeshIntrError ); /* Re-enable ints for reselect */
enableCommands(); /* let superclass issue another cmd */
}
return;
}/* end doInterruptStageGood */
/* 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 meshSCSIController::doInterruptStageXfer()
{
UInt32 count; /* DMA transfer count */
UInt8 phase; /* Current bus phase */
IOReturn rc;
int goAround;
count = fCmdData->results.bytesTransferred;
updateCurrentIndex();
do
{ goAround = false; /* assume loop not repeated */
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;
/* REQ is set, right? */
switch ( phase )
{
case kBusPhaseSTS:
fFlagIncompleteDBDMA = false; /* indicate no-more-data */
runDBDMA( kcclGetStatus, kcclStageStat );
break;
case kBusPhaseMSGI:
rc = DoMessageInPhase();
if ( rc == kIOReturnSuccess && fCmd )
goAround = true; /* msg ok & not disconnect */
break;
case kBusPhaseDATO:
case kBusPhaseDATI:
if ( count != fCmdData->results.bytesTransferred )
{ /* Data phase had already started: */
PAUSE( 0, phase, 'dat-', "doInterruptStageXfer - unexpected Data phase.\n" );
}
else
{ /* try starting data phase again */
runDBDMA( kcclDataXfer, kcclStageXfer );
}
break;
default:
PAUSE( 0, phase, 'Phs-', "doInterruptStageXfer - bogus phase.\n" );
break;
}/* end SWITCH on phase */
} while ( goAround );
return;
}/* end doInterruptStageXfer */
/* doInterruptStageArb - Process an anomaly during arbitration. */
void meshSCSIController::doInterruptStageArb()
{
PAUSE( 0, 0, 'Arb-', "doInterruptStageArb - Lost arbitration.\n" );
disableCommands();
rescheduleCommand( fCmd );
fCmd = 0;
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 analyzer 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" );
setSeqReg( kMeshResetMESH ); /* completes quickly */
getHBARegsAndClear( true ); /* clear cmdDone */
setSeqReg( kMeshEnableReselect );
setIntMask( kMeshIntrMask ); /* Enable Interrupts */
return; /* now wait for another reselect interrupt */
}
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 meshSCSIController::doInterruptStageSelA()
{
ELG( fCmd, 0, 'Sel-', "doInterruptStageSelA - Selection stage.\n" );
if ( fCmd )
{ fCmdData->results.returnCode = kIOReturnNoDevice;
completeCommand();
}
setSeqReg( kMeshEnableReselect );
setSeqReg( kMeshBusFreeCmd ); /* clear ATN signal MESH left on */
getHBARegsAndClear( false ); /* check MESH registers */
if ( g.shadow.mesh.exception & kMeshExcResel )
{
handleReselectionInterrupt();
}
else
{ setIntMask( kMeshIntrMask ); /* Enable Interrupts */
/* enableCommands on the Bus-Free interrupt. */
/* If we do it here, we'll get a command, start the DBDMA, */
/* and get the Bus-Free interrupt seeming to be spurious. */
}
return;
}/* end doInterruptStageSelA */
/* Process an anomaly during Message-Out phase. */
/* Target probably doing Message Reject (0x07). */
void meshSCSIController::doInterruptStageMsgO()
{
UInt8 phase;
IOReturn rc;
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask; /* phase me */
ELG( fCmd, phase, 'Mgo-', "doInterruptStageMsgO - error during msg-out phase.\n" );
switch ( phase ) /* Probably negotiating Sync */
{
case kBusPhaseMSGI:
rc = DoMessageInPhase();
if ( rc != kIOReturnSuccess )
{
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, fMsgInFlag, 'rej?', "doInterruptStageMsgO - got MsgIn.\n" );
if ( fMsgInFlag & kFlagMsgIn_Reject )
abortActiveCommand();
}
break;
default:
PAUSE( fMsgInFlag, phase, 'mgo-',
"doInterruptStageMsgO - unknown phase during MsgOut phase.\n" );
break;
}
return;
}/* end doInterruptStageMsgO */
/* doInterruptStageCmdO - Process an anomaly during command stage. */
void meshSCSIController::doInterruptStageCmdO()
{
SCSICDBInfo scsiCDB;
UInt8 phase;
IOReturn rc;
/* See if this is part of the normal AbortTag/BusDeviceReset process: */
fCmd->getCDB( &scsiCDB );
if ( scsiCDB.cdbAbortMsg )
{
setSeqReg( kMeshFlushFIFO ); /* flush the FIFO */
getHBARegsAndClear( true ); /* clear cmdDone */
ELG( fCmd, fCmd->getOriginalCmd(), 'Abo-', "doInterruptStageCmdO - Aborting." );
completeCommand(); /* complete the cmd with Abort msg */
setIntMask( kMeshIntrMask ); /* Re-enable interrupts */
enableCommands(); /* let superclass issue another command */
return;
}
/* Not aborting - something bad happened: */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask; /* phase me */
ELG( fCmd, phase, 'CMD?', "doInterruptStageCmdO - anomaly during Cmd phase.\n" );
if ( phase == kBusPhaseMSGI )
{ /* We are probably negotiating SDTR or */
/* getting rejected on a nonzero LUN. */
rc = DoMessageInPhase();
if ( rc != kIOReturnSuccess )
{
PAUSE( 0, rc, ' mi-',
"doInterruptStageCmdO - MsgIn during Cmd phase.\n" );
}
else
{ /* Message processed - where do we go from here? */
if ( !fCmd ) /* if Rejected, */
return; /* return */
phase = g.shadow.mesh.busStatus0 & kMeshPhaseMask;
switch ( phase )
{
case kBusPhaseSTS:
runDBDMA( kcclCmdoStage, kcclStageInit );
break;
case kBusPhaseMSGO:
fMsgOutPtr = &fCCL[ kcclMSGOdata ];
setupMsgO();
runDBDMA( kcclMsgoStage, kcclStageInit );
break;
case kBusPhaseCMD:
runDBDMA( kcclCmdoStage, kcclStageInit );
break;
}
}
}
else if ( phase == kBusPhaseSTS ) /* Probably Check Condition */
{ /* Perhaps block # invalid */
fFlagIncompleteDBDMA = false; /* indicate no-more-data */
runDBDMA( kcclGetStatus, 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. */
enum /// ??? header file
{
kSCSIMsgOneByteMax = 0x1Fu,
kSCSIMsgTwoByteMin = 0x20u,
kSCSIMsgTwoByteMax = 0x2Fu
};
IOReturn meshSCSIController::DoMessageInPhase()
{
register UInt8 messageByte;
UInt32 index = 0;
IOReturn ioReturn = kIOReturnSuccess;
/* 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. */
setIntMask( 0 ); /* no MESH interrupt latching */
setSeqReg( kMeshFlushFIFO ); /* Flush the FIFO */
fMsgInCount = 0;
fMsgInState = kMsgInInit;
while ( fMsgInState != kMsgInReady /* Disconnect makes fCmd */
&& ioReturn == kIOReturnSuccess ) /* go away */
{
fMESHAddr->transferCount1 = 0;
fMESHAddr->transferCount0 = 1; /* get single byte */
setSeqReg( kMeshMessageInCmd ); /* issue MsgIn */
ioReturn = waitForMesh( true ); /* wait for cmdDone */
if ( ioReturn != kIOReturnSuccess )
{
PAUSE( *(UInt32*)&fCurrentTargetLun, 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( *(UInt16*)&fCurrentTargetLun, 0, 'mgi-', "DoMessageInPhase - no message byte.\n" );
break;
}
fMsgInBuffer[ index++ ] = messageByte = fMESHAddr->xFIFO; /***** get msg byte *****/
switch ( fMsgInState )
{
case kMsgInInit: /* This is the first message byte. */
if ( (messageByte == kSCSIMsgCmdComplete)
|| (messageByte >= (UInt8)kSCSIMsgIdentify) )
{ /* This is 1-byte cmdComplete or Identify message. */
fMsgInState = kMsgInReady;
}
else if ( messageByte == kSCSIMsgExtended )
{ /* This is an extended message. The next byte has the count. */
fMsgInState = kMsgInCounting;
}
else if ( messageByte <= kSCSIMsgOneByteMax )
{ /* These are other 1-byte messages. */
fMsgInState = kMsgInReady;
}
else if ( messageByte >= kSCSIMsgTwoByteMin
&& messageByte <= kSCSIMsgTwoByteMax )
{ /* This is a two-byte message. */
/* Set the count and read the next byte. */
fMsgInState = kMsgInReading; /* Need one more */
fMsgInCount = 1;
}
else
{ /* This is an unknown message. */
fMsgInState = kMsgInReady;
}
break;
case kMsgInCounting: /* Count byte of multi-byte message: */
fMsgInCount = messageByte;
fMsgInState = kMsgInReading;
break;
case kMsgInReading: /* Body of multi-byte message: */
if ( --fMsgInCount <= 0 )
fMsgInState = kMsgInReady;
break;
default:
PAUSE( 0, 0, 'Msg-', "DoMessageInPhase - Bogus MSGI state!\n" );
fMsgInState = kMsgInReady;
break;
}/* end SWITCH on MSGI state */
if ( fMsgInState == kMsgInReady )
{
ProcessMSGI();
fMsgInState = kMsgInInit;
index = 0;
if ( fMsgInBuffer[0] == kSCSIMsgDisconnect )
ioReturn = kIOReturnIOError; /* break out of WHILE loop */
if ( fMsgInFlag & kFlagMsgIn_Reject )
{
abortActiveCommand();
break;
}
if ( fFlagReselecting )
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 ( fMsgInState != kMsgInInit )
{
PAUSE( *(UInt32*)&fCurrentTargetLun, fMsgInState, 'MGI-', "DoMessageInPhase - incomplete message.\n" );
if ( ioReturn == kIOReturnSuccess )
ioReturn = kIOReturnIOError; /* General IO error */
}
return ioReturn;
}/* end DoMessageInPhase */
/* ProcessMSGI - DoMessageInPhase has read a complete message. */
/* Process it (this will probably change our internal state). */
void meshSCSIController::ProcessMSGI()
{
/* Note that, during reselection, we may not have */
/* a current target or LUN, nor possibly a valid command */
UInt8 sdtr;
UInt8 period, offset;
ELG( fCmd, *(UInt32*)fMsgInBuffer, '<Msg', "ProcessMSGI" );
switch ( fMsgInBuffer[0] )
{
case kSCSIMsgCmdComplete:
if ( fCmd )
{
/* This command is complete. Clear interrupts and */
/* allow subsequent MESH interrupts. Then tell the */
/* MESH to wait for the target to release the bus. */
setSeqReg( kMeshEnableReselect );
setSeqReg( kMeshBusFreeCmd ); /* cause Int */
completeCommand();
}
goto exit; /* Don't exit through the SWITCH end */
case kSCSIMsgLinkedCmdComplete:
case kSCSIMsgLinkedCmdCompleteFlag:
PAUSE( *(UInt16*)&fCurrentTargetLun, 0, 'pmi-', "ProcessMSGI - linked command complete not supported.\n" );
abortActiveCommand();
break;
case kSCSIMsgNop:
break;
case kSCSIMsgRestorePointers:
if ( fCmd )
fCmdData->results.bytesTransferred = fCmdData->savedDataPosition;
break;
case kSCSIMsgSaveDataPointers:
if ( fCmd )
fCmdData->savedDataPosition = fCmdData->results.bytesTransferred;
break;
case kSCSIMsgDisconnect:
/* 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. */
fMsgInFlag |= kFlagMsgIn_Disconnect;
disconnect(); /* requeue active */
setSeqReg( kMeshEnableReselect ); /* enable reselect */
setIntMask( kMeshIntrMask ); /* Enable Ints */
setSeqReg( kMeshBusFreeCmd ); /* issue BusFree */
/* wait for Bus Free command to complete: */
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 */
/* messes up. */
g.intLevel |= kLevelLatched; /* set latched-interrupt flag */
setIntMask( 0 ); /* prevent multiple MESH ints */
break;
case kSCSIMsgRejectMsg:
ELG( *(UInt16*)&fCurrentTargetLun, fMsgOutFlag, 'Rej-', "ProcessMSGI - Reject." );
fMsgInFlag |= kFlagMsgIn_Reject;
break;
case kSCSIMsgSimpleQueueTag:
fTagType = fMsgInBuffer[0];
fTag = fMsgInBuffer[1];
ELG( 0, fTag, '=Tag', "Simple Queue Tag" );
break;
case kSCSIMsgExtended:
/* Multi-byte message, presumably Synchronous Negotiation: */
switch ( fMsgInBuffer[ 2 ] ) /* switch on the msg code byte */
{
case kSCSIMsgSyncXferReq: /* handle sync negotiation: */
/* Get period in nanoseconds */
period = fMsgInBuffer[ 3 ] * 4; /* SCSI uses 4ns granularity */
/* determine target responding or initiating? */
if ( fNegotiatingSDTR )
{
if ( fMsgInBuffer[ 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);
}
}/* end ELSE have offset ergo Synchronous */
/* OR in the offset. */
sdtr |= (fMsgInBuffer[ 4 ] << 4);
}/* end IF Target is responding to negotiation */
else /* target is initiating negotiation: */
{
fMsgOutPtr = &fCCL[ kcclMSGOdata ];
*fMsgOutPtr++ = kSCSIMsgExtended; /* 0x01 Ext Msg */
*fMsgOutPtr++ = 0x03; /* 0x03 Message Len */
*fMsgOutPtr++ = kSCSIMsgSyncXferReq; /* 0x01 SDTR code */
offset = fMsgInBuffer[ 4 ];
if ( offset == 0 ) /* Offset == 0 means async: */
{
*fMsgOutPtr++ = 0; /* clear period byte */
*fMsgOutPtr++ = 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;
}
*fMsgOutPtr++ = period / 4; /* SCSI 4ns granularity */
*fMsgOutPtr++ = offset;
sdtr = (offset << 8) | (UInt8)((period - 41) / 40);
}/* end target is negotiating Sync */
/* respond to target: */
runDBDMA( kcclMsgoStage, kcclStageInit );
}/* end ELSE target is initiating negotiation */
fMESHAddr->syncParms = sdtr;
SynchronizeIO();
fSyncParms[ fCurrentTargetLun.target ] = sdtr;
ELG( *(UInt32*)&fMsgInBuffer[0], fMsgInBuffer[4]<<24 | sdtr, 'SDTR', "ProcessMSGI - SDTR" );
break;
default:
PAUSE( *(UInt16*)&fCurrentTargetLun, fMsgInBuffer[0], 'PMi-', "ProcessMSGI - unsupported extended message.\n" );
abortActiveCommand();
break;
}/* end SWITCH on extended message code */
break;
default: /* Better be Identify with LUN: */
if ( fMsgInBuffer[0] >= kSCSIMsgIdentify )
{
fCurrentTargetLun.lun = fMsgInBuffer[0] & 0x07;
}
else
{
PAUSE( *(UInt16*)&fCurrentTargetLun, fMsgInBuffer[0], 'mi -', "ProcessMSGI - unsupported message: rejected.\n" );
abortActiveCommand();
}
}/* end SWITCH on message selection */
exit:
return;
}/* end ProcessMSGI */
/* Process a reselection interrupt. */
void meshSCSIController::handleReselectionInterrupt()
{
IOReturn ioReturn;
fFlagReselecting = true;
disableCommands(); /* tell superclass we're busy */
fMESHAddr->interrupt = kMeshIntrMask; /* clr Exc, Err regs to prevent SeqErr*/
/* 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 )
{
setSeqReg( kMeshBusFreeCmd );
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 fCurrentTargetLun. Note that, during reselection, we will */
/* have a NULL gCurrentCommand and a valid fCurrentTargetLun. */
/* If we get a valid reselection target, call the message in phase */
/* directly to read the LUN byte. */
/* @return true if successful. */
if ( fMESHAddr->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 ( getReselectionTargetID() )
{
if ( DoMessageInPhase() != kIOReturnSuccess ) /* 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: */
fTag = kInvalidTag;
ioReturn = reselectNexus();
if ( ioReturn != kIOReturnSuccess )
{ /* No untagged command, try to get a Tag. Hope that */
/* you're still in Message-In phase at this point. */
if ( DoMessageInPhase() != kIOReturnSuccess ) /* get Tag msg */
{
PAUSE( 0, 0, 'tag-', "handleReselectionInterrupt - Expected tag message.\n" );
/// need to bus device reset since it seems confused.
}
ioReturn = reselectNexus();
}
if ( ioReturn == kIOReturnSuccess )
{
ELG( fCmd,
fTag<<16 | fCurrentTargetLun.lun<<8 | fCurrentTargetLun.target,
'Resl', "handleReselectionInterrupt" );
/* If reselectNexus succeeded, fCmd is set to the command. */
/* Clear out the channel command Results and build the channel */
/* command to continue operation. */
clearCPResults();
updateCP( true ); /* Bypass arbitrate/select/command sequence */
runDBDMA( kcclReselect, kcclStageInit );
}
else
{ /* There is no associated command. */
/* Reject the reselection attempt. */
PAUSE( *(UInt16*)&fCurrentTargetLun, fTag, 'Rsl-',
"handleReselectionInterrupt - No command for reselection attempt.\n" );
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 (fCurrentTarget is now valid). */
bool meshSCSIController::getReselectionTargetID()
{
register UInt8 targetID = 0;
register UInt8 bitValue = 0; /* Suppress warning */
register UInt8 targetBits;
bool success = false;
targetBits = fMESHAddr->xFIFO; /***** Read the FIFO *****/
targetBits &= ~fInitiatorIDMask; /* 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! */
fCurrentTargetLun.target = targetID; /* Save the current target */
}
}
if ( !success )
PAUSE( targetID, targetBits, 'rsl-', "getReselectionTargetID - Expected Identify byte after reselection.\n" );
return success;
}/* end getReselectionTargetID */
IOReturn meshSCSIController::resetBus()
{
UInt8 defaultSelectionTimeout = 25; // mlj ??? fix this value
DBDMAChannelRegisters *DBDMARegs = (DBDMAChannelRegisters*)dbdmaAddr;
ELG( 0, 0, 'RstB', "resetBus" );
/* Stop the DBDMA: */
DBDMARegs->channelControl = SWAP( 0x20002000 ); // set FLUSH bit
SynchronizeIO();
DBDMARegs->channelControl = SWAP( 0x80000000 ); // clr RUN bit
SynchronizeIO();
/* Reset interrupts, the MESH Hardware Bus Adapter, and the DMA engine. */
setIntMask( 0 );
setSeqReg( kMeshResetMESH ); /* completes quickly */
getHBARegsAndClear( true ); /* clear cmdDone */
/// dbdma_reset( DBDMA_MESH_SCSI );
/* Init state variables: */
fFlagIncompleteDBDMA = false;
/* Smash all active command state (just in case): */
fCmd = NULL;
fCmdData = NULL;
fCurrentTargetLun.target = kInvalidTarget;
fCurrentTargetLun.lun = kInvalidLUN;
fMsgInState = kMsgInInit;
fMsgOutPtr = &fCCL[ kcclMSGOdata ];
fMESHAddr->busStatus1 = kMeshRst; /***** ASSERT RESET SIGNAL *****/
SynchronizeIO();
IODelay( 25 ); /* leave asserted for 25 mikes */
fMESHAddr->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 */
setSeqReg( kMeshResetMESH ); /* clear Err condition */
getHBARegsAndClear( true ); /* check regs */
fMESHAddr->selectionTimeOut = defaultSelectionTimeout;
SynchronizeIO();
enableCommands(); /* let superclass issue another command */
return kIOReturnSuccess;
}/* end resetBus */
/* 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 meshSCSIController::waitForMesh( bool clearInterrupts )
{
mach_timespec_t time, startTime, endTime, waitTime;
IOReturn ioReturn = kIOReturnSuccess;
IOGetTime( &time );
startTime = endTime = time;
waitTime.tv_sec = 0;
waitTime.tv_nsec = 250000000; // mlj - make it 250 milliseconds for SONY CD-ROM;
ADD_MACH_TIMESPEC( &endTime, &waitTime );
for ( g.shadow.mesh.interrupt = 0; g.shadow.mesh.interrupt == 0; )
{
getHBARegsAndClear( clearInterrupts );
IOGetTime( &time );
if ( time.tv_sec < endTime.tv_sec )
continue;
if ( time.tv_nsec > endTime.tv_nsec )
{ /* It took too long! We're dead. */
PAUSE( 0, 0, 'WFM-', "waitForMesh - MESH chip does not respond to command.\n" );
ioReturn = kIOReturnInternalError;
break;
}
}/* end FOR */
ELG( time.tv_sec - startTime.tv_sec, time.tv_nsec - startTime.tv_nsec, ' WFM', "waitForMesh" );
return ioReturn;
}/* end waitForMesh */
/* Send a command to the MESH chip. This may cause an interrupt. */
void meshSCSIController::setSeqReg( MeshCommand meshCommand )
{
ELG( (fMESHAddr->interruptMask<<16) | fMESHAddr->interrupt, meshCommand, '=Seq', "setSeqReg" );
if ( fMESHAddr->interruptMask & kMeshIntrCmdDone
&& meshCommand <= kMeshBusFreeCmd )
ELG( fMESHAddr->interrupt, fMESHAddr->interruptMask, 'Trig',
"setSeqReg - may trigger interrupt." );
fMESHAddr->sequence = (UInt8)meshCommand; /***** DO IT *****/
SynchronizeIO();
IODelay( 1 ); /* G3 is too fast */
return;
}/* end setSeqReg */
/* Retrieve the MESH volatile register contents, */
/* storing them in the global register shadow. */
/* @param clearInts YES to clear MESH interrupts. */
void meshSCSIController::getHBARegsAndClear( bool clearInts )
{
register MeshRegister *mesh = fMESHAddr;
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
{ // OR Exc:reselect occurs just before busFree->Seq reg.
ELG( g.shadow.mesh.interruptMask, g.shadow.mesh.sequence, 'Err-',
"getHBARegsAndClear - MESH error detected" );
mesh->interrupt = kMeshIntrError;
SynchronizeIO();
}
/* 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 meshSCSIController::setIntMask( UInt8 mask )
{
ELG( (fMESHAddr->interrupt<<16) | fMESHAddr->interruptMask, mask, 'Mask', "setIntMask" );
fMESHAddr->interruptMask = mask; /* enable whatever */
SynchronizeIO();
return;
}/* end setIntMask */
void meshSCSIController::abortActiveCommand()
{
IOReturn ioReturn;
ELG( fCmd, 0, '-AB*', "abortActiveCommand" );
if ( fCmd )
{
fCmdData->results.returnCode = kIOReturnError; // ??? use kIOAborted?
completeCommand();
}
getHBARegsAndClear( true ); /* clear possible cmdDone et al */
setIntMask( 0 ); /* Disable MESH interrupts */
fMsgInFlag = 0; /* clear kFlagMsgIn_Reject et al */
fMESHAddr->busStatus0 = kMeshAtn; /***** Raise ATN signal *****/
SynchronizeIO();
setSeqReg( kMeshBusFreeCmd ); /* clear ACK */
waitForMesh( true ); /* wait for PhaseMM */
if ( (g.shadow.mesh.busStatus0 & (kMeshPhaseMask | kMeshReq))
== (kBusPhaseMSGO | kMeshReq) )
{ /* this is what we want: */
setSeqReg( kMeshFlushFIFO ); /* Flush the FIFO */
fMESHAddr->transferCount0 = 1; /* set TC low = 1 */
fMESHAddr->transferCount1 = 0;
fMESHAddr->busStatus0 = 0; /***** clear ATN signal *****/
SynchronizeIO();
/* Issue the Message Out sending the Abort on its way. */
/* Note that this will cause an Unexpected-Disconnect. */
setSeqReg( kMeshMessageOutCmd ); /* drop ATN signal */
fMESHAddr->xFIFO = kSCSIMsgAbort; /* put out the Abort byte */
ioReturn = waitForMesh( true ); /* wait for cmdDone */
if ( ioReturn == kIOReturnSuccess )
{
setSeqReg( kMeshEnableReselect ); /* bus about to go free */
setIntMask( kMeshIntrMask ); /* Enable interrupts */
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" );
resetBus();
super::resetOccurred();
return;
}/* end abortActiveCommand */
/* IO associated with fCmd has disconnected. */
/* Place it on the disconnected command queue and */
/* enable another transaction. */
void meshSCSIController::disconnect()
{
fCmd = NULL;
fCmdData = NULL;
fCurrentTargetLun.target = kInvalidTarget;
fCurrentTargetLun.lun = kInvalidLUN;
/* Since there is no active command, the caller */
/* must configure the bus interface to wait for */
/* bus free, then allow reselection. */
return;
}/* end disconnect */
/* The specified target, LUN, and queueTag is trying to reselect. */
/* If we have a xxxCommandBuffer for this TLQ nexus on disconnectQ, */
/* remove it, make it the current fCmd, and return YES. */
/* Else return NO. A value of zero for queueTag indicates a */
/* nontagged command (zero is never used as the queue tag value for */
/* a tagged command). */
IOReturn meshSCSIController::reselectNexus()
{
fCmd = findCommandWithNexus( fCurrentTargetLun, fTag );
if ( fCmd )
{ fCmdData = (PrivCmdData*)fCmd->getCommandData();
ELG( fCmd, *(UInt16*)&fCurrentTargetLun<<16 | (UInt16)fTag, '=Nex', "reselectNexus" );
return kIOReturnSuccess;
}
else
{ if ( fTag != kInvalidTag )
PAUSE( 0, *(UInt16*)&fCurrentTargetLun<<16 | (UInt16)fTag, 'Nex-', "reselectNexus" );
}
return kIOReturnInternalError;
}/* end reselectNexus */
void meshSCSIController::updateCurrentIndex()
{
UInt32 count; /* DMA transfer count */
UInt32 length = g.shadow.mesh.FIFOCount;
UInt8 buffer[ 16 ];
UInt32 i;
/* Calculate the number of bytes xferred by this channel command. */
/* We don't trust the DBDMA residual count. */
count = *(UInt32*)&fCCL[ kcclBatchSize ]; /* Our transfer count */
if ( count == 0 ) /* If batch is empty, */
return; /* look at nothing else.*/
count -= g.shadow.mesh.transferCount1 << 8; /* MESH residual high */
count -= g.shadow.mesh.transferCount0; /* MESH residual low */
fCmdData->results.bytesTransferred += count; /* Increment data index */
*(UInt32*)&fCCL[ kcclBatchSize ] = 0; /* Clear DBDMA count */
if ( fReadAlignmentCount ) // Hack for Radar 1670626
{
fCmdData->mdp->writeBytes( fReadAlignmentIndex, &fCCL[ kcclReadBuf8 ], fReadAlignmentCount );
fReadAlignmentCount = 0;
}
/* Check the FIFO, if empty, increment the current data pointer. */
/* If there is stuff in it, we have more work to do. */
if ( g.shadow.mesh.FIFOCount ) /* If data in FIFO: */
{
/* We didn't process these bytes in the FIFO - adjust index */
fCmdData->results.bytesTransferred -= g.shadow.mesh.FIFOCount;
if ( fCmdData->isWrite ) /* If Writing: */
{
setSeqReg( kMeshFlushFIFO );
}
else /* Must be Reading: */
{ /* On a Read with data left in the FIFO, we must copy */
/* the FIFO directly into the user's data buffer: */
ELG( fCmdData->results.bytesTransferred, g.shadow.mesh.FIFOCount, 'FIFO',
"updateCurrentIndex - copy FIFO to user buffer." );
count = fCmdData->xferCount - fCmdData->results.bytesTransferred;
if ( count > length )
count = length;
/* FYI - emptying the FIFO causes cmdDone to get set. */
for ( i = 0; i < count; i++ )
buffer[ i ] = fMESHAddr->xFIFO;
fCmdData->mdp->writeBytes( fCmdData->results.bytesTransferred, buffer, count );
fCmdData->results.bytesTransferred += count;
}/* end if/ELSE must be Reading */
}/* end IF FIFO was not empty */
ELG( 0, fCmdData->results.bytesTransferred, 'UpIx', "updateCurrentIndex" );
return;
}/* end updateCurrentIndex */