|
|
Sample Programs from NeXSTEP 3.3
/* Copyright (c) 1994 NeXT Computer, Inc. All rights reserved.
*
* AMD_Chip.m - chip (53C974/79C974) specific methods for AMD SCSI driver
*
* HISTORY
* 21 Oct 94 Doug Mitchell at NeXT
* Created.
*/
#import "AMD_Chip.h"
#import "AMD_ChipPrivate.h"
#import "AMD_Private.h"
#import "AMD_x86.h"
#import "AMD_Regs.h"
#import "AMD_Types.h"
#import "AMD_ddm.h"
#import "bringup.h"
#import <driverkit/generalFuncs.h>
#import <kernserv/prototypes.h>
IONamedValue scsiMsgValues[] = {
{MSG_CMDCMPLT, "Command Complete" },
{MSG_EXTENDED, "Extended Message" },
{MSG_SAVEPTRS, "Save Pointers" },
{MSG_RESTOREPTRS, "Restore Pointers" },
{MSG_DISCONNECT, "Disconnect" },
{MSG_IDETERR, "Initiator Det Error" },
{MSG_ABORT, "Abort" },
{MSG_MSGREJECT, "Message Reject" },
{MSG_NOP, "Nop" },
{MSG_MSGPARERR, "Message parity Error" },
{0, NULL }
};
#ifdef DDM_DEBUG
IONamedValue scsiPhaseValues[] = {
{PHASE_DATAOUT, "data_out" },
{PHASE_DATAIN, "data_in" },
{PHASE_COMMAND, "command" },
{PHASE_STATUS, "status" },
{PHASE_MSGOUT, "message_out" },
{PHASE_MSGIN, "message_in" },
{0, NULL }
};
#endif DDM_DEBUG
#ifdef DEBUG
/*
* For IOFindNameForValue() and ddm's.
*/
IONamedValue scStateValues[] = {
{SCS_UNINITIALIZED, "SCS_UNINITIALIZED" },
{SCS_DISCONNECTED, "SCS_DISCONNECTED" },
{SCS_SELECTING, "SCS_SELECTING" },
{SCS_INITIATOR, "SCS_INITIATOR" },
{SCS_COMPLETING, "SCS_COMPLETING" },
{SCS_DMAING, "SCS_DMAING" },
{SCS_ACCEPTINGMSG, "SCS_ACCEPTINGMSG" },
{SCS_SENDINGMSG, "SCS_SENDINGMSG" },
{SCS_GETTINGMSG, "SCS_GETTINGMSG" },
{SCS_SENDINGCMD, "SCS_SENDINGCMD" },
{0, NULL },
};
#endif DEBUG
@implementation AMD_SCSI(Chip)
/*
* One-time-only init and probe. Returns YES if a functioning chip is
* found, else returns NO. -hwReset must be called subsequent to this
* to enable operation of the chip.
*/
- (BOOL)probeChip
{
int target;
/*
* Init sync mode to async, until we negotiate.
*/
for(target=0; target<SCSI_NTARGETS; target++) {
perTarget[target].syncXferOffset = 0;
}
return YES;
}
/*
* Reusable 53C974 init function. This includes a SCSI reset.
* Handling of ioComplete of active and disconnected commands must be done
* elsewhere. Returns non-zero on error.
*/
- (int)hwReset : (const char *)reason
{
int target;
unsigned char reg;
/*
* First of all, reset interrupts, the SCSI block, and the DMA engine.
*/
[self disableAllInterrupts];
WRITE_REG(scsiCmd, SCMD_RESET_DEVICE);
WRITE_REG(scsiCmd, SCMD_NOP);
[self dmaIdle];
/*
* Clear possible pending interrupt.
*/
READ_REG(intrStatus);
/*
* Init state variables.
*/
reselPending = 0;
scState = SCS_DISCONNECTED;
activeCmd = NULL;
currMsgInCnt = 0;
currMsgOutCnt = 0;
msgOutState = MOS_NONE;
SDTR_State = SNS_NONE;
/*
* Sync negotiation is needed after a reset.
*/
for(target=0; target<SCSI_NTARGETS; target++) {
perTarget[target].syncNegotNeeded = 1;
}
/*
* Control1....
*/
reg = CR1_RESET_INTR_DIS | CR1_PERR_ENABLE | AMD_SCSI_ID;
if(extendTiming) {
/*
* Per instance table. This slows down transfers on the
* bus.
*/
reg |= CR1_EXTEND_TIMING;
}
WRITE_REG(control1, reg);
ddm_init("control1 = 0x%x\n", reg, 2,3,4,5);
hostId = AMD_SCSI_ID;
/*
* Clock factor and select timeout.
*/
ASSERT(scsiClockRate != 0);
if(scsiClockRate < 10) {
IOLog("AMD53C974: Clock %d MHZ too low; using 10 MHz\n",
scsiClockRate);
scsiClockRate = 10;
}
if(scsiClockRate > 40) {
IOLog("AMD53C974: Clock %d MHZ too high; using 40 MHz\n",
scsiClockRate);
scsiClockRate = 40;
}
reg = AMD_CLOCK_FACTOR(scsiClockRate) & 0x7;
WRITE_REG(clockFactor, reg);
ddm_init("clockFactor %d\n", reg, 2,3,4,5);
reg = amdSelectTimeout(AMD_SELECT_TO, scsiClockRate);
WRITE_REG(scsiTimeout, reg);
ddm_init("select timeout reg 0x%x\n", reg, 2,3,4,5);
/*
* control2 - enable extended features - mainly, 24-bit transfer count.
*/
WRITE_REG(control2, CR2_ENABLE_FEAT);
/*
* control3
*/
reg = 0;
if(fastModeEnable) {
reg |= CR3_FAST_SCSI;
}
if(scsiClockRate > 25) {
reg |= CR3_FAST_CLOCK;
}
ddm_init("control3 = 0x%x\n", reg, 2,3,4,5);
WRITE_REG(control3, reg);
/*
* control4 - glitch eater, active negation. Let's not
* worry about these whizzy features just yet.
*/
WRITE_REG(control4, 0);
/*
* Go to async xfer mode for now. Sync gets enabled on a per-target
* basis in -targetContext.
*/
WRITE_REG(syncOffset, 0);
/*
* Reset SCSI bus, wait, clear possible interrupt.
*/
WRITE_REG(scsiCmd, SCMD_RESET_SCSI);
if(reason) {
IOLog("AMD53C974: Resetting SCSI bus (%s)\n", reason);
}
else {
IOLog("AMD53C974: Resetting SCSI bus\n");
}
IOSleep(AMD_SCSI_RESET_DELAY);
READ_REG(intrStatus);
ddm_init("hwReset: enabling interrupts\n", 1,2,3,4,5);
[self enableAllInterrupts];
ddm_init("hwReset: DONE\n", 1,2,3,4,5);
return 0;
}
/*
* reset SCSI bus.
*/
- (void)scsiReset
{
WRITE_REG(scsiCmd, SCMD_RESET_SCSI);
READ_REG(intrStatus);
}
/*
* Start a SCSI transaction for the specified command. ActiveCmd must be
* NULL. A return of HWS_REJECT indicates that caller may try again
* with another command; HWS_BUSY indicates a condition other than
* (activeCmd != NULL) which prevents the processing of the command.
*/
- (hwStartReturn)hwStart : (commandBuf *)cmdBuf
{
unsigned char cdb_ctrl;
IOSCSIRequest *scsiReq = cmdBuf->scsiReq;
cdb_t *cdbp = &scsiReq->cdb;
unsigned char identify_msg = 0;
unsigned char *cp;
unsigned char okToDisc;
unsigned char okToQueue;
perTargetData *perTargetPtr;
int i;
BOOL cmdQueueDisableFlag = NO;
unsigned char selectCmd;
ddm_chip("hwStart cmdBuf = 0x%x opcode %s\n", cmdBuf,
IOFindNameForValue(cdbp->cdb_opcode, IOSCSIOpcodeStrings),
3,4,5);
ASSERT(activeCmd == NULL);
/*
* Currently, the only reason we return HWS_BUSY is if we have
* a reselect pending.
*/
if(reselPending) {
queue_enter(&pendingQ, cmdBuf, commandBuf *, link);
return HWS_BUSY;
}
ASSERT(scState == SCS_DISCONNECTED);
/*
* Initialize driver return values and state machine.
*/
cmdBuf->currentByteCount = cmdBuf->savedByteCount =
scsiReq->maxTransfer;
scsiReq->bytesTransferred = 0;
cmdBuf->savedPtr = cmdBuf->currentPtr = (vm_offset_t)cmdBuf->buffer;
scsiReq->driverStatus = SR_IOST_INVALID;
scsiReq->totalTime = 0ULL;
scsiReq->latentTime = 0ULL;
/*
* Figure out what kind of cdb we've been given and grab the ctrl byte.
*/
switch (SCSI_OPGROUP(cdbp->cdb_opcode)) {
case OPGROUP_0:
cmdBuf->cdbLength = sizeof(cdb_6_t);
cdb_ctrl = cdbp->cdb_c6.c6_ctrl;
break;
case OPGROUP_1:
case OPGROUP_2:
cmdBuf->cdbLength = sizeof(cdb_10_t);
cdb_ctrl = cdbp->cdb_c10.c10_ctrl;
break;
case OPGROUP_5:
cmdBuf->cdbLength = sizeof(cdb_12_t);
cdb_ctrl = cdbp->cdb_c12.c12_ctrl;
break;
case OPGROUP_6:
cmdBuf->cdbLength = (scsiReq->cdbLength ?
scsiReq->cdbLength : sizeof (struct cdb_6));
cdb_ctrl = 0;
break;
case OPGROUP_7:
cmdBuf->cdbLength = (scsiReq->cdbLength ?
scsiReq->cdbLength : sizeof (struct cdb_10));
cdb_ctrl = 0;
break;
default:
goto abortReq;
}
ddm_chip("cdbLength = %d\n", cmdBuf->cdbLength, 2,3,4,5);
/*
* Do a little command snooping.
*/
perTargetPtr = &perTarget[scsiReq->target];
switch(cdbp->cdb_opcode) {
case C6OP_INQUIRY:
/*
* The first command SCSIDisk sends us is an Inquiry command.
* 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(activeArray[scsiReq->target][scsiReq->lun] == 0) {
cmdQueueDisableFlag = YES;
}
break;
case C6OP_REQSENSE:
/*
* Always force sync renegotiation on this one to
* catch independent target power cycles.
*/
if(SYNC_RENEGOT_ON_REQ_SENSE) {
perTargetPtr->syncNegotNeeded = 1;
}
break;
}
/*
* Avoid command queueing if if we're going to do sync
* negotiation.
* FIXME - this might be illegal - what if we're doing a request
* sense in response to a legitimate error, and there are
* tagged commands pending?
*/
if(perTargetPtr->syncNegotNeeded &&
!perTargetPtr->syncDisable &&
syncModeEnable) {
cmdQueueDisableFlag = YES;
SDTR_State = SNS_HOST_INIT_NEEDED;
ddm_chip("hwStart: entering SNS_HOST_INIT_NEEDED state\n",
1,2,3,4,5);
}
else {
SDTR_State = SNS_NONE;
}
/*
* Determine from myriad sources whether or not it's OK to
* disconnect and to use command queueing.
*/
okToQueue = cmdQueueEnable && // global per driver
!scsiReq->cmdQueueDisable && // per I/O
!perTargetPtr->cmdQueueDisable && // per target
!cmdQueueDisableFlag; // inquiry hack
okToDisc = ([self numReserved] > // > 1 target on bus
(1 + SCSI_NLUNS)) ||
okToQueue; // hope to do cmd q'ing
#if FORCE_DISCONNECTS
okToDisc = 1;
#else FORCE_DISCONNECTS
if(!scsiReq->disconnect) {
/*
* This overrides everything...
*/
okToQueue = okToDisc = 0;
}
#endif FORCE_DISCONNECTS
cmdBuf->discEnable = okToDisc;
if(okToQueue) {
/*
* Avoid using tag QUEUE_TAG_NONTAGGED...
*/
cmdBuf->queueTag = nextQueueTag;
if(++nextQueueTag == QUEUE_TAG_NONTAGGED) {
nextQueueTag++;
}
}
else {
cmdBuf->queueTag = QUEUE_TAG_NONTAGGED;
}
/*
* Make sure nothing unreasonable has been asked of us.
*/
if((cdb_ctrl & CTRL_LINKFLAG) != CTRL_NOLINK) {
ddm_err("Linked CDB (Unimplemented)\n",
1,2,3,4,5);
goto abortReq;
}
/*
* OK, this command is hot.
*/
[self activateCommand:cmdBuf];
scState = SCS_SELECTING;
msgOutState = MOS_NONE;
bzero(currMsgIn, AMD_MSG_SIZE);
bzero(currMsgOut, AMD_MSG_SIZE);
currMsgInCnt = 0;
currMsgOutCnt = 0;
/*
* Load per-target context.
*/
[self targetContext:scsiReq->target];
/*
* set target bus id
* punch message(s), optional cdb into fifo
* write appropriate select command
*/
ddm_chip("hwStart: opcode 0x%x targ %d lun %d maxTransfer 0x%x\n",
cdbp->cdb_opcode, scsiReq->target, scsiReq->lun,
scsiReq->maxTransfer, 5);
WRITE_REG(scsiCmd, SCMD_CLEAR_FIFO);
WRITE_REG(scsiDestID, scsiReq->target);
identify_msg = MSG_IDENTIFYMASK | (scsiReq->lun & MSG_ID_LUNMASK);
if(okToDisc) {
identify_msg |= MSG_ID_DISCONN;
}
WRITE_REG(scsiFifo, identify_msg);
/*
* Note this logic assumes that queue tag and SDTR messages are
* mutually exclusive...
*/
if(SDTR_State == SNS_HOST_INIT_NEEDED) {
selectCmd = SCMD_SELECT_ATN_STOP;
}
else {
if(okToQueue) {
WRITE_REG(scsiFifo, MSG_SIMPLE_QUEUE_TAG);
WRITE_REG(scsiFifo, cmdBuf->queueTag);
/*
* Save these in currMsgOut[] in case
* the target rejects this message.
*/
currMsgOut[0] = MSG_SIMPLE_QUEUE_TAG;
currMsgOut[1] = cmdBuf->queueTag;
currMsgOutCnt = 2;
}
cp = (u_char *)cdbp;
for(i=0; i<cmdBuf->cdbLength; i++) {
WRITE_REG(scsiFifo, *cp++);
}
if(okToQueue) {
selectCmd = SCMD_SELECT_ATN_3;
}
else {
selectCmd = SCMD_SELECT_ATN;
}
}
WRITE_REG(scsiCmd, selectCmd);
IOGetTimestamp(&cmdBuf->startTime);
return HWS_OK;
abortReq:
scsiReq->driverStatus = SR_IOST_CMDREJ;
[self ioComplete:cmdBuf];
return HWS_REJECT;
}
/*
* SCSI device interrupt handler.
*/
- (void)hwInterrupt
{
ddm_chip("hwInterrupt: activeCmd 0x%x\n", activeCmd, 2,3,4,5);
switch([self scsiInterruptPending]) {
case SINT_NONE:
/*
* Must be another device....
*/
[self enableAllInterrupts];
return;
case SINT_DEVICE:
case SINT_DMA:
break;
default:
/*
* What do we do now, batman?
*/
[self hwAbort:SR_IOST_HW reason:"Bad Interrupt Received"];
return;
}
goAgain:
/*
* Save interrupt state.
*/
saveStatus = READ_REG(scsiStat);
saveSeqStep = READ_REG(internState);
saveIntrStatus = READ_REG(intrStatus);
ddm_chip(" status 0x%x intstatus 0x%x scState %s\n",
saveStatus, saveIntrStatus,
IOFindNameForValue(scState, scStateValues), 4,5);
if((saveStatus & SS_ILLEGALOP) || (saveIntrStatus & IS_ILLEGALCMD)) {
/*
* Software screwup. Start over from scratch.
*/
IOLog("AMD53C974: hardware command reject\n");
[self hwAbort:SR_IOST_INT reason:"Hardware Command Reject"];
return;
}
/*
* OK, grind thru the state machine.
*/
switch(scState) {
case SCS_DISCONNECTED:
[self fsmDisconnected];
break;
case SCS_SELECTING:
[self fsmSelecting];
break;
case SCS_INITIATOR:
[self fsmInitiator];
break;
case SCS_COMPLETING:
[self fsmCompleting];
break;
case SCS_DMAING:
[self fsmDMAing];
break;
case SCS_ACCEPTINGMSG:
[self fsmAcceptingMsg];
break;
case SCS_SENDINGMSG:
[self fsmSendingMsg];
break;
case SCS_GETTINGMSG:
[self fsmGettingMsg];
break;
case SCS_SENDINGCMD:
[self fsmSendingCmd];
break;
default:
IOPanic("AMD53C974: Bad scState");
} /* switch scState */
if ((scState != SCS_DISCONNECTED) &&
(saveIntrStatus & IS_DISCONNECT)) {
/*
* the target just up and went away. This is a catch-all
* trap for any unexpected disconnect.
*/
ddm_err("hwInterrupt: target disconnected\n", 1,2,3,4,5);
scState = SCS_DISCONNECTED;
if(activeCmd != NULL) {
activeCmd->scsiReq->driverStatus = SR_IOST_TABT;
[self ioComplete:activeCmd];
activeCmd = NULL;
}
}
/*
* Handle a SCSI Phase change if necessary.
*/
if (scState == SCS_INITIATOR)
[self fsmPhaseChange];
#ifdef DEBUG
else {
ddm_chip("hwInterrupt #2: scState %s\n",
IOFindNameForValue(scState, scStateValues),
2,3,4,5);
}
#endif DEBUG
/*
* If we're off the bus, enable reselection at chip level.
*/
if (scState == SCS_DISCONNECTED) {
ddm_chip("hwInterrupt: enabling reselection\n", 1,2,3,4,5);
WRITE_REG(scsiCmd, SCMD_ENABLE_SELECT);
}
/*
* If another SCSI interrupt is pending, go for it again (avoiding
* an unnecessary enableInterrupt and msg_receive()).
*/
switch([self scsiInterruptPending]) {
case SINT_DEVICE:
#if INTR_LATENCY_TEST
ddm_chip("hwInterrupt: INTR TRUE; EXITING FOR MEASUREMENT\n",
1,2,3,4,5);
break;
#else INTR_LATENCY_TEST
ddm_chip("hwInterrupt: going again without enabling "
"interrupt\n", 1,2,3,4,5);
goto goAgain;
#endif INTR_LATENCY_TEST
default:
break;
}
[self enableAllInterrupts];
/*
* One more thing - if we're still disconnected, enable processing
* of new commands.
*/
if(scState == SCS_DISCONNECTED)
[self busFree];
ddm_chip("hwInterrupt: DONE; scState %s\n",
IOFindNameForValue(scState, scStateValues), 2,3,4,5);
}
- (void)logRegs
{
#if DEBUG
unsigned char cs, cis;
unsigned char fifoDepth;
unsigned scsiXfrCnt;
unsigned value;
IOLog("*** saveStatus 0x%x saveIntrStatus 0x%x\n",
saveStatus, saveIntrStatus);
IOLog("*** scState = %s scsiCmd = 0x%x\n",
IOFindNameForValue(scState, scStateValues),
READ_REG(scsiCmd));
cs = READ_REG(scsiStat);
cis = READ_REG(intrStatus);
IOLog("*** current status 0x%x current intrStatus 0x%x\n", cs, cis);
IOLog("*** syncOffset %d syncPeriod 0x%x\n",
syncOffsetShadow, syncPeriodShadow);
fifoDepth = READ_REG(currFifoState) & FS_FIFO_LEVEL_MASK;
scsiXfrCnt = READ_REG(currXfrCntLow);
value = READ_REG(currXfrCntMid);
scsiXfrCnt += (value << 8);
value = READ_REG(currXfrCntHi);
scsiXfrCnt += (value << 16);
IOLog("*** fifoDepth %d scsiXfrCnt 0x%x\n", fifoDepth, scsiXfrCnt);
#endif DEBUG
}
@end /* AMD_SCSI(Chip) */
/* end of AMD_Chip.m */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.