mirror of
https://github.com/elliotnunn/mac-rom.git
synced 2025-01-01 11:29:27 +00:00
0ba83392d4
Resource forks are included only for .rsrc files. These are DeRezzed into their data fork. 'ckid' resources, from the Projector VCS, are not included. The Tools directory, containing mostly junk, is also excluded.
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
|
|
|