;---------------------------------------------------------------------------------------------- ; ; File: SCSIMgrNew.a ; ; Written by: Jerry Katzung ; ; Copyright: © 1989-1992 by Apple Computer, Inc. All rights reserved. ; ; Change History (most recent first): ; ; 11/3/92 SWC Changed SCSIEqu.a->SCSI.a. ; 6/2/92 kc Revert to ; 6/1/92 CS Roll more cyclone stuff in. ; 5/17/92 kc Include PowerPrivEqu.a. The powerCntl equ was moved inside the ; pmCommandRec record. ; <8> 5/1/92 JSM Get rid of conditionals: donÕt use not forROM, this file is only ; used in ROM builds, donÕt use hasGDU, isUniversal, or onMacXX ; style conditionals, left padForOverPatch conditionals for now. ; <7> 1/15/92 KC Fixed "short branch to the next instruction changed to NOP" ; warning. ; <6> 9/16/91 JSM Cleanup header. ; <5> 9/21/90 BG Removed <4>. 040s are behaving more reliably now. ; <4> 7/20/90 BG Added EclipseNOPs for flakey 040s. ; <3> 5/16/90 MSH Added hasPowerControls to hcmac conditional. ; <2> 1/11/90 CCH Added include of ÒHardwarePrivateEqu.aÓ. ; <1.5> 9/11/89 jwk NEEDED FOR F19 - Support for Disc during DMA, Mac SE and Mac ; Plus support ; <1.4> 7/24/89 jwk NEEDED FOR AURORA - Unlink w/ ioQElSize to fix alt. bus call bug ; <1.3> 7/17/89 jwk NEEDED FOR AURORA - Fixed "Command" routine to use scsiCmdOffs ; ptr ; <1.2> 7/15/89 jwk NEEDED FOR AURORA - Added code review changes, F19 support ; routines ; <1.1> 6/29/89 jwk NEEDED FOR AURORA - Fixed Tape Drive and Slow Write timing bugs ; <1.0> 6/13/89 jwk Reorganizing SCSI sources to be Universal-ROM friendly ; ;---------------------------------------------------------------------------------------------- ; ; Old comments are saved for historical purposes in the SCSIMgrOld.a file. ; ;---------------------------------------------------------------------------------------------- BLANKS ON STRING ASIS PRINT OFF IF (&TYPE('dbSymbols') = 'UNDEFINED') THEN dbSymbols EQU 0 ; for debugging purposes ENDIF IF (&TYPE('phaseLogging') = 'UNDEFINED') THEN phaseLogging EQU 0 ENDIF LOAD 'StandardEqu.d' INCLUDE 'HardwarePrivateEqu.a' INCLUDE 'SCSI.a' INCLUDE 'SCSIPriv.a' INCLUDE 'PowerPrivEqu.a' PRINT ON SCSINew PROC EXPORT WITH scsiPB,scsiPrivPB, scsiGlobalRecord, dcInstr, machSpecRecord EXPORT ClearBus EXPORT DataDMA ; EXPORT DataIO, Command, StatusPhase, ResetBus, MsgOut, MsgIn EXPORT msgCmdComplete, msgSaveDataPtr, msgRestorePtrs, msgDisconnect EXPORT msgIdentifyIn, msgMsgRejIn, msgInvalidIn, msgLCCF EXPORT msgInvalidOut, msgIdentifyOut, msgNoOp, msgMsgRejOut, msgKillIO EXPORT msgBusDevRst ; EXPORT Preflight, EnDequeue EXPORT Message, SCSIDT, Find, Setup, TimeTask EXPORT ClearState, SetTimer, LogError ; EXPORT NewSCSITrap EXPORT DoRequestIO, DoKillIO, DoBusInfo, AltBusCall WITH scsiPB,scsiPrivPB, scsiGlobalRecord, dcInstr, PmgrRec ;------------------------------------------------------------- ; ; The new and improved SCSI Manager. ; ; A4 is set up by StdEntry and contains a pointer to the SCSI Manager global ; area in the system heap. ; ; ; Register convention: ; ; Old SCSI Mgr New SCSI Mgr ; ; a0 - scratch scratch ; a1 - scratch scratch ; a2 - buffer pointer buffer pointer ; a3 - SCSI read base pointer SCSI read base pointer ; a4 - SCSI Mgr globals pointer SCSI Mgr globals pointer ; a5 - n/a active request pointer ; ; d0 - scratch (result) scratch ; d1 - scratch (bytes left) scratch ; d2 - scratch (count) scratch ; d3 - scratch (6) scratch ; d4 - scratch (data transfer flags) data transfer flags ; d5 - scratch data-chaining instruction ptr ; d6 - scratch/patches' SCSI write offset SCSI write offset for patches ; d7 - zero register zero register ; ; ; NewSCSITrap ; ; Called by: OS trap dispatcher. ; ; Calls: Preflight, ResetBus, Message ; ; On entry: a0 - pointer to SCSI request parameter block ; d0 - routine selector ; ; On exit: d0 - result code (OSErr) ; ; Function: This routine is responsible for implementing the various new SCSI Mgr routines. ; The new SCSI Mgr routines are dispatched OS traps, with a selector passed in d0. ; It checks the parameter block, enqueues the request, and starts processing it if ; it is the only request in the queue. Returns an OSErr to describe what happened. ; NewSCSITrap: movem.l scsiRegs,-(sp) ; save registers movea.l SCSIGlobals,a4 ; get pointer to SCSI Manager globals NewSCSICommon ; move.l a0,a5 ; set up pointer to the request move.l base5380(a4),a3 ; SCSI chip read base address moveq.l #0,zeroReg ; set up the "zero register" @checkBusID tst.w scsiBus(a5) ; is it for the motherboard ? beq.s @checkCall ; keep going, it's for us @sendItOff movea.l jvAltBusCall(a4),a0 ; get addr of alternate SCSI bus call jsr (a0) ; pass the call off to the other SCSI bus bra.s @exit ; let's get out of here @checkCall cmpi.w #scsiNewSCSIMax,d0 ; valid selector ? bls.s @goodSelector ; if so, call the appropriate routine @SCSIOops move.w #scsiBadPBErr,d0 ; tell driver that call is not implemented bra.s @exit ; return the error message @goodSelector lea.l newSCSIDisp(a4),a0 ; point to base of NewSCSI dispatch table movea.l 0(a0,d0.w*4),a0 ; get address of trap code jsr (a0) ; call the appropriate trap code @exit movem.l (sp)+,scsiRegs ; restore registers (d0 is not restored) rts ; ; Send a synchronous control call to the other SCSI bus. ; Map any errors to "scsiBadPB". ; AltBusCall: lea.l -ioQElSize(sp),sp ; reserve storage for control PB movea.l sp,a0 ; point to Control PB move.w scsiBus(a5),ioRefNum(a0) ; SCSI bus ID is the driver refnum move.w d0,csCode(a0) ; pass NewSCSI selector to driver move.l a5,csParam+4(a0) ; pass pointer to scsiPB in Control PB _Control ; send it off synchronously lea.l ioQElSize(sp),sp ; clean up the stack beq.s @exit ; if no error, then we can leave move.w #scsiBadPBErr,d0 ; map all errors into "bad PB" errors @exit rts ; ; Perform a SCSIRequestIO call ; DoRequestIO: movea.l jvPreflight(a4),a0 ; get address of routine jsr (a0) ; preflight the parameter block bne.s @exit ; get out @goodPB moveq.l #scNewRequest,d0 ; we've got a new request to process move.l a5,d1 ; ptr to parameter block movea.l jvMessage(a4),a0 ; addr of deferred task messaging routine jsr (a0) ; send the message moveq.l #noErr,d0 ; queueing was successful move.l scsiMgrFlags(a5),d1 ; get SCSI flags btst.l #scsiBAsync,d1 ; is the async bit set ? bne.s @exit ; if async, then exit @wait tst.w scsiResult(a5) ; wait for a change to negative or zero bgt.s @wait ; if greater than zero, keep waiting @exit rts ; ; Perform a SCSIKillIO call ; Could be graceful here; instead, reset the SCSI bus, killing all aboard. ; DoKillio: movea.l jvResetBus(a4),a0 ; addr of "reset SCSI bus" routine jsr (a0) ; reset the bus, and kill all I/O requests moveq.l #noErr,d0 ; it worked (imagine that) rts ; we're done ; ; Perform a SCSIBusInfo call ; DoBusInfo: moveq.l #noErr,d0 ; assume no error move.w scsiVersion(a5),d1 ; get the selector bne.s @check1 ; if nonzero, check if it is a 1 move.l zeroReg,d1 ; clear upper bits move.b state2(a4),d1 ; return our SCSI Mgr revision level move.l d1,scsiQLink(a5) ; return the result rts ; we're done @check1 subq.l #1,d1 ; what's our byte-to-byte timeout ? bne.s @check2 ; check if it is a 2 move.l busTO(a4),scsiQLink(a5) ; return the result rts ; we're done @check2 subq.l #1,d1 ; do we have a SCSI DMA channel ? bne.s @SCSIOops ; if not, we have an unimplemented selector move.l G_Reserved0(a4),d1 ; get machine info (including SCSI DMA) andi.l #1< beq.s @DMArslt ; return zero if no DMA moveq.l #1,d1 ; return one if DMA @DMArslt move.l d1,scsiQLink(a5) ; return the result rts ; we're out of here @SCSIOops move.w #scsiBadPBErr,d0 ; tell driver that call is not implemented rts ; return the result ; ; ResetBus - Reset the SCSI bus and kill all the requests in the queue. ; ; Calls: ClearState ; ResetBus: move.w sr,-(sp) ori.w #HiIntMask,sr ; no more interrupts move.b #iRST,sICR+WrOffs(a3) ; assert SCSI Reset line lea.l scsiQHead(a4),a5 ; load pointer to first request in the queue lea.l dummy(a4),a1 ; point to dummy parameter block move.w #scsiBusResetErr,d0 ; report that SCSI bus was reset movea.l jvClearState(a4),a0 ; addr of clear request routine @loop movea.l scsiQLink(a5),a5 ; point to the next request cmp.l a5,zeroReg ; are we done ? beq.s @delay1 ; if so, delay for good measure cmpa.l a1,a5 ; are we pointing at the dummy PB ? beq.s @killOldPB ; if so, kill it differently jsr (a0) ; kill off the new SCSI Mgr PB bra.s @loop ; keep going @killOldPB moveq.l #scsiOldSCSIErr,d1 ; pretend old SCSI Mgr is killing this request exg.l d0,d1 ; switch error messages jsr (a0) ; kill the request exg.l d0,d1 ; restore the error message bra.s @loop ; keep going @delay1 move.w TimeDBRA,d0 ; DBRAs/ms. lsr.w #2,d0 ; 1 ms./4 = 250 us. @delay dbra d0,@delay ; twiddle our thumbs... move.b zeroReg,sICR+WrOffs(a3) ; de-assert *RST movea.l jvClearIRQ(a4),a0 ; routine to clear interrupt request jsr (a0) ; clear 5380's IRQ move.w (sp)+,sr ; restore interrupts move.w TimeDBRA,d0 ; DBRAs/ms. mulu.w #500,d0 ; delay 500 msec for recovery move.l d0,d1 swap d1 ; get high word for dbra @2 dbra d0,@2 ; low word dbra d1,@2 ; high word btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? <2.8> beq.s @DMAdone ; if not, skip it <2.8> move.l #iINTREN,sDCTRL(a3) ; turn off hhsk @DMAdone ; <2.8> move.b zeroReg,sMR+WrOffs(a3) ; make certain of SCSI mode rts ; ; EnDequeue ; ; Called by: ClearState, Message, SCSIGet ; ; On entry: a1 - ptr to "scsiPB" to enqueue/dequeue ; d0.l - selector: ; enqueueFlag (bit 31): 1 = enqueue, 0 = dequeue ; values: ; enqNormal = Enqueue at end of queue ; enqFront = Enqueue at front of queue ; deqNormal = Dequeue 1 request (linked commands are elevated) ; deqLinked = Dequeue linked cmds completely (to cleanly kill linked cmds) ; ; Function: Removes a SCSI request from the SCSI request queue ; EnDequeue: move.w sr,-(sp) ; preserve status ori.w #HiIntMask,sr ; disable interrupts for exclusion btst.l #enqueueFlag,d0 ; is this an enqueue request ? bne.s @enqueue ; if so, enqueue the PB ; ; dequeue the request ; @dequeue movem.l a2-a3,-(sp) ; preserve work registers move.l scsiQHead(a4),a2 ; start searching at the head move.l a2,a3 @deqLoop cmp.l a3,a1 ; is this the one? beq.s @delete ; if so, go delete it @nextElement move.l a3,a2 ; update previous pointer move.l QLink(a2),a3 ; follow the link cmp.l scsiQTail(a4),a2 ; have we reached the tail? bne.s @deqLoop @noElement bra.s @deqDone1 ; we don't return an error @delete cmp.l a2,a3 ; deleting the first element? bne.s @delGeneral ; if not, perform general case delete @first ; special case for first element in the queue move.l QLink(a2),scsiQHead(a4) ; point head ptr to next element bne.s @deqDone ; was it the only element? clr.l scsiQTail(a4) ; if so, clear the tail too bra.s @deqDone ; we're done @delGeneral ; delete for the general case move.l QLink(a3),QLink(a2) ; unlink it from the chain cmp.l scsiQTail(a4),a1 ; was it the tail? bne.s @deqDone ; if not we're done move.l a2,scsiQTail(a4) ; update tail pointer @deqDone clr.l scsiQLink(a1) ; clear the link in the element @deqDone1 movem.l (sp)+,a2-a3 ; restore work registers bra.s @exit ; we're finished ; ; enqueue the request ; @enqueue movem.l d1/a2-a3,-(sp) ; save registers movea.l a1,a3 ; point to request @linkLoop move.l scsiLinkCmd(a3),d1 ; is there a linked command ? beq.s @noLink ; if not, exit move.l d1,scsiQLink(a3) ; point this PB at the linked PB move.l d1,a3 ; point to the linked PB bra.s @linkLoop ; keep going @noLink move.l scsiQHead(a4),d1 ; anything in the queue ? (Used later) bne.s @nonempty ; if so, handle differently @empty move.l a1,scsiQHead(a4) ; empty queue - point head to new element move.l a3,scsiQTail(a4) ; point tail to new element bra.s @enqDone ; we're out of here @nonempty cmpi.w #enqNormal,d0 ; enqueue at the end ? bne.s @front ; no - enqueue at the front @normal move.l scsiQTail(a4),a2 ; get ptr to old QTail move.l a1,scsiQLink(a2) ; update its link move.l a3,scsiQTail(a4) ; update QTail bra.s @enqDone ; we're done @front move.l d1,scsiQLink(a3) ; point last linked PB at old head-of-queue (set above) move.l a1,scsiQHead(a4) ; point queue header at new head-of-queue @enqDone movem.l (sp)+,d1/a2-a3 ; restore registers @exit move.w (sp)+,sr ; restore status and go home moveq.l #noErr,d0 ; return noErr rts ; ; LogError - Record a SCSI error in the parameter block ; ; On entry: a4 - ptr to the SCSI Mgr globals ; a5 - ptr to the active PB ; d0.w - result code for the parameter block ; LogError: cmp.l a5,zeroReg ; do we have an active request ? beq.s @exit ; if not, nothing to record movea.l scsiPrivate(a5),a0 ; get private storage cmp.l a0,zeroReg ; so we have private storage ? beq.s @exit ; if not, nothing to record tst.w scsiTempErr(a0) ; have we stored an error message ? bne.s @exit ; if so, don't clobber it move.w d0,scsiTempErr(a0) ; save the error message @exit ; rts ; we're done ; ; ClearState - Mark the SCSI Mgr as free ; ; Called by: SCSIRequestIO, SCSIKillIO, SCSIIntHnd, MsgIn, Data ; ; Calls: EnDequeue ; ; On entry: a3 - ptr to SCSI base read address ; a4 - ptr to the SCSI Mgr globals ; a5 - ptr to the PB to be removed ; d0.w - result code for the parameter block ; ; On exit: d0.w - result ; ; Function: ; This routine is the standard exit routine for a completed request, as well as a failed ; request. ; It removes the request from the SCSI request queue, sets the result field, ; and then calls the completion routine (if any). ; Additionally, the "timeout" global is reset by walking the SCSI request queue. ; ClearState: movem.l intrRegs,-(sp) ; save registers ; ; If the error in d0 is "oldSCSIMgrErr", then ClearState was called ; by the old SCSI Mgr. Dequeue the dummy PB, and check if the SCSI bus is ok. ; cmpi.w #scsiOldSCSIErr,d0 ; were we called by the old SCSI Mgr ? bne.s @newSCSIMgr ; if not, we were called by the new SCSI Mgr @oldSCSIMgr move.l scsiQHead(a4),d1 ; get head of the queue beq.s @clearGState ; if no dummy element, just clear G_State moveq.l #deqNormal,d0 ; dequeue the dummy PB normally lea.l dummy(a4),a1 ; get address of dummy parameter block movea.l jvEnDequeue(a4),a0 ; blow away the dummy parameter block jsr (a0) ; use the standard routine @clearGState move.b zeroReg,G_State(a4) ; clear out SCSI Mgr semaphore tst.b discID(a4) ; any disconnected ID's ? beq.w @exit ; if not, we can leave Reselection interrupts disabled move.b G_ID(a4),sSER+wrOffs(a3) ; enable Reselection interrupts bra.w @exit ; bus can't be cleared under old SCSI Mgr ; ; If we get here, ClearState was called by the new SCSI Mgr, so a5 is valid. ; If a5 is zero, then we got a Reselect from a device we don't know about. ; @newSCSIMgr move.l zeroReg,activeReq(a4) ; no currently active request move.l a5,a1 ; point to SCSI request to dequeue move.l d0,d2 ; hold onto the result code moveq.l #deqNormal,d0 ; dequeue this request normally movea.l jvEnDequeue(a4),a0 ; addr of routine to dequeue request jsr (a0) ; dequeue the request @releasePrivates move.l scsiPrivate(a5),d0 ; point at the SCSI Mgr private storage beq.s @cleanupGlobals ; no private storage for this request move.l d0,a0 ; valid pointer move.w scsiTempErr(a0),d0 ; do we have a pending error ? beq.s @rel1 ; if not, then use the error in d2 move.w d0,d2 ; return original error message @rel1 move.l zeroReg,scsiPrivate(a5) ; release the SCSI Mgr private storage move.l zeroReg,scsiFlags(a0) ; clear "scsiFlags","State","Flag1", and "Flag2" move.l zeroReg,scsiTime(a0) move.l zeroReg,scsiCmdOffs(a0) move.l zeroReg,scsiTempErr(a0) ; clear temporary error and filler fields move.l zeroReg,scsiDCSaved(a0) move.l zeroReg,scsiDCOffs(a0) move.l zeroReg,scsiMoveOffs(a0) tst.l nextPrivate(a4) ; check for a free private storage record bne.s @cleanupGlobals ; there is already a free record move.l a0,nextPrivate(a4) ; this record is free @cleanupGlobals move.w d2,scsiResult(a5) ; save the result code lea.l discLUN(a4),a1 ; point to disconnected LUN table move.l zeroReg,d2 ; clear offset into LUN table move.b scsiReqID(a5),d2 ; get offset into LUN table move.b scsiReqLUN(a5),d1 ; this LUN is available now bclr.b d1,0(a1,d2.l) ; requests for this LUN can be run tst.b 0(a1,d2.l) ; is anybody disconnected at this ID ? bne.s @completion ; if so, continue bclr.b d2,discID(a4) ; this entire ID is available @completion move.l scsiMgrFlags(a5),d0 ; get the SCSI flags btst.l #scsiBImmed,d0 ; is it a page-fault request ? beq.s @chkAsync ; if not, continue move.l zeroReg,pageFault(a4) ; clear page fault indicator @chkAsync btst.l #scsiBAsync,d0 ; is it an asynchronous call ? beq.s @newTimeOut ; synchronous - "scsiCompletion" is undefined move.l scsiCompletion(a5),d0 ; is there a valid completion routine ? beq.s @newTimeOut ; if not, calculate new earliest timeout movem.l intrRegs,-(sp) ; save all registers btst.b #sDeferUserFn,G_Reserved0+3(a4) ; is the VM-friendly call implemented ? bne.s @DUF ; if so, defer completion routine cleanly @noDUF move.l d0,a1 ; get completion routine address move.l a5,a0 ; point a0 at parameter block jsr (a1) ; simply jump to completion routine bra.s @afterCompletion ; we've called the completion routine @DUF move.l d0,a0 ; pointer to completion routine move.l a5,d0 ; pointer to parameter block nop ; trap not defined ??? ; _DeferUserFn ; post the completion (can't recover from an error) @afterCompletion ; movem.l (sp)+,intrRegs ; restore all registers @newTimeOut moveq.l #-1,d0 ; start out with an "infinite" timeout lea.l scsiQHead(a4),a1 ; get pointer to first parameter block @loop movea.l scsiQLink(a1),a1 ; get the next parameter block cmp.l a1,zeroReg ; are we done ? beq.s @setTimeOut ; if so, save timeout and make sure the bus is free move.l scsiPrivate(a1),d1 ; is this request in progress ? beq.s @loop ; if not, check next parameter block move.l d1,a0 ; point to private storage for this request cmp.l scsiTime(a0),d0 ; is this timeout earlier ? blo.s @loop ; if not, check the next parameter block move.l scsiTime(a0),d0 ; if earlier, save it bra.s @loop ; check next parameter block @setTimeOut move.l d0,timeout(a4) ; save the new timeout @exit movem.l (sp)+,intrRegs ; restore registers rts ; ; ClearBus - Clean up the bus, if necessary. ; ; Called by: SCSIRequestIO, SCSIKillIO, SCSIIntHnd, MsgIn, Data ; ; Calls: ResetBus ; ; On entry: a3 - ptr to SCSI base read address ; a4 - ptr to the SCSI Mgr globals ; a5 - ptr to the PB to be removed (NIL = reselected by a timed-out ID) ; d0.w - result code for the parameter block ; ; On exit: d0.w - result ; ; Function: ; This routine attempts to clean up the bus in three stages: first, an attempt is made ; to send an "Abort" message to the device; second, a "Bus Device Reset" message is ; sent to the device; finally, the SCSI bus is reset. ; (If the parameter block pointer is nil, then a device is on the bus, but the device's ; request has timed out. This is handled by aborting the rest of the transfer, since ; the original request has already failed.) ; ; ClearBus: movem.l intrRegs,-(sp) ; save registers @chkBus ; ; At this point, the bus is probably hosed. It is possible that a SCSI Bus Reset ; is in progress, but the following code will ignore this. ; moveq.l #aBSY+aSEL,d0 ; clear all but BSY and SEL (ignore RST) and.b sCSR(a3),d0 ; mask with a snapshot of the bus beq.s @exit ; if no BSY or SEL, bus is clear ; ; Reset the SCSI bus. We were running, so our device is hanging on the bus. ; If we are in the middle of a Reset, no other devices should be on the bus. ; @assertReset movea.l jvResetBus(a4),a0 ; get address of bus reset routine jsr (a0) ; reset the SCSI bus and kill all requests @exit movem.l (sp)+,intrRegs ; restore registers rts ; ; Message ; ; Function: Sends a message to the deferred task. ; (It also installs the deferred task, if necessary) ; ; On entry: d0.b - message selector ; d1 - additional information, if any ; ; Current messages include: ; ; scResel ID mask in d1.b ; scEOP ; scPhaseMm ; scNewRequest ptr to scsiPB in d1.l ; scLossBsy ; scBusReset ; scParity ; ; ; Message: movem.l intrRegs,-(sp) ; save registers bset.b d0,dTask+dtParm(a4) ; tell SCSIDT which interrupt occurred @reselect cmpi.b #scResel,d0 ; is it a Reselect message ? bne.s @newRequest move.b d1,dTask+dtParm+3(a4) ; pass the reselect ID to dTask in low byte of dtParm bra.s @enqueueSCSIDT @newRequest cmpi.b #scNewRequest,d0 ; is there a new request to process ? bne.s @enqueueSCSIDT movea.l jvEnDequeue(a4),a0 ; address of enqueue/dequeue routine move.l d1,a1 ; get the pointer to the request move.l scsiMgrFlags(a1),d1 ; get the flags from the request btst.l #scsiBImmed,d1 ; is this a page-fault request ? bne.s @enqInsert ; if so, place it at the front @enqNormal moveq.l #enqNormal,d0 ; enqueue this request at the end of the queue jsr (a0) ; go do it bra.s @enqExit ; we're done @enqInsert move.l a1,pageFault(a4) ; we're handling a page fault - keep ptr to PB moveq.l #enqFront,d0 ; place this request at the front of the queue jsr (a0) ; go do it movea.l jvSCSIDT(a4),a0 ; address of SCSI Mgr deferred task jsr (a0) ; handle VM page-fault by calling SCSIDT directly @enqExit @enqueueSCSIDT bset.b #dtEnqueued,state1(a4) ; going to enqueue the deferred task bne.s @exit ; if already set, then don't enqueue dTask lea.l dTask(a4),a0 ; point to deferred task record _DTInstall ; install it in the deferred task queue @exit movem.l (sp)+,intrRegs ; restore registers rts ; ; Preflight ; ; Called by: SCSIRequestIO ; ; Calls: None. ; ; On entry: a0 - addr of Preflight routine (used for recursive calls) ; a4 - ptr to SCSI Mgr globals ; a5 - ptr to parameter block in question ; ; On exit: d0 - result code ; ; Function: ; This routine checks that the parameter block is consistent. ; Preflight: @initResult moveq.l #noErr,d0 ; assume a good parameter block @prelimCheck tst.l scsiQLink(a5) ; is link NIL ? bne.w @errorExit ; if not, get out tst.l scsiPrivate(a5) ; is private storage link NIL ? bne.w @errorExit ; if not, get out tst.w scsiVersion(a5) ; is the version number greater than zero ? beq.w @errorExit ; valid version numbers > zero @checkLink tst.l scsiLinkCmd(a5) ; are we pointing to a linked command ? beq.s @checkPB ; if not, continue to check PB bra.w @errorExit ; not allowed in the initial release movem.l a0/a5,-(sp) ; save pointer to the PB movea.l scsiLinkCmd(a5),a5 ; point to linked command PB jsr (a0) ; call Preflight recursively movem.l (sp)+,a0/a5 ; restore pointer to the parameter block @checkPB tst.l scsiReqTO(a5) ; check for a nonzero request timeout beq.w @errorExit ; no timeout provided tst.w scsiSelTO(a5) ; check for a nonzero selection timeout beq.w @errorExit ; no timeout provided cmpi.w #mgrVersion,scsiVersion(a5) ; is this a level-1 PB ? bne.s @1 ; if not, we can't preflight the reserved fields move.l scsiMgrFlags(a5),d2 ; get a copy of the flags btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? <2.8> beq.s @noDMA ; if not, different check <2.8> andi.l #$FFFFFF00,d2 ; if anything else is on, it's bad bra.s @univDone ; done with the check @noDMA andi.l #$FFFFFF10,d2 ; if "physical" or anything else on, error @univDone bne.w @errorExit ; a reserved bit was non-zero @1 moveq.l #LUNMask,d2 ; mask to check for valid ID and LUN cmp.b scsiReqID(a5),d2 ; check for a valid target ID blo.w @errorExit ; get out (ID > 7) cmp.b scsiReqLUN(a5),d2 ; check for a valid logical unit number blo.w @errorExit ; get out (LUN > 7) tst.l scsiCmdBuf(a5) ; check that command pointer is non-nil beq.s @errorExit ; nil command buffer ptr - get out cmp.l scsiSnsBuf(a5),zeroReg ; is auto-sense being requested ? beq.s @noSense ; if not, continue cmpi.b #minSnsLen,scsiSnsLen(a5) ; is sense buffer of minimum length ? blo.s @errorExit ; it's too short - error @noSense cmp.l scsiDataLen(a5),zeroReg ; is a data transfer expected ? bne.s @dataTransfer ; if non-zero, data transfer is expected @noDataTransfer cmp.l scsiDCInstr(a5),zeroReg ; if no data transfer, this must be nil bne.s @errorExit ; if non-nil, error moveq.l #noErr,d0 ; no error bra.s @initialize ; go initialize rest of PB @dataTransfer move.l scsiDCInstr(a5),a0 ; get ptr to data-chaining instructions cmp.l a0,zeroReg ; check that data chaining is requested beq.s @errorExit ; no data-chaining instr provided - error @repeatInstr tst.l dcAddr(a0) ; is it a dcStop instruction ? beq.s @chkStop ; check if it is a valid dcStop (dcCount=dcOffset=0) adda.w #dcSize,a0 ; point to next data-chaining instruction bra.s @repeatInstr ; check next instruction @chkStop tst.l dcCount(a0) ; is the dcCount field zero ? bne.s @errorExit ; if not, error tst.l dcOffset(a0) ; is the dcOffset field zero ? bne.s @errorExit ; if not, error @initialize ; ; NOTE: "scsiCompletion","scsiUsrData", and "scsiCmdLen" are always untouched ; move.w #scsiEnqueued,scsiResult(a5) ; mark async request in progress move.b zeroReg,scsiSnsXfer(a5) ; no sense bytes returned, yet move.l zeroReg,scsiDataXfer(a5) ; no data bytes transferred, yet move.b #statusInitial,scsiStatus(a5) ; initialize status byte to "invalid" value tst.l scsiDataLen(a5) ; data transfer expected ? beq.s @exit ; if not, we're done ; ; In order to implement the "Save Data Pointer" message, it is necessary to ; save a copy of the entire data-chaining instruction block. The data-chaining ; instructions combined with the DCOffs pointer make up the data pointer for ; the request. ; @datachaining move.l scsiDCInstr(a5),a0 ; point to data-chaining instructions moveq.l #dcLoop,d2 ; used for comparison @repeat move.l dcAddr(a0),d1 ; get the address or opcode beq.s @dcStop ; if "dcStop", then we're done cmp.l d2,d1 ; is it a "dcLoop" instruction ? bne.s @save ; if not, save the address in "dcStore" move.l dcCount(a0),d1 ; the loop count needs to be saved @save move.l d1,dcStore(a0) ; save the necessary value adda.w #dcSize,a0 ; point to next data-chaining instruction bra.s @repeat ; copy another instruction @dcStop move.l zeroReg,dcStore(a0) ; clear out dcStore field in dcStop instruction bra.s @exit ; everything is fine, no error @errorExit move.w #scsiBadPBErr,d0 ; a bad SCSI parameter block @exit tst.w d0 ; set condition codes rts ; ; Setup ; ; Called by: SCSIDT ; ; Calls: SetTimer. ; ; On entry: a4 - ptr to SCSI Mgr globals ; a5 - ptr to parameter block in question ; ; On exit: d0 - result code ; ; Function: ; This routine sets up the SCSI Manager globals and the CPU registers to handle ; a SCSI request. It is the "standard entry" routine used by the rest of the ; new SCSI Manager code. ; Setup: move.l a5,activeReq(a4) ; record the active request @calcTimeout move.l Ticks,d2 ; get the current time move.l scsiReqTO(a5),d1 ; get the request timeout value asr.l #4,d1 ; convert to ticks (using msec timebase if possible) move.l scsiPrivate(a5),a0 ; point to private storage for this request move.l scsiTime(a0),d0 ; check the deadline for the request bne.s @calculated ; if non-zero, it's already calculated move.l d2,d0 ; get the current time add.l d1,d0 ; calculate the time it should timeout move.l d0,scsiTime(a0) ; save timeout bra.s @default ; let's go @calculated sub.l d2,d0 ; calculate the differential bcs.s @default ; current time past timeout, use default cmp.l d1,d0 ; compare default and calculated timeouts bls.s @default ; if lower, use the default timeout move.l d0,d1 ; else, use the calculated timeout @default move.l d2,d0 ; get current time add.l d1,d0 ; add time differential move.l d0,timeout(a4) ; next timeout time mulu.w #17,d1 ; convert ticks to milliseconds move.l d1,d0 ; milliseconds to the timeout movea.l jvSetTimer(a4),a0 ; set the timer (no error possible) jsr (a0) ; go to the routine rts ; ; Find ; ; Called by: SCSIDT ; ; Calls: None. ; ; On entry: d0.l - contains a mask of SCSI ID's to check. ; bit 31 : if set, find any request ; bits 7-0 : bitmap of possible ID(s) ; (Time to start a new request.) ; ; bit 31 : if clear, find the first request (A reselection has occurred.) ; bits 15-8 : bitmap with ID to find ; bits 7-0 : bitmap with LUN to find (Zero, if reselected without an Identify) ; ; On exit: d0 - points to the request that was found. (Set to zero if none was found.) ; ; Function: ; This routine is used in two ways: (1) it searches for the first request that is not ; disconnected, and is not queued behind a disconnected request, or (2) find the first ; disconnected request for a given ID and LUN. It stops when it hits the end of the ; queue, or the dummy parameter block used by an old SCSI Mgr call. ; ; If an Identify message is not sent after a Reselection, the LUN bitmap will zero. ; (It is assumed that the LUN, if any, was passed in the command buffer.) ; In this case, the target does not implement the Identify message, and so the LUN ; is forced to zero. This means that only one LUN at a time may be used at a given ; ID if the Identify message is not implemented. ("scsiReqLUN" must be set to zero.) ; ; ActiveReq, by convention, is always cleared at the end of a SCSI request. ; Find does nothing if ActiveReq is set. ; Find movem.l a0-a3/d1-d2,-(sp) ; save registers move.w sr,-(sp) ; save the status register contents ori.w #HiIntMask,sr ; disable interrupts temporarily tst.l activeReq(a4) ; are we currently servicing a request ? bne.s @notFound ; if so, act as if nothing was found lea.l discLUN(a4),a1 ; ptr to base of the disconnected LUN table lea.l scsiQHead(a4),a0 ; get the head pointer move.l zeroReg,d2 ; clear out d2 to use it as an offset btst.l #findAnyFlag,d0 ; which type of search are we doing ? beq.s @setupGiven ; go find first request for given ID mask move.b discID(a4),d1 ; get mask of disconnected ID's eor.b d1,d0 ; remove disconnected ID's from the running bra.s @nextReq ; start searching @setupGiven move.b d0,d1 ; get LUN bitmap into d1 bne.s @fixID ; nonzero LUN bitmap is correct bset.l zeroReg,d0 ; LUN is in scsiCmdBuf (no Identify - force to LUN 0) @fixID rol.w #8,d0 ; get ID bitmap into d0.b ; ; Bit 31 set: d0.b contains a bitmap of possible ID's (1 or more) ; d1.b is unused ; ; Bit 31 clear: d0.b contains an ID bitmap ; d1.b contains an LUN bitmap to use: ; ID 3, LUN 6 : d0.b = 00001000 d1.b = 01000000 ; @nextReq move.l scsiQLink(a0),a0 ; get next request cmp.l a0,zeroReg ; are we at the end ? beq.s @notFound ; if so, we didn't find the request @chkSearchType btst.l #findAnyFlag,d0 ; what type of search was requested ? bne.s @findAny ; find first request for any non-disc ID @findGiven move.b scsiReqID(a0),d2 ; get the ID of the parameter block blt.s @dummy ; if ID < 0, this is the dummy param block btst.l d2,d0 ; is the ID the same as the given ID ? beq.s @nextReq ; if not, check the next request move.b scsiReqLUN(a0),d2 ; get the LUN of the parameter block btst.l d2,d1 ; is the LUN the same ? beq.s @nextReq ; if not, keep looking bra.s @found ; we found the request we were looking for @findAny move.b scsiReqID(a0),d2 ; get the ID that we wish to use blt.s @dummy ; if ID < 0, this is the dummy parameter block btst.l d2,d0 ; is this ID still in the running ? beq.s @nextReq ; if not, check the next request @okID move.b 0(a1,d2.w),d1 ; get the LUN bitmap for the current ID move.b scsiReqLUN(a0),d2 ; get the LUN to check btst.l d2,d1 ; check if this LUN is disconnected bne.s @nextReq ; if it is, it is not a possible candidate @found ; we found it -- fall through from above move.l a0,d0 ; return the pointer in d0 bra.s @exit ; we found our request @notFound @dummy move.l zeroReg,d0 ; no requests in front of the dummy parameter block @exit move.w (sp)+,sr ; restore status register contents movem.l (sp)+,a0-a3/d1-d2 ; restore register contents tst.l d0 rts ; ; SetTimer ; ; Called by: Setup, MsgIn ; ; Calls: _RmvTime, _InsTime, _PrimeTime ; ; On entry: a3 - ptr to SCSI read base address ; a4 - ptr to SCSI Manager globals ; d0 - number of milliseconds to wait (0 means stop the timer) ; ; Function: Provides a timer facility for the SCSI Mgr. It contains the use of the ; Time Manager traps in one routine. (This makes it easier for A/UX to use ; the new SCSI Manager.) ; SetTimer: lea.l timer(a4),a0 ; point to timer task tst.l d0 ; is there a need to re-arm the timer ? bne.s @arm ; if count <> 0, re-arm the timer @stop _RmvTime ; stop the timer (ignore error) rts ; we're done (we've stopped the timer) @arm move.l d0,-(sp) ; hold on to timeout value _RmvTime ; just for safety's sake (ignore error) _InsTime ; put it back in the queue (ignore error) move.l (sp)+,d0 ; restore timeout value _PrimeTime ; set the timer rts ; ; TimeTask ; ; Called by: Time Manager ; ; Calls: ClearState ; ; On exit: Timed-out requests are removed from the queue. ; ; Function: This routine handles timeouts on the SCSI bus. ; ; Algorithm: if (queue nonempty) { ; if (curtime > timeout) { ; for (each element) { ; if (tagged) ; ClearState(element, saTimeOutErr); ; else if (scsiTime < curtime) ; tag element; ; } ; } ; rearm timer; ; } ; TimeTask: IF dbSymbols THEN link a6,#0 ; MacsBug stack frame nop ; filler ENDIF movem.l intrRegs,-(sp) ; save registers movea.l SCSIGlobals,a4 ; get pointer to SCSI Manager globals TimeTaskCommon ; move.l base5380(a4),a3 ; point to the SCSI base read address moveq.l #0,zeroReg ; set up the "zero" register @timer move.l scsiQHead(a4),d0 ; is there anything in the queue ? beq.s @exit ; if empty, leave without resetting the timer move.l Ticks,d0 ; get the current "time" cmp.l timeout(a4),d0 ; have we hit the first possible timeout ? blo.s @rearmTimer ; not yet, it's a spurious watchdog interrupt move.l activeReq(a4),a5 ; get the active request, if any cmp.l a5,zeroReg ; is there a currently active request ? beq.s @watchdogTimeout ; if not, then the watchdog timer went off @requestTimeout movea.l scsiPrivate(a5),a0 ; point to private storage for this request cmp.l scsiTime(a0),d0 ; check timeout time against current time blo.s @rearmTimer ; if less, then it has not timed out @timedOut move.w #scsiTimeOutErr,d0 ; it took too long movea.l jvClearState(a4),a0 ; clear SCSI Mgr and SCSI bus, if necessary jsr (a0) ; go to the routine ;movea.ljvClearBus(a4),a0 ;jsr (a0) bra.s @rearmTimer ; set up the next timeout @watchdogTimeout move.l d0,d1 ; hold on to the current time move.w #scsiTimeOutErr,d0 ; walk queue, killing requests lea.l scsiQHead(a4),a5 ; ptr to start of SCSI request queue @loop move.l a5,a1 ; hold onto current parameter block movea.l scsiQLink(a5),a5 ; point to the next parameter block cmp.l a5,zeroReg ; are we done ? beq.s @rearmTimer ; if so, get out movea.l scsiPrivate(a5),a0 ; point to private storage for this request cmp.l a0,zeroReg ; is it in progress ? beq.s @loop ; if not, check next request cmp.l scsiTime(a0),d1 ; else, has this request timed out ? blo.s @loop ; if not, check the next request movea.l jvClearState(a4),a0 ; get address of ClearState routine jsr (a0) ; kill off this request move.l a1,a5 ; continue with the next parameter block bra.s @loop ; continue until all PB's are checked @rearmTimer move.l zeroReg,d0 ; clear high word of count move.w #WDTO,d0 ; set timeout movea.l jvSetTimer(a4),a0 ; set the timer jsr (a0) ; go to the routine @exit movem.l (sp)+,intrRegs ; restore registers IF dbSymbols THEN unlk a6 rts dc.b 'TIMETASK' ELSE rts ENDIF ; ; SCSIDT ; ; Called by: Deferred Task Dispatcher, SCSIIntHnd, TimeTask, Message ; ; Calls: ; ; On entry: "dtParm" (a1) reflects the type(s) of interrupts. ; ; Function: ; This is the remainder of the SCSI interrupt handler. It also handles the Time Manager ; interrupts that provide the watchdog timer feature. (On the Mac Plus, a second timer ; is used to monitor the SCSI chip's interrupt line.) It is called with all interrupts ; enabled, except possibly during a page-fault. ; SCSIDT: if dbSymbols then link a6,#0 nop ; filler for debugging purposes endif @setup movem.l intrRegs,-(sp) ; save registers movea.l SCSIGlobals,a4 ; get pointer to SCSI Manager globals SCSIDTCommon ; moveq.l #0,zeroReg ; clear out the zero register move.l base5380(a4),a3 ; SCSI read base address move.l activeReq(a4),a5 ; point to active request, if any @clrDTEnqueued move.l zeroReg,dTask(a4) ; clear out the link for safety bclr.b #dtEnqueued,state1(a4) ; tell Message that dTask is not enqueued @doWhile ; start of giant loop @intrPoll ; ; If interrupts are disabled, and there are no interrupts to process, then ; we will have to poll for interrupts, and call IntHnd to post them. ; (This only happens while servicing a page fault with interrupts disabled.) ; tst.l dTask+dtParm(a4) ; are there any interrupts to process ? bne.s @chkBusReset ; if there are, service them first move.w sr,d0 ; get a copy of the status register andi.w #HiIntMask,d0 ; get the CPU interrupt level cmpi.w #scsiIntMask,d0 ; check against the SCSI chip's interrupt level blo.s @chkBusReset ; if CPU level is lower, interrupts will get through @waitForIRQ btst.b #bIREQ,sBSR(a3) ; is an interrupt pending ? beq.s @waitForIRQ ; if not, keep waiting movea.l jvIntHnd(a4),a0 ; get address of interrupt handler jsr (a0) ; call SCSIIntHnd to post interrupt for service ; ; At this point, we have an interrupt to service. ; @chkBusReset bclr.b #scBusReset,dTask+dtParm(a4) ; is it a SCSI Bus Reset interrupt ? beq.s @notBusReset ; if not, check other interrupts ; ; Send "bus was reset" message to all the requests in the queue ; @busReset lea.l scsiQHead(a4),a5 ; load pointer to first request in the queue lea.l dummy(a4),a1 ; point to dummy parameter block move.w #scsiBusResetErr,d0 ; report that SCSI bus was reset @brLoop movea.l jvClearState(a4),a0 ; addr of clear request routine movea.l scsiQLink(a5),a5 ; point to the next request cmp.l a5,zeroReg ; are we done ? beq.w @endWhile ; if so, see if anything needs to be done cmpa.l a1,a5 ; are we pointing at the dummy PB ? beq.s @killOldPB ; if so, kill it differently jsr (a0) ; kill off the new SCSI Mgr PB bra.s @brLoop ; keep going @killOldPB moveq.l #scsiOldSCSIErr,d1 ; pretend old SCSI Mgr is killing this request exg.l d0,d1 ; switch error messages jsr (a0) ; kill the request exg.l d0,d1 ; restore the error message bra.s @brLoop ; keep going IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF ; ; Handle any of the other interrupts. The algorithm is as follows: ; ; If (Reselect or EOP or PhaseMm) { ; do what is necessary to handle the interrupt ; } elseif (newRequest or requestsToProcess or LossOfBSY or Parity) { ; do what is necessary to handle the interrupt, ; start up a new request if possible ; } ; if necessary, handle phases until a Bus Free condition exists ; @notBusReset @Reselect bclr.b #scResel,dTask+dtParm(a4) ; is it a Reselection interrupt ? beq.s @EOP ; if not, check for EOP @Resel1 btst.b #bSEL,sCSR(a3) ; wait for *SEL to go away (infinite loop) bne.s @Resel1 move.b zeroReg,sICR+WrOffs(a3) ; release *BSY @validResel ; bypass check for "activeReq" because bra.w @noActiveReq ; "activeReq" is set after an Identify message @EOP bclr.b #scEOP,dTask+dtParm(a4) ; is it an End Of Process interrupt ? beq.w @PhaseMm ; if not, check if Phase Mismatch btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? beq.s @nonDMA ; if not, no processing required bclr.b #waitingForIRQ,state1(a4) ; we've been kicked out of DMA move.l sDCTRL(a3),d0 ; look at the DMA control register btst.l #bDMABERR,d0 ; did we get a DMA bus error ? bne.s @eopDMABERR ; if so, handle it move.l sDCNT(a3),d0 ; did we transfer all the data ? beq.s @eopSuccess ; if so, clean up after the DMA @eopDMABERR @eopError if dbSymbols then _Debugger endif IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF bra.w @nextPhase ; just exit for now @eopSuccess movea.l jvDataDMA(a4),a0 ; addr of DMA data-chaining interpreter jsr (a0) ; start up the next DMA, if necessary btst.b #waitingForIRQ,state1(a4) ; did we start a new DMA ? (waiting for intr) bne.w @endWhile ; if so, bail out for now, if possible ; ; fall through - we moved all the data requested, so it should be a new phase ; @nonDMA bra.w @nextPhase ; finished - resume handling phases @PhaseMm bclr.b #scPhaseMm,dTask+dtParm(a4) ; is it a Phase Mismatch interrupt ? beq.w @newRequest ; if not, check for a new request if dbSymbols then _Debugger endif btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? beq.s @nonDMA2 ; if not, no processing required cmp.l a5,zeroReg ; do we have an active request ? beq.w @pmmCleanup ; if not, just clean up the interrupt bclr.b #waitingForIRQ,state1(a4) ; are we awaiting an interrupt ? beq.s @pmmCleanup ; if not, just clean up the interrupt movea.l scsiPrivate(a5),a0 ; point to private storage for request move.l scsiPCount(a0),d0 ; length of DMA transfer request sub.l sDCNT(a3),d0 ; subtract number of bytes left to transfer ; ; On the SCSI DMA chip, if a Disconnect occurs during a long DMA transfer, the ; sDCNT register will be off by one. (It reports that one too many bytes were ; transferred.) To correct this, we need to subtract one from the number of ; bytes to be transferred only if it is a write operation. ; move.l scsiMgrFlags(a5),d1 ; get the flags for this transaction btst.l #scsiBWrite,d1 ; test the Write flag beq.s @pmmUpdate ; if it's a read operation, just continue subq.l #1,d0 ; adjust the number of bytes transferred @pmmUpdate add.l d0,scsiMoveOffs(a0) ; save number of bytes transferred add.l d0,scsiDataXfer(a5) ; update number of bytes transferred move.b zeroReg,sICR+WrOffs(a3) ; disable data bus (mainly for writes) move.l #iINTREN,sDCTRL(a3) ; turn off hhsk ; finish cleaning up <7> @nonDMA2 @pmmCleanup move.b zeroReg,sMR+WrOffs(a3) ; disable phase mismatch interrupts bra.w @nextPhase ; finished - resume handling phases ; ; These interrupts happen outside of a transaction, so after servicing ; the interrupt, we start up another request, if possible. ; @newRequest bclr.b #scNewRequest,dTask+dtParm(a4) ; is it a new request to process ? beq.s @LossBsy ; if not, check for Loss of BSY bra.s @startSCSI ; start up a request, if possible @LossBsy bclr.b #scLossBsy,dTask+dtParm(a4) ; is it a Loss of BSY interrupt ? beq.s @Parity ; if not, check for Parity Error bclr.b #waitingForIRQ,state1(a4) ; we got the interrupt we were waiting for move.b zeroReg,sMR+WrOffs(a3) ; disable Loss of BSY interrupts bra.s @startSCSI ; finished - start up a request, if possible @Parity bclr.b #scParity,dTask+dtParm(a4) ; is it a Parity Error interrupt ? IF dbSymbols THEN beq.s @startSCSI ; if not, start up a request, if possible <7> _Debugger ; we got a parity interrupt (never enabled) ENDIF ; Fall through to "@startSCSI" ; ; Start up another request, if possible ; @startSCSI move.w sr,-(sp) ; save status register contents ori.w #HiIntMask,sr ; bump up the interrupt level (critical section) moveq.l #findAny,d0 ; find the first available request to process movea.l jvFind(a4),a0 ; get address of Find routine jsr (a0) ; is there a request to process ? beq.w @failed ; there is nothing to process, so exit @foundRequest movea.l d0,a5 ; point at the request to process tst.l scsiPrivate(a5) ; does it have private storage ? bne.s @arbitrate ; if so, then it's an autosense PB - just start it up move.l nextPrivate(a4),d0 ; is there a private storage record available ? beq.s @failed ; if not, we can't process, so exit @havePrivate movea.l d0,a0 ; point to private storage record move.l a0,scsiPrivate(a5) ; point to SCSI Mgr private storage record move.b #scsiFInUse,scsiFlags(a0) ; clear SCSI Mgr flags and mark record as in use @findNextPrivate move.l zeroReg,nextPrivate(a4) ; assume no more records are available lea.l firstPrivate(a4),a1 ; point to head of private storage queue @loop movea.l scsiPrivLink(a1),a1 ; look at the next record cmp.l a1,zeroReg ; are we done ? beq.s @arbitrate ; nothing free ("nextPrivate" = nil), try to arbitrate tst.b scsiFlags(a1) ; is this record in use ? bne.s @loop ; it's in use, so check the next record @setNextPrivate move.l a1,nextPrivate(a4) ; this is the next available record @arbitrate btst.b #shwCbPwrMgr,G_Reserved0+3(a4) ; see if we have a Power Mgr beq.s @PwrDone ; if not, exit ; ; This portion of code freezes the "Spin Down" timer for the hard disk, preventing ; the Power Mgr from cutting hard disk power during ANY SCSI transaction. ; movea.l PmgrBase,a0 ; point to Pmgr locals move.l zeroReg,LastHd(a0) ; always freeze the spin down timer @startItUp ; power up the hard drive with pmCommandRec link a6,#-14 ; Power Mgr task, and 2-byte buffer lea.l -14(a6),a0 ; point to the parameter block move.w #powerCntl,pmCommand(a0) ; power up the SCSI +5V and +12V move.w #1,pmLength(a0) ; one byte of data in buffer move.w #hdOn*256,-2(a6) ; hdOn command pea.l -2(a6) ; address of the send buffer move.l (sp)+,pmSBuffer(a0) ; save the send buffer pointer move.l zeroReg,pmRBuffer(a0) ; no data to be received _PmgrOp ; send it off to the Power Mgr unlk a6 ; release local storage endwith @PwrDone movea.l jvArb(a4),a0 ; arbitrate for the SCSI bus jsr (a0) ; go to the routine beq.s @success ; arbitration succeeded, so we can continue @arbFailed ; arbitration failed, but we'll get a LossOfBSY intr move.l scsiPrivate(a5),a0 ; get pointer to private storage record move.b zeroReg,scsiFlags(a0) ; mark this private storage record available move.l zeroReg,scsiPrivate(a5) ; since it failed, release the private storage tst.l nextPrivate(a4) ; check for a free private storage record bne.s @failed ; there is already a free record move.l a0,nextPrivate(a4) ; this record is free @failed move.w (sp)+,sr ; restore the status register (end critical section) bra.s @afterPhase ; no phase handling will be necessary - no request @success move.w (sp)+,sr ; restore status register (end critical section) move.l scsiPrivate(a5),a0 ; get pointer to private storage record move.l scsiCmdBuf(a5),scsiCmdOffs(a0) ; copy of command buf ptr between interrupts movea.l jvSetup(a4),a0 ; get address of Setup routine jsr (a0) ; set up the current request (activeReq) movea.l jvSel(a4),a0 ; select the target device jsr (a0) ; go to the routine beq.s @nextPhase ; selection succeeded, so start handling phases @failedSelection movea.l jvClearState(a4),a0 ; error in d0 jsr (a0) ; blow away the SCSI PB movea.l zeroReg,a5 ; no active request bra.s @afterPhase ; no phase handling will be necessary - no request @noActiveReq @nextPhase IF phaseLogging THEN nop ; good place to jump to phase-logging routine ENDIF move.l zeroReg,d0 ; clear jump table offset in d0 @wfREQ move.b sCSR(a3),d0 ; get current state btst.l #bBSY,d0 ; is *BSY asserted ? beq.s @afterPhase ; if not, we're done btst.l #bREQ,d0 ; do we have a REQ (valid phase) beq.s @wfREQ ; if not, wait andi.b #aMSG+aCD+aIO,d0 ; d0 = {0,4,8,12,24,28} (16,20 undefined) lea.l phaseTable(a4),a0 ; point to phase jump table in globals movea.l 0(a0,d0),a0 ; get phase handler address from phase jump table lsr.b #2,d0 ; set TCR so phase match bit is correct move.b d0,sTCR+WrOffs(a3) ; set phase to match addi.w #scsiDataOut,d0 ; create the constant for phase logging cmp.l a5,zeroReg ; are we pointing to a request ? beq.s @skip ; if not, can't log phase move.w d0,scsiResult(a5) ; reflect the current phase in the PB @skip jsr (a0) ; go to phase-handling routine btst.b #waitingForIRQ,state1(a4) ; is DMA in progress ? (waiting for an intr) bne.s @afterPhase ; if so, then we should exit tst.l activeReq(a4) ; is there still a current request ? bne.s @nextPhase ; if so, handle next phase @afterPhase tst.b discID(a4) ; are any ID's disconnected ? beq.s @endWhile ; if not, no need to enable reselection interrupts move.b G_ID(a4),sSER+WrOffs(a3) ; enable reselection interrupts ; ; do { ; } while ( (pageFault && noInterrupts) || (dTask.dtParm != 0) || ; (!waitingForIRQ && nextPrivate && scsiQHead && !Find(any,discID)) ); ; @endWhile tst.l pageFault(a4) ; check pointer to page-fault parameter block beq.s @chkIntrPending ; if no page fault, any other interrupts pending ? move.w sr,d0 ; get a copy of the CPU's interrupt level andi.w #HiIntMask,d0 ; get CPU's interrupt level cmpi.w #scsiIntMask,d0 ; compare SCSI interrupt level to the CPU's bhs.s @goBack ; no interrupts, so keep looping @chkIntrPending tst.l dTask+dtParm(a4) ; are there any interrupt to be serviced ? bne.s @goBack ; there are interrupts left to service (keep looping) @chkOtherRequests btst.b #waitingForIRQ,state1(a4) ; are we "waiting for interrupt" ? bne.s @exit ; if so, we should exit (we'll get an intr) tst.l nextPrivate(a4) ; do we have private storage for a new request ? beq.s @exit ; if not, we can't start up a new request tst.l scsiQHead(a4) ; any requests left ? beq.s @exit ; no requests in the queue moveq.l #findAny,d0 ; find the first available request to process movea.l jvFind(a4),a0 ; get address of Find routine jsr (a0) ; is there a request to process ? beq.s @exit ; there is nothing to process @goBack bra.w @doWhile ; keep going (don't exit the "while" loop) @exit tst.l activeReq(a4) ; are we processing a request ? bne.s @enableIntr ; if so, the timer was already set (by "Setup") movea.l jvSetTimer(a4),a0 ; addr of timer setup routine move.l zeroReg,d0 ; assume no more timing needed tst.l scsiQHead(a4) ; are there any more requests ? beq.s @setTimer ; if there are not, stop the timer move.w #WDTO,d0 ; watchdog timeout @setTimer jsr (a0) ; set the timer btst.b #shwCbPwrMgr,G_Reserved0+3(a4) ; do we have a Power Mgr chip ? beq.s @PwrDone2 ; if not, skip ; ; This portion of code "starts" the "Spin Down" timer for the hard disk. ; The timer value is the value of Ticks when the last SCSI I/O completed. ; Whenever the SCSI bus is used, the timer is reset. ; When it goes off, "DoSpinDown" turns off the hard drive power. ; ; If there are any requests in the queue, leave the hard disk power on. ; @chkPower tst.l scsiQHead(a4) ; anything left in the queue ? bne.s @PwrDone2 ; if so, don't start the timer move.l PmgrBase,a0 ; point to Pmgr locals move.l Ticks,LastHd(A0) ; indicate last activity for ID 0 @PwrDone2 @enableIntr moveq.l #1,d0 ; enable interrupts move.l jvDisEnable(a4),a0 ; addr of interrupt disable/enable routine jsr (a0) ; enable all SCSI interrupts movem.l (sp)+,intrRegs ; restore registers IF dbSymbols THEN unlk a6 rts ; finished dc.b 'SCSIDT ' ELSE rts ENDIF ; ; Command ; ; Called by: SCSIDT ; ; Function: Sends "scsiCmdLen" command bytes to the target. ; If BSY disappears, it's assumed we timed out and the bus was reset to recover. ; Command: move.l scsiPrivate(a5),a0 ; get ptr to private storage move.l scsiCmdOffs(a0),a2 ; get the current command buffer ptr move.l zeroReg,d2 ; clear lower word of count move.b scsiCmdLen(a5),d2 ; get number of command bytes subq.l #1,d2 ; setup for a DBRA @nextCmdByte @wfREQ move.b sCSR(a3),d0 ; look at the bus btst.l #bBSY,d0 ; is BSY asserted ? beq.s @errExit ; if not, won't send command (a5 may be invalid) btst.l #bREQ,d0 ; wait for REQ beq.s @wfREQ btst.b #bPM,sBSR(a3) ; check for phase match beq.s @exit ; out of phase, so exit @inPhase move.b (a2)+,sODR+WrOffs(a3) ; load byte move.b #iDB,sICR+WrOffs(a3) ; assert the data bus move.b #iACK+iDB,sICR+WrOffs(a3) ; set *ACK @wfnREQ btst.b #bBSY,sCSR(a3) ; do we still have BSY ? beq.s @errExit ; if not, get out btst.b #bREQ,sCSR(a3) ; wait for no REQ bne.s @wfnREQ move.b zeroReg,sICR+WrOffs(a3) ; deassert *ACK and *DB dbra.w d2,@nextCmdByte ; send the next command byte ; ; This is a hack for the Tape Backup 40SC drive. It asserts an extra REQ on ; 10-byte commands, which appears as a request for an 11th byte (that's what ; the SCSI standard says!) We shouldn't leave until this extra REQ goes away. ; This delay will wait at least 256ms for a phase change before continuing. ; move.l zeroReg,d0 ; clear upper word move.w TimeSCSIDB,d0 ; get SCSI DBRA's/ms timing constant lsl.l #8,d0 ; wait at least 256ms for the tape drive move.l d0,d2 ; set up d2 as high word swap d2 @cmdHack btst.b #bBSY,sCSR(a3) ; is BSY still asserted ? beq.s @exit ; if not, leave btst.b #bPM,sBSR(a3) ; are we still in phase ? dbeq.w d0,@cmdHack ; loop until phase mismatch or timeout dbeq.w d2,@cmdHack ; high word of timeout @exit move.l a2,scsiCmdOffs(a0) ; save current offset into the cmd buffer @errExit ; if we lost BSY, a5 may be invalid rts ; ; StatusPhase ; ; Called by: SCSIDT ; ; On entry: a4 - ptr to SCSI Mgr globals ; a5 - ptr to active SCSI request parameter block ; ; Function: Reads only 1 status byte from the target. ; StatusPhase: @wfREQ move.b sCSR(a3),d0 ; look at the bus btst.l #bBSY,d0 ; is BSY asserted ? beq.s @exit ; no BSY - we won't get status (a5 may be invalid) btst.l #bREQ,d0 ; wait for REQ beq.s @wfREQ btst.b #bPM,sBSR(a3) ; check for phase match beq.s @exit ; we're out of phase @inPhase move.b sCDR(a3),d0 ; get the status byte move.b #iACK,sICR+WrOffs(a3) ; set *ACK @wfnREQ btst.b #bBSY,sCSR(a3) ; do we have BSY ? beq.s @exit ; if not, exit btst.b #bREQ,sCSR(a3) ; wait for no REQ bne.s @wfnREQ move.b zeroReg,sICR+WrOffs(a3) ; deassert *ACK @done move.b d0,scsiStatus(a5) ; store the status byte movea.l scsiPrivate(a5),a0 ; point at private storage for this request btst.b #scsiBAutoSns,scsiFlags(a0) ; is this the auto sense param block ? bne.s @exit ; don't note passage through Status phase @normal bset.b #scsiBStatus,scsiFlags(a0) ; note passage through status phase @exit rts ; ; MsgIn and MsgOut - handle the "Message In" and "Message Out" phases ; ; Called by: PhaseHnd ; ; Calls: None. ; ; Function: Transfers message bytes to and from the target in programmed I/O mode. ; ; Note: In the new SCSI Manager, the message support is as follows: ; ; Message In: Message Out: ; ; Command Complete Supported ; Extended Messages ; Extended Identify Msg Reject Never sent ; Modify Data Ptr Msg Reject ; Synchronous DTR Msg Reject Never sent ; Wide DTR Msg Reject Never sent ; Reserved Msg Reject Never sent ; Vendor Unique Msg Reject Never sent ; Save Data Pointer Supported ; Restore Pointers Supported ; Disconnect (In) Supported ; Disconnect (Out) Never sent ; Init Detected Err Msg Reject Never sent ; Abort Msg Reject Supported ; Message Reject Supported Supported ; No Operation Msg Reject Supported ; Message Parity Err Msg Reject Never sent ; Linked Cmd Complete Msg Reject (Currently) ; Lnkd Cmd Cmpl w/Flag Msg Reject (Currently) ; Bus Device Reset Msg Reject Supported ; Clear Queue Msg Reject Never sent ; Initiate Recovery Msg Reject Never sent ; Release Recovery Msg Reject Never sent ; Mundane Queue Tag Msg Reject Never sent ; Head of Queue Tag Msg Reject Never sent ; Ordered Queue Tag Msg Reject Never sent ; Abort Tag Msg Reject Never sent ; Ignore Wide Residue Msg Reject ; Identify Supported Supported ; Reserved (0D) Msg Reject Never sent ; Reserved (11-1F) Msg Reject Never sent ; Reserved (25-7F) Msg Reject Never sent ; ; ; This is the Message In phase handler. ; MsgIn: @getNextByte move.l zeroReg,d0 ; clear out offset @wfREQ move.b sCSR(a3),d0 ; look at the bus btst.l #bBSY,d0 ; is BSY asserted ? beq.s @msgInDone ; BSY is gone -- we won't get a message byte btst.l #bREQ,d0 ; wait for REQ beq.s @wfREQ btst.b #bPM,sBSR(a3) ; check for phase match beq.s @msgInDone ; lost phase match @inPhase move.b sCDR(a3),d0 ; get byte move.b #iACK,sICR+WrOffs(a3) ; set *ACK @wfnREQ btst.b #bREQ,sCSR(a3) ; wait for no REQ bne.s @wfnREQ @chkIdentify btst.l #scsiIdentBit,d0 ; check for "Identify" message (bit 7 set) beq.s @chkInvalid ; if not "Identify", is it valid ? move.b d0,d1 ; hold onto the "Identify" message byte movea.l jvIdentifyIn(a4),a0 ; address of Identify (In) message handler jsr (a0) ; handle the Identify message bra.s @msgHandled ; check if there is another message to handle @chkInvalid cmpi.b #numMsgVct-1,d0 ; is it in range of the message routing table ? bhi.s @msgInvalid ; if not, it's invalid @msgValid lea.l msgInTbl(a4),a0 ; point to base of message byte routing table move.b 0(a0,d0.l),d0 ; get offset into msgTable lea.l msgTable(a4),a0 ; point to message handler jump table movea.l 0(a0,d0.l),a0 ; get address of message byte handler jsr (a0) ; go handle the message byte @msgHandled btst.b #bPM,sBSR(a3) ; still in message in phase ? bne.s @getNextByte ; keep going @msgInDone rts @msgInvalid movea.l jvInvalidIn(a4),a0 ; otherwise, start sending a Message Reject jsr (a0) ; go to the routine bra.s @msgHandled ; see if there is anything else to do msgInvalidIn: movea.l scsiPrivate(a5),a0 ; point at SCSI Mgr private storage move.b #scsiMOMR,scsiState(a0) ; set state machine to "Msg Reject" state move.b #iATN+iACK,sICR+wrOffs(a3) ; assert ATN to start Msg Reject sequence move.b #iATN,sICR+wrOffs(a3) ; release ACK to complete handshake rts msgIdentifyIn: move.l zeroReg,d0 ; clear the "findAnyFlag" in d0 move.b dTask+dtParm+3(a4),d0 ; get the reselection ID mask from SCSIIntHnd move.b zeroReg,dTask+dtParm+3(a4) ; clear reselect ID mask (this intr has been serviced) rol.w #8,d0 ; upper byte of d0.w = ID mask; d0.b = 0 andi.b #LUNMask,d1 ; leave LUN in d1 bset.l d1,d0 ; set the bit in the LUN bitmap for Find movea.l jvFind(a4),a0 ; get address of Find routine jsr (a0) ; go find that request movea.l d0,a5 ; point to the parameter block bne.s @clearDiscLUN ; we found a real parameter block @PBTimedOut ; A timed-out target has reconnected. move.l jvClearBus(a4),a0 ; get address of ClearBus routine jsr (a0) ; abort this target (a5=0) (go to a BUS FREE) rts ; get out of here (BUS FREE condition) @clearDiscLUN move.l zeroReg,d0 ; clear out d0 (including the "findAnyFlag") move.b scsiReqID(a5),d0 ; use ID in d0 as offset into "discLUN[]" move.b scsiReqLUN(a5),d1 ; restore the LUN in d1 as offset into bitmap lea.l discLUN(a4),a0 ; point at disconnected LUN bitmap bclr.b d1,0(a0,d0.w) ; mark LUN as no longer disconnected tst.b 0(a0,d0.w) ; are any more LUN's disconnected for this ID ? bne.s @continue ; if so, don't clear the disconnected ID bit bclr.b d0,discID(a4) ; this is no longer a valid reselecting ID @continue movea.l jvSetup(a4),a0 ; get address of Setup routine jsr (a0) ; setup the current request movea.l jvRestorePtrs(a4),a0 ; implied Restore Pointers message jmp (a0) ; perform the Restore Pointers message msgCmdComplete: movea.l scsiPrivate(a5),a0 ; point at private storage for this request btst.b #scsiBAutoSns,scsiFlags(a0) ; is this an auto Request Sense PB ? bne.w @autoSense ; if so, restore original contents cmp.l scsiSnsBuf(a5),zeroReg ; was automatic sense disabled ? beq.w @completed ; if disabled, just leave @checkStat move.b scsiStatus(a5),d0 ; get the status byte andi.b #statusByteCode,d0 ; mask off vendor-unique bits in status byte btst.l #statusRsrvBit,d0 ; is the reserved bit set ? bne.w @completed ; if so, might not be "Check Condition" - exit cmpi.b #statusChkCond,d0 ; "Check Condition" status ? bne.w @completed ; no, so we're done ; ; At this point, the user wants an automatic REQUEST SENSE command, and the ; original command has failed. It is necessary to save all the fields in the ; user's parameter block that will be altered when handling the Request Sense ; command. The fields are saved in a portion of the dummy parameter block. ; @startAutoSense lea.l dummy(a4),a0 ; ptr to the dummy parameter block ; ; scsiQLink, scsiVersion, and scsiResult are not saved or changed. ; scsiBus, scsiReqID, and scsiReqLUN are not saved or changed. ; This leaves the auto Request Sense parameter block in its original ; position in the request queue. ; move.l scsiCompletion(a5),scsiCompletion(a0) ; save the completion address move.l zeroReg,scsiCompletion(a5) ; no completion routine for Req Sense ; ; scsiUsrData, scsiReqTO, scsiUsrFlags, and scsiSelTO are not saved or changed. ; move.l scsiMgrFlags(a5),scsiMgrFlags(a0) ; save original SCSI flags move.l #scsiFNoDisc,scsiMgrFlags(a5) ; sync virtual slow read, no disconnect move.l scsiLinkCmd(a5),scsiLinkCmd(a0) ; save linked command pointer move.l zeroReg,scsiLinkCmd(a5) ; no linked commands @setupCmd lea.l RSCmd(a4),a1 ; point to "Request Sense" command move.b scsiSnsLen(a5),allocLen(a1) ; fill in allocation length move.l scsiCmdBuf(a5),scsiCmdBuf(a0) ; save the command buffer pointer move.l a1,scsiCmdBuf(a5) ; perform a "Request Sense" @setupDC move.l scsiDataLen(a5),scsiDataLen(a0) ; save the original data byte count move.l zeroReg,d0 ; clear out d0 move.b scsiSnsLen(a5),d0 ; length of sense buffer move.l d0,scsiDataLen(a5) ; sense buf length is data buf length move.l scsiDataXfer(a5),scsiDataXfer(a0) ; save # of data bytes transferred move.l zeroReg,scsiDataXfer(a5) ; no data bytes transferred, yet lea.l RSDC(a4),a1 ; ptr to Req Sense data-chaining instr move.l scsiDCInstr(a5),scsiDCInstr(a0) ; save data-chaining instruction ptr move.l a1,scsiDCInstr(a5) ; data-chaining instr for Req Sense move.l scsiSnsBuf(a5),d0 ; address of request's sense buffer move.l d0,dcAddr(a1) ; buffer address in "dcAddr" field move.l d0,dcStore(a1) ; buffer address in "dcStore" field adda.w #dcSize,a1 ; point to next instruction move.l scsiDataLen(a5),d0 ; number of bytes to transfer (set previously) move.l d0,dcCount(a1) ; store in the "dcCount" field move.l d0,dcStore(a1) ; store in the "dcStore" field @setupAutoSense move.l scsiSnsBuf(a5),scsiSnsBuf(a0) ; save the sense buffer pointer move.l zeroReg,scsiSnsBuf(a5) ; disable auto Request Sense move.b scsiSnsLen(a5),scsiSnsLen(a0) ; save the sense buffer length move.b zeroReg,scsiSnsLen(a5) ; no sense buffer ; ; scsiSnsXfer is zero at this point, so it doesn't need initialization. ; move.b scsiStatus(a5),scsiStatus(a0) ; hold on to the status byte move.b #statusInitial,scsiStatus(a5) ; initial status byte value @setupPrivate ; ; scsiTime, scsiMgrFlag1, scsiMgrFlag2, scsiMoveOffs, ; and the virtual/physical tranlation record are not changed or saved ; movea.l scsiPrivate(a5),a1 ; point to private storage for this request lea.l dummyPriv(a4),a0 ; point to dummy PB's private storage record bset.b #scsiBAutoSns,scsiFlags(a1) ; mark this as the auto-Req Sense PB move.b scsiState(a1),scsiState(a0) ; save the Message Out state variable move.b #scsiMOID,scsiState(a1) ; back to normal "Identify" state move.l scsiCmdOffs(a1),scsiCmdOffs(a0) ; save the temp command buffer ptr move.l scsiCmdBuf(a5),scsiCmdOffs(a1) ; copy of scsiCmdBuf move.l scsiDCSaved(a1),scsiDCSaved(a0) ; save the saved data pointer value move.l zeroReg,scsiDCSaved(a1) ; start at beginning of table move.l scsiDCOffs(a1),scsiDCOffs(a0) ; save the temp saved data pointer move.l zeroReg,scsiDCOffs(a1) ; start at beginning of table move.l zeroReg,activeReq(a4) ; the bus is currently free move.b zeroReg,sICR+WrOffs(a3) ; deassert *ACK bra.w @done ; done for now @autoSense ; ; At this point, a5 is pointing at a "Auto Request Sense" parameter block. ; It is necessary to restore the fields in the parameter block to their ; original values. ; @restorePB lea.l dummy(a4),a0 ; point to the dummy parameter block move.l scsiCompletion(a0),scsiCompletion(a5) ; restore completion routine ptr move.l scsiMgrFlags(a0),scsiMgrFlags(a5) ; restore SCSI flags move.l scsiLinkCmd(a0),scsiLinkCmd(a5) ; restore linked command pointer move.l scsiCmdBuf(a0),scsiCmdBuf(a5) ; restore command buffer pointer move.l scsiSnsBuf(a0),scsiSnsBuf(a5) ; restore sense buffer ptr move.b scsiSnsLen(a0),scsiSnsLen(a5) ; restore sense buffer length move.l scsiDataXfer(a5),d0 ; get number of bytes transferred move.b d0,scsiSnsXfer(a5) ; save sense byte count in original PB move.l scsiDataLen(a0),scsiDataLen(a5) ; restore data buffer length move.l scsiDataXfer(a0),scsiDataXfer(a5) ; restore data transfer byte count move.l scsiDCInstr(a0),scsiDCInstr(a5) ; restore data-chaining instruction ptr move.b scsiStatus(a5),d0 ; get the status byte move.b scsiStatus(a0),scsiStatus(a5) ; restore original status byte andi.b #statusByteCode,d0 ; mask vendor-unique bits in Req Sense status btst.l #statusRsrvBit,d0 ; is the reserved bit set ? bne.s @restorePrivate ; if so, might not be "Check Cond" - continue cmpi.b #statusChkCond,d0 ; "Check Condition" status ? bne.s @restorePrivate ; no, so continue move.b zeroReg,scsiSnsXfer(a5) ; Req Sense failed - report "no sense data" @restorePrivate movea.l scsiPrivate(a5),a1 ; point to private storage for this request lea.l dummyPriv(a4),a0 ; point to dummy PB's private storage record bclr.b #scsiBAutoSns,scsiFlags(a1) ; restore SCSI Mgr flags move.b scsiState(a0),scsiState(a1) ; restore Message Out state variable move.l scsiCmdOffs(a0),scsiCmdOffs(a1) ; restore temp command buffer ptr move.l scsiDCSaved(a0),scsiDCSaved(a1) ; restore saved data ptr value move.l scsiDCOffs(a0),scsiDCOffs(a1) ; restore temp saved data ptr value @completed moveq.l #noErr,d0 ; assume all is well movea.l scsiPrivate(a5),a0 ; point to private storage for this request btst.b #scsiBStatus,scsiFlags(a0) ; is the Status byte valid ? bne.s @validStatus ; if so, then exit @noStatus move.w #scsiNoStatusErr,d0 ; never received a Status byte @validStatus move.b zeroReg,sICR+WrOffs(a3) ; deassert ACK (avoids completion-routine deadlock) movea.l jvClearState(a4),a0 ; clear SCSI Mgr state jsr (a0) ; go to the routine @linkLoop move.l scsiLinkCmd(a5),d0 ; any linked commands left to be executed ? movea.l d0,a5 ; point to the linked command beq.s @done ; if not, we're truly done move.w #scsiLinkFailErr,d0 ; this linked command was never started jsr (a0) ; remove this request bra.s @linkLoop ; keep checking @done rts ; continue msgExtendedMsg: ; ; Currently, no extended messages are supported ; msgSaveDataPtr: move.l scsiDCInstr(a5),a0 ; point to data-chaining instructions cmp.l a0,zeroReg ; is it nil ? beq.s @dcStop ; if so, there is nothing to save movea.l scsiPrivate(a5),a1 ; point to private storage for this request move.l scsiDCOffs(a1),scsiDCSaved(a1) ; save the active "data" pointer state moveq.l #dcLoop,d0 ; used for comparison @repeat move.l dcAddr(a0),d1 ; get the address or opcode beq.s @dcStop ; if "dcStop", then we're done cmp.l d0,d1 ; is it a "dcLoop" instruction ? bne.s @save ; if not, save the address in "dcStore" move.l dcCount(a0),d1 ; the loop count needs to be saved @save move.l d1,dcStore(a0) ; save the necessary value adda.w #dcSize,a0 ; point to next data-chaining instruction bra.s @repeat ; copy another instruction @dcStop move.b zeroReg,sICR+wrOffs(a3) ; deassert *ACK rts ; continue msgRestorePtrs: movea.l scsiPrivate(a5),a0 ; point to private storage for this request move.l scsiCmdBuf(a5),scsiCmdOffs(a0) ; restore command buffer pointer move.l scsiDCSaved(a0),scsiDCOffs(a0) ; restore the active "data" pointer state move.l scsiDCInstr(a5),a0 ; point to data-chaining instructions cmp.l a0,zeroReg ; are there data-chaining instructions ? beq.s @dcStop ; if not, then we're done moveq.l #dcLoop,d0 ; used for comparison @repeat move.l dcStore(a0),d3 ; get the saved value move.l dcAddr(a0),d1 ; get the address or opcode beq.s @dcStop ; if "dcStop", then we're done cmp.l d0,d1 ; is it a "dcLoop" instruction ? bne.s @dcMove ; if not, it is a "dcMove" instruction @dcLoop move.l d3,dcCount(a0) ; restore the old count bra.s @continue ; go to next instruction @dcMove move.l d3,dcAddr(a0) ; restore the old address @continue adda.w #dcSize,a0 ; point at next instruction bra.s @repeat ; handle next instruction @dcStop move.b zeroReg,sICR+WrOffs(a3) ; deassert *ACK rts ; continue msgDisconnect: move.l zeroReg,d1 ; clear out d1 for use as an offset move.b scsiReqID(a5),d1 ; get ID of request move.b scsiReqLUN(a5),d0 ; get the LUN of request bset.b d1,discID(a4) ; mark ID as disconnected lea.l discLUN(a4),a0 ; point to base of disconnected LUN table bset.b d0,0(a0,d1.w) ; mark bit "scsiReqLUN" of "discLUN[scsiReqID]" move.l zeroReg,activeReq(a4) ; no active request move.w #scsiDisc,scsiResult(a5) ; mark this PB as disconnected move.b zeroReg,sICR+WrOffs(a3) ; deassert *ACK rts ; continue msgMsgRejIn: ; for now, no processing if a Msg Rej occurs IF dbSymbols THEN _Debugger ENDIF move.b zeroReg,sICR+wrOffs(a3) ; deassert *ACK rts IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF msgLCCF: ; Linked Command Complete with (or without) Flag moveq.l #noErr,d0 ; assume all is well movea.l scsiPrivate(a5),a0 ; point to private storage for this request btst.b #scsiBStatus,scsiFlags(a0) ; is the Status byte valid ? bne.s @validStatus ; if so, then continue @noStatus move.w #scsiNoStatusErr,d0 ; never received a Status byte @validStatus move.b zeroReg,sICR+WrOffs(a3) ; deassert ACK (avoids completion-routine deadlock) movea.l jvClearState(a4),a0 ; clear SCSI Mgr state jsr (a0) ; go to the routine IF dbSymbols THEN _Debugger ; what now ??? (find a private record, Setup, etc.) ENDIF rts ; continue IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF ; ; This is the Message Out phase handler ; MsgOut: @loop moveq.l #scsiMONOP,d0 ; assume unexpected Message Out phase tst.l activeReq(a4) ; check if we're processing a request beq.s @handleMsgOut ; send No Op's - unexpected Msg Out phase movea.l scsiPrivate(a5),a0 ; point at private storage for this request move.l zeroReg,d0 ; clear out the offset in d0 move.b scsiState(a0),d0 ; get the Message Out state for this request cmpi.b #numMsgVct-1,d0 ; is it in range of the Msg Out state routing table ? bls.s @handleMsgOut ; if so, call the Msg Out handler @invalid moveq.l #scsiMOInvalid,d0 ; Msg Out state machine messed up - kill the request @handleMsgOut lea.l msgOutTbl(a4),a0 ; point to base of Message Out routing table move.b 0(a0,d0.l),d0 ; get offset into msgTable lea.l msgTable(a4),a0 ; point to msgTable movea.l 0(a0,d0.l),a0 ; get address of message out state handler jsr (a0) ; go handle the message out state @wfReq move.b sCSR(a3),d0 ; get a snapshot of the bus btst.l #bBSY,d0 ; do we have BSY ? beq.s @msgOutDone ; if not, get out btst.l #bREQ,d0 ; wait for REQ before checking phase beq.s @wfReq ; no REQ yet btst.b #bPM,sBSR(a3) ; are we still in Message Out phase ? bne.s @loop ; if so, continue running the state machine @msgOutDone rts msgIdentifyOut: ; ; first send an Identify message to the appropriate LUN ; move.b scsiReqLUN(a5),d1 ; set up "Identify" message ori.b #scsiIdentFlg+scsiReselFlg,d1 ; "Identify and Disconnect" message move.l scsiMgrFlags(a5),d0 ; get SCSI flags btst.l #scsiBImmed,d0 ; is it a page-fault request ? bne.s @discNotOK ; sorry, page-faults can't disconnect btst.l #scsiBNoDisc,d0 ; check if disconnect is disabled beq.s @discOK ; we may disconnect @discNotOK bclr.l #scsiReselBit,d1 ; no disconnect -- simple "Identify" message @discOK move.b d1,sODR+wrOffs(a3) ; send out Identify message @chkBusDevReset btst.l #scsiBBusDevRst,d0 ; should we send a Bus Device Reset msg ? beq.s @noMessagesLeft ; if not, we're done move.b #iATN+iDB,sICR+wrOffs(a3) ; assert data bus - keep ATN on move.b #iATN+iACK+iDB,sICR+wrOffs(a3) ; assert ACK and data bus @wfnREQ0 btst.b #bREQ,sCSR(a3) ; wait for REQ to go away bne.s @wfnREQ0 ; keep waiting move.b #iATN,sICR+wrOffs(a3) ; keep ATN on - release data bus and ACK move.l scsiPrivate(a5),a0 ; point to private storage move.b #scsiMOBDR,scsiState(a0) ; set state machine to Bus Device Reset rts ; we're done for now @noMessagesLeft move.b zeroReg,sICR+wrOffs(a3) ; deassert ATN line - no messages left move.b #iDB,sICR+wrOffs(a3) ; assert data bus move.b #iACK+iDB,sICR+wrOffs(a3) ; assert ACK and data bus @wfnREQ1 btst.b #bREQ,sCSR(a3) ; wait for REQ to go away bne.s @wfnREQ1 ; keep waiting move.b zeroReg,sICR+wrOffs(a3) ; deassert data bus and release ACK - we're done move.l scsiPrivate(a5),a0 ; point to private storage move.b #scsiMONOP,scsiState(a0) ; set state machine to No Operation rts ; we're done for now msgBusDevRst: ; ; send a Bus Device Reset message ; move.b #scsiBusDevReset,sODR+wrOffs(a3) ; send out a Bus Device Reset message @noMessagesLeft move.b zeroReg,sICR+wrOffs(a3) ; deassert ATN line - no messages left move.b #iDB,sICR+wrOffs(a3) ; assert data bus move.b #iACK+iDB,sICR+wrOffs(a3) ; assert ACK and data bus @wfnREQ1 btst.b #bREQ,sCSR(a3) ; wait for REQ to go away bne.s @wfnREQ1 ; keep waiting move.b zeroReg,sICR+wrOffs(a3) ; deassert data bus and release ACK - we're done @wfBusFree move.b sCSR(a3),d0 ; get a snapshot of the bus btst.l #bBSY,d0 ; is BSY still asserted ? beq.s @accepted ; the device took the Bus Dev Reset message btst.l #bREQ,d0 ; do we have a REQ ? beq.s @wfBusFree ; if not, keep waiting for Bus Free @rejected ; ; The target has asserted REQ, which probably means it is preparing to send a ; Message Reject in response to the Bus Device Reset message. We simply let the ; phase handler take care of this case. ; move.l scsiPrivate(a5),a0 ; point to private storage move.b #scsiMONOP,scsiState(a0) ; set state machine to No Operation rts ; we're done for now @accepted move.w #scsiNoStatusErr,d0 ; error indicating the device took the BDR message movea.l jvClearState(a4),a0 ; remove this request jsr (a0) movea.l zeroReg,a5 ; no active request rts ; we're done msgMsgRejOut: IF dbSymbols THEN _Debugger ; performing a Message Reject sequence ENDIF move.b #scsiMsgReject,sODR+wrOffs(a3) ; send out a Message Reject message move.b zeroReg,sICR+wrOffs(a3) ; deassert ATN line - no messages left move.b #iDB,sICR+wrOffs(a3) ; assert data bus move.b #iACK+iDB,sICR+wrOffs(a3) ; assert ACK and data bus @wfnREQ1 btst.b #bREQ,sCSR(a3) ; wait for REQ to go away bne.s @wfnREQ1 ; keep waiting move.b zeroReg,sICR+wrOffs(a3) ; deassert data bus and release ACK - we're done move.l scsiPrivate(a5),a0 ; point to private storage move.b #scsiMONOP,scsiState(a0) ; set state machine to No Operation rts ; see what happens next msgInvalidOut: IF dbSymbols THEN _Debugger ; bad state machine state ENDIF rts ; get out IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF msgKillio: ; could be more elegant ??? IF dbSymbols THEN _Debugger ; Send an Abort and/or Bus Device Reset message ENDIF movea.l jvResetBus(a4),a0 ; addr of reset SCSI bus routine jsr (a0) ; reset the bus, killing all requests rts ; we're done IF padForOverPatch THEN ; padding dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's dc.w $4e71, $4e71, $4e71, $4e71 ; nop's ENDIF msgNoOp: ; If the target still wants message bytes, we send "No Operation" bytes. move.b #scsiNoOperation,sODR+wrOffs(a3) ; send out a No Op message move.b #iDB,sICR+wrOffs(a3) ; assert data bus move.b #iACK+iDB,sICR+wrOffs(a3) ; assert ACK and data bus @wfnREQ3 btst.b #bREQ,sCSR(a3) ; wait for REQ to go away bne.s @wfnREQ3 ; keep waiting move.b zeroReg,sICR+wrOffs(a3) ; release ACK and data bus rts ; check phase, and repeat, if necessary ; ; DataIO -- data-chaining instruction interpreter ; ; Called by: PhaseHnd ; ; Calls: Transfer and the data transfer routines. ; ; On entry: a3 - ptr to SCSI read base address ; a4 - ptr to SCSI Mgr globals ; a5 - ptr to active request ; d5 - offset into the data-chaining instructions ; ; Function: Performs the data-chaining interpretation and calls the data transfer routines. ; DataIO: move.w #scsiOverrunErr,d0 ; assume we shouldn't be in a data phase tst.l scsiDataLen(a5) ; is a data phase expected ? beq.s @errorExit ; if not, exit @expected moveq.l #scsiFFast+scsiFWrite+scsiFPhysical,d4 ; d4 = mask data transfer bits and.l scsiMgrFlags(a5),d4 ; d4 = {0,4,...,24,28} jump table offset movea.l scsiPrivate(a5),a0 ; point to private storage for this request move.l scsiDCOffs(a0),d5 ; get offset into data-chaining instr btst.b #bIO,sCSR(a3) ; check if I/O is asserted bne.s @input ; if asserted, we're in Data In phase @output btst.l #scsiBWrite,d4 ; test the write flag bne.s @dc_interpreter ; we're in the correct phase - start data transfer @badPhase move.w #scsiTransferErr,d0 ; data phase doesn't match read/write flag @errorExit movea.l jvLogError(a4),a0 ; note SCSI error in PB jsr (a0) ; go to the routine bra.w @exit ; exit @input btst.l #scsiBWrite,d4 ; test the write flag bne.s @badPhase ; we're in the wrong phase move.b #iDMA,sMR+WrOffs(a3) ; set DMA mode btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? <2.8> beq.s @DMAdone ; if not, skip it <2.8> move.l #iHSKEN,sDCTRL(a3) ; turn on hhsk @DMAdone move.b zeroReg,sIDMArx+WrOffs(a3) ; start read DMA @dc_interpreter move.l scsiDCInstr(a5),a1 ; point to the data-chaining buffer @start move.l dcCount(a1,d5.l),d2 ; get count move.l dcAddr(a1,d5.l),d0 ; get the data-chaining instruction beq.w @dcStop ; if d0 is 0, then it is "dcStop" move.l d0,a2 ; hold on to it in case it's an address addq.l #1,d0 ; check if it is a "dcLoop" instruction beq.w @dcLoop ; if d0 was -1, then it is "dcLoop" @dcMove ; a2 contains the address btst.b #bDMAR,sBSR(a3) ; do we have a DRQ ? bne.s @chkPhase ; if so, we can check the phase btst.b #bREQ,sCSR(a3) ; do we have a REQ ? beq.s @dcMove ; if not, we can't accurately check the phase @chkPhase btst.b #bPM,sBSR(a3) ; are we still in phase ? beq.w @dcStop ; if not, get out @adjust ; ; If a data-chaining instruction was only partially executed (due to a Disconnect), ; then the "scsiMoveOffs" field in the private storage will be nonzero, reflecting ; the number of bytes already transferred during the instruction. If it is nonzero, ; then the residual data to be transferred is the DCInstr value minus the amount ; already transferred, and the address pointer is incremented by the amount of data ; already transferred. ; move.l scsiMoveOffs(a0),d0 ; have we partially executed this DCInstr ? beq.s @chkOverrun ; if not, no adjustment necessary sub.l d0,d2 ; transfer only residual amount of data adda.l d0,a2 ; place data in the remaining buffer space @chkOverrun move.l scsiDataXfer(a5),d0 ; get number of bytes transferred already add.l d2,d0 ; expected bytes transferred after "dcMove" cmp.l scsiDataLen(a5),d0 ; will we go over the limit ? bls.s @noOverrun ; if not, start the transfer move.w #scsiOverrunErr,d0 ; possible overrun condition movea.l jvLogError(a4),a0 ; note SCSI error information in the PB jsr (a0) ; go to the routine bra.s @dcStop ; exit @noOverrun movea.l jvTransfer(a4),a0 ; transfer the data jsr (a0) ; go to the routine @update tst.w d0 ; check if an error occurred bne.s @dcStop ; we're finished for now btst.b #waitingForIRQ,state1(a4) ; are we DMA-ing ? bne.s @exit ; if so, bail for now movea.l scsiPrivate(a5),a0 ; point to private storage add.l d1,scsiDataXfer(a5) ; update bytes transferred cmp.l d1,d2 ; did we transfer everything ? beq.s @all ; if so, update PB and start next DCInstr @notAll add.l d1,scsiMoveOffs(a0) ; update the amount of data transferred bra.s @start ; restart current instruction @all move.l dcOffset(a1,d5.l),d0 ; get offset for the buffer address add.l d0,dcAddr(a1,d5.l) ; add the offset to the buffer address move.l zeroReg,scsiMoveOffs(a0) ; clear the amount of data transferred @nextInstr moveq.l #dcSize,d0 add.l d0,d5 ; point to next instruction movea.l scsiPrivate(a5),a0 ; point to private storage for this request move.l d5,scsiDCOffs(a0) ; save value in PB for consistency bra.s @start ; handle next instruction @dcLoop tst.l d2 ; check for loop count of zero beq.s @nextInstr ; count exhausted, go to the next instr subq.l #1,d2 ; decrement move.l d2,dcCount(a1,d5.l) ; update count beq.s @nextInstr ; if 0, count exhausted, go to next instr move.l dcOffset(a1,d5.l),d0 ; get offset asl.l #4,d0 ; multiply by 16 add.l d0,d5 ; adjust ptr to next instruction movea.l scsiPrivate(a5),a0 ; point to private storage for this request move.l d5,scsiDCOffs(a0) ; save value in PB for consistency bra.s @start ; handle next instruction @dcStop move.b zeroReg,sMR+WrOffs(a3) ; turn off pDMA to be safe btst.b #sSCSIDMAExists,G_Reserved0+3(a4) ; do we have a SCSI DMA chip ? <2.8> beq.s @DMAdone2 ; if not, skip it <2.8> move.l #iINTREN,sDCTRL(a3) ; turn off hardware-handshaking @DMAdone2 @exit moveq.l #noErr,d0 ; no problem rts ; ; DataDMA -- DMA-related data-chaining interpreter for use after first DMA ; ; Called by: SCSIDT ; ; Calls: The data transfer routines. ; ; On entry: a3 - ptr to SCSI read base address ; a4 - ptr to SCSI Mgr globals ; a5 - ptr to active request ; ; Function: Performs the data-chaining interpretation and calls the data transfer routines. ; DataDMA: move.l scsiDCInstr(a5),a1 ; point to data-chaining instructions move.l scsiPrivate(a5),a0 ; point to private storage for the request lea.l scsiVAddr(a0),a2 ; point to GetPhysical translation table move.l scsiDCOffs(a0),d5 ; get offset into data-chaining instructions move.l 12(a2),d1 ; get number of bytes transferred by DMA add.l d1,scsiDataXfer(a5) ; update bytes transferred tst.l 4(a2) ; is the residual equal to zero ? beq.s @all ; if so, we're done with this dcInstr @notAll ; not at data-chaining instruction boundary add.l d1,scsiMoveOffs(a0) ; add DMA bytes to form new offset into dcInstr bra.s @start ; restart the current instruction @all ; at data-chaining instruction boundary move.l dcOffset(a1,d5.l),d0 ; get offset for buffer address add.l d0,dcAddr(a1,d5.l) ; add offset to buffer address move.l zeroReg,scsiMoveOffs(a0) ; clear the amount of data transferred @nextInstr moveq.l #dcSize,d0 ; point to next dcInstr add.l d0,d5 move.l d5,scsiDCOffs(a0) ; save offset into data-chaining table @start move.l dcCount(a1,d5.l),d2 ; get count move.l dcAddr(a1,d5.l),d0 ; get the data-chaining instruction beq.w @dcStop ; if d0 is 0, then it is "dcStop" move.l d0,a2 ; hold on to it in case it's an address addq.l #1,d0 ; check if it is a "dcLoop" instruction beq.w @dcLoop ; if d0 was -1, then it is "dcLoop" @dcMove ; a2 contains the address btst.b #bDMAR,sBSR(a3) ; do we have a DRQ ? bne.s @chkPhase ; if so, we can check the phase btst.b #bREQ,sCSR(a3) ; do we have a REQ ? beq.s @dcMove ; if not, we can't accurately check the phase @chkPhase btst.b #bPM,sBSR(a3) ; are we still in phase ? beq.w @dcStop ; if not, get out @adjust ; ; If a data-chaining instruction was only partially executed (due to a Disconnect), ; then the "scsiMoveOffs" field in the private storage will be nonzero, reflecting ; the number of bytes already transferred during the instruction. If it is nonzero, ; then the residual data to be transferred is the DCInstr value minus the amount ; already transferred, and the address pointer is incremented by the amount of data ; already transferred. ; move.l scsiMoveOffs(a0),d0 ; have we partially executed this DCInstr ? beq.s @chkOverrun ; if not, no adjustment necessary sub.l d0,d2 ; transfer only residual amount of data adda.l d0,a2 ; place data in the remaining buffer space @chkOverrun move.l scsiDataXfer(a5),d0 ; get number of bytes transferred already add.l d2,d0 ; expected bytes transferred after "dcMove" cmp.l scsiDataLen(a5),d0 ; will we go over the limit ? bls.s @noOverrun ; if not, start the transfer move.w #scsiOverrunErr,d0 ; possible overrun condition movea.l jvLogError(a4),a0 ; note SCSI error information in the PB jsr (a0) ; go to the routine bra.s @dcStop ; exit @noOverrun moveq.l #noErr,d0 ; assume no error move.l d2,d1 ; make a copy of the count - is it zero ? beq.w @all ; if so, we've finished this instruction moveq.l #scsiFFast+scsiFWrite+scsiFPhysical,d4 ; d4 = mask to leave data transfer bits and.l scsiMgrFlags(a5),d4 ; d4 = {0,4,...,24,28} jump table offset lea.l dataTable(a4),a0 ; point to data transfer table in globals movea.l 0(a0,d4.l),a0 ; get the routine address jmp (a0) ; go to the routine - it will return to SCSIDT @dcLoop tst.l d2 ; check for loop count of zero beq.w @nextInstr ; count exhausted, go to the next instr subq.l #1,d2 ; decrement move.l d2,dcCount(a1,d5.l) ; update count beq.w @nextInstr ; if 0, count exhausted, go to next instr move.l dcOffset(a1,d5.l),d0 ; get offset asl.l #4,d0 ; multiply by 16 add.l d0,d5 ; adjust ptr to next instruction movea.l scsiPrivate(a5),a0 ; point to private storage for this request move.l d5,scsiDCOffs(a0) ; save value in PB for consistency bra.w @start ; handle next instruction @dcStop move.b zeroReg,sMR+WrOffs(a3) ; turn off pDMA to be safe move.l #iINTREN,sDCTRL(a3) ; turn off hardware-handshaking moveq.l #noErr,d0 ; no problem rts ENDWITH END