mirror of
https://github.com/elliotnunn/supermario.git
synced 2024-08-29 08:29:03 +00:00
2328 lines
96 KiB
Plaintext
2328 lines
96 KiB
Plaintext
;----------------------------------------------------------------------------------------------
|
||
;
|
||
; 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: 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. <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
|
||
|