sys7.1-doc-wip/OS/SCSIMgr/SCSIMgrNew.a
2019-07-27 22:37:48 +08:00

2328 lines
96 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;----------------------------------------------------------------------------------------------
;
; File: SCSIMgrNew.a
;
; Written by: Jerry Katzung
;
; Copyright: © 1989-1992 by Apple Computer, Inc. All rights reserved.
;
; Change History (most recent first):
;
; <SM6> 11/3/92 SWC Changed SCSIEqu.a->SCSI.a.
; <SM5> 6/2/92 kc Revert to <SM3>
; <SM4> 6/1/92 CS Roll more cyclone stuff in.
; <SM3> 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: dont use not forROM, this file is only
; used in ROM builds, dont 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. <v1.2>
;
;----------------------------------------------------------------------------------------------
BLANKS ON
STRING ASIS
PRINT OFF
IF (&TYPE('dbSymbols') = 'UNDEFINED') THEN
dbSymbols EQU 0 ; for debugging purposes <v1.5>
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 ;<v1.2>
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 ;<v1.5>
EXPORT Preflight, EnDequeue
EXPORT Message, SCSIDT, Find, Setup, TimeTask
EXPORT ClearState, SetTimer, LogError ;<v1.2>
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 <v1.7>
NewSCSICommon ; <v1.2>
move.l a0,a5 ; set up pointer to the request <v1.2>
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 <v1.2>
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 <v1.2>
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 <v1.2>
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 <v1.4>
beq.s @exit ; if no error, then we can leave
move.w #scsiBadPBErr,d0 ; map all errors into "bad PB" errors <v1.2>
@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 <v1.2>
move.w scsiVersion(a5),d1 ; get the selector <v1.2>
bne.s @check1 ; if nonzero, check if it is a 1 <v1.2>
move.l zeroReg,d1 ; clear upper bits <v1.2>
move.b state2(a4),d1 ; return our SCSI Mgr revision level <v1.2>
move.l d1,scsiQLink(a5) ; return the result <v1.2>
rts ; we're done <v1.2>
@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 <v1.2>
rts ; we're done <v1.2>
@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) <v1.1><v1.2>
andi.l #1<<sSCSIDMAExists,d1 ; mask for DMA bit (clears other bits) <v1.1><v1.2>
beq.s @DMArslt ; return zero if no DMA <v1.1><v1.2>
moveq.l #1,d1 ; return one if DMA <v1.1><v1.2>
@DMArslt move.l d1,scsiQLink(a5) ; return the result <v1.1><v1.2>
rts ; we're out of here <v1.2>
@SCSIOops
move.w #scsiBadPBErr,d0 ; tell driver that call is not implemented <v1.2>
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 <v1.2>
movea.l jvClearState(a4),a0 ; addr of clear request routine
@loop
movea.l scsiQLink(a5),a5 ; point to the next request <v1.2>
cmp.l a5,zeroReg ; are we done ?
beq.s @delay1 ; if so, delay for good measure <v1.2>
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. <v1.2>
lsr.w #2,d0 ; 1 ms./4 = 250 us. <C859> <v1.2>
@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 <C859>
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 <C914> <v1.2>
@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 <v1.2>
@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 <v1.2>
rts
;
; LogError - Record a SCSI error in the parameter block <v1.2>
; <start>
; 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 ; <end>
rts ; we're done <v1.2>
;
; 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 <v2.3>
;
; 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 <v1.2>
move.l d0,a0 ; valid pointer <v1.2>
move.w scsiTempErr(a0),d0 ; do we have a pending error ? <v1.2>
beq.s @rel1 ; if not, then use the error in d2 <v1.2>
move.w d0,d2 ; return original error message <v1.2>
@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 <v1.2>
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 ? <v1.1>
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 <v1.1>
btst.b #sDeferUserFn,G_Reserved0+3(a4) ; is the VM-friendly call implemented ? <start>
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 ; <v1.1>
movem.l (sp)+,intrRegs ; restore all registers <end>
@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) <v1.2>
and.b sCSR(a3),d0 ; mask with a snapshot of the bus <v1.2>
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 ? <v1.1>
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) <v1.2>
; 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 <v1.2>
bra.w @errorExit ; not allowed in the initial release <v1.2>
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><v1.1>
beq.s @noDMA ; if not, different check <2.8>
andi.l #$FFFFFF00,d2 ; if anything else is on, it's bad <v1.2>
bra.s @univDone ; done with the check
@noDMA
andi.l #$FFFFFF10,d2 ; if "physical" or anything else on, error <v1.2>
@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 <v1.2>
@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) <v1.2>
@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 <v1.7>
TimeTaskCommon ; <v1.2>
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 <v1.2>
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 <v1.7>
SCSIDTCommon ; <v1.2>
moveq.l #0,zeroReg ; clear out the zero register <v1.2>
move.l base5380(a4),a3 ; SCSI read base address
move.l activeReq(a4),a5 ; point to active request, if any <v1.2>
@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 <v1.2>
@brLoop
movea.l jvClearState(a4),a0 ; addr of clear request routine
movea.l scsiQLink(a5),a5 ; point to the next request <v1.2>
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 <v1.3>
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 <v1.2>
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 <v1.5>
move.l sDCTRL(a3),d0 ; look at the DMA control register <v1.2>
btst.l #bDMABERR,d0 ; did we get a DMA bus error ? <v1.2>
bne.s @eopDMABERR ; if so, handle it <v1.2>
move.l sDCNT(a3),d0 ; did we transfer all the data ? <v1.2>
beq.s @eopSuccess ; if so, clean up after the DMA <v1.2>
@eopDMABERR
@eopError
if dbSymbols then
_Debugger
endif
IF padForOverPatch THEN ; padding <v1.3>
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 <v1.2>
@eopSuccess
movea.l jvDataDMA(a4),a0 ; addr of DMA data-chaining interpreter <v1.2>
jsr (a0) ; start up the next DMA, if necessary <v1.2>
btst.b #waitingForIRQ,state1(a4) ; did we start a new DMA ? (waiting for intr) <v1.5>
bne.w @endWhile ; if so, bail out for now, if possible <v1.2>
;
; fall through - we moved all the data requested, so it should be a new phase <v1.2>
;
@nonDMA
bra.w @nextPhase ; finished - resume handling phases <v1.2>
@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 ? <v1.5>
beq.s @nonDMA2 ; if not, no processing required <start>
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. <v1.5>
;
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 <C914> <end>
; finish cleaning up <v1.5> <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 <v1.5>
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. <v1.1>
;
movea.l PmgrBase,a0 ; point to Pmgr locals
move.l zeroReg,LastHd(a0) ; always freeze the spin down timer <v1.1>
@startItUp ; power up the hard drive <v1.1>
with pmCommandRec
link a6,#-14 ; Power Mgr task, and 2-byte buffer <v1.1>
lea.l -14(a6),a0 ; point to the parameter block <v1.1>
move.w #powerCntl,pmCommand(a0) ; power up the SCSI +5V and +12V <v1.1>
move.w #1,pmLength(a0) ; one byte of data in buffer <v1.1>
move.w #hdOn*256,-2(a6) ; hdOn command <v1.1>
pea.l -2(a6) ; address of the send buffer <v1.1>
move.l (sp)+,pmSBuffer(a0) ; save the send buffer pointer <v1.1>
move.l zeroReg,pmRBuffer(a0) ; no data to be received <v1.1>
_PmgrOp ; send it off to the Power Mgr <v1.1>
unlk a6 ; release local storage <v1.1>
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 <v1.5>
jsr (a0) ; blow away the SCSI PB <v1.5>
movea.l zeroReg,a5 ; no active request <v1.5>
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) <v1.2>
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 <v1.2>
cmp.l a5,zeroReg ; are we pointing to a request ? <v1.2>
beq.s @skip ; if not, can't log phase <v1.2>
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) <v1.5>
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)) ); <v1.5>
;
@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" ? <v1.5>
bne.s @exit ; if so, we should exit (we'll get an intr) <v1.5>
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. <v1.1>
; When it goes off, "DoSpinDown" turns off the hard drive power. <v1.1>
;
; If there are any requests in the queue, leave the hard disk power on. <v1.1>
;
@chkPower
tst.l scsiQHead(a4) ; anything left in the queue ? <v1.1>
bne.s @PwrDone2 ; if so, don't start the timer <v1.1>
move.l PmgrBase,a0 ; point to Pmgr locals <v1.4>
move.l Ticks,LastHd(A0) ; indicate last activity for ID 0 <v1.4>
@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. <v1.2>
; 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 <v1.3>
move.l scsiCmdOffs(a0),a2 ; get the current command buffer ptr <v1.3>
move.l zeroReg,d2 ; clear lower word of count <v1.2>
move.b scsiCmdLen(a5),d2 ; get number of command bytes <v1.2>
subq.l #1,d2 ; setup for a DBRA <v1.2>
@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)<v1.3>
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 <v1.2>
@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 ? <v1.2>
beq.s @errExit ; if not, get out <v1.3>
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 <v1.2>
;
; 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.<v1.2>
; This delay will wait at least 256ms for a phase change before continuing. <start>
;
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 <v1.2>
dbeq.w d2,@cmdHack ; high word of timeout <end>
@exit
move.l a2,scsiCmdOffs(a0) ; save current offset into the cmd buffer <v1.3>
@errExit ; if we lost BSY, a5 may be invalid <v1.3>
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 ? <v1.2>
beq.s @exit ; if not, exit <v1.2>
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 <from C846/PA081/03Mar87>
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 <v1.2>
@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 <v1.5>
beq.s @done ; if not, we're truly done <v1.5>
move.w #scsiLinkFailErr,d0 ; this linked command was never started <v1.2>
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 <v1.3>
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 <v1.2>
@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 <v1.3>
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 <v1.2>
tst.l activeReq(a4) ; check if we're processing a request <v1.2>
beq.s @handleMsgOut ; send No Op's - unexpected Msg Out phase <v1.2>
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 <v1.5>
btst.l #bBSY,d0 ; do we have BSY ? <v1.5>
beq.s @msgOutDone ; if not, get out <v1.2>
btst.l #bREQ,d0 ; wait for REQ before checking phase <v1.5>
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 <v1.5>
@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 ? <v1.1>
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 ? <v1.5>
beq.s @noMessagesLeft ; if not, we're done <start>
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 <end>
rts ; we're done for now <v1.5>
@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:
; <v1.5>
; send a Bus Device Reset message <start>
;
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 <end>
rts ; we're done <v1.5>
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 <v1.3>
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 <v1.3>
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 <v1.2>
tst.l scsiDataLen(a5) ; is a data phase expected ? <v1.2>
beq.s @errorExit ; if not, exit <v1.2>
@expected
moveq.l #scsiFFast+scsiFWrite+scsiFPhysical,d4 ; d4 = mask data transfer bits <v1.2>
and.l scsiMgrFlags(a5),d4 ; d4 = {0,4,...,24,28} jump table offset <v1.2>
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 <v1.2>
@errorExit
movea.l jvLogError(a4),a0 ; note SCSI error in PB <v1.2>
jsr (a0) ; go to the routine <v1.2>
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 <C478/10Dec86>
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 <C914>
@DMAdone
move.b zeroReg,sIDMArx+WrOffs(a3) ; start read DMA <C478/10Dec86>
@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 ? <v1.1><v1.2>
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. <v1.1>
;
move.l scsiMoveOffs(a0),d0 ; have we partially executed this DCInstr ? <v1.1>
beq.s @chkOverrun ; if not, no adjustment necessary <v1.1>
sub.l d0,d2 ; transfer only residual amount of data <v1.1>
adda.l d0,a2 ; place data in the remaining buffer space <v1.1>
@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 <v1.2>
movea.l jvLogError(a4),a0 ; note SCSI error information in the PB <v1.2>
jsr (a0) ; go to the routine <v1.2>
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 ? <v1.5>
bne.s @exit ; if so, bail for now <v1.1><v1.2>
movea.l scsiPrivate(a5),a0 ; point to private storage <v1.1><v1.2>
add.l d1,scsiDataXfer(a5) ; update bytes transferred <v1.1><v1.2>
cmp.l d1,d2 ; did we transfer everything ? <v1.1>
beq.s @all ; if so, update PB and start next DCInstr <v1.1>
@notAll
add.l d1,scsiMoveOffs(a0) ; update the amount of data transferred <v1.1><v1.2>
bra.s @start ; restart current instruction <v1.2>
@all
move.l dcOffset(a1,d5.l),d0 ; get offset for the buffer address <v1.2>
add.l d0,dcAddr(a1,d5.l) ; add the offset to the buffer address <v1.2>
move.l zeroReg,scsiMoveOffs(a0) ; clear the amount of data transferred <v1.1>
@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 <v1.2>
@DMAdone2
@exit
moveq.l #noErr,d0 ; no problem <v1.2>
rts
;
; DataDMA -- DMA-related data-chaining interpreter for use after first DMA <v1.2>
; <start>
; 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 <end>
rts
ENDWITH
END