mirror of
https://github.com/elliotnunn/sys7.1-doc-wip.git
synced 2024-10-31 19:05:04 +00:00
2300 lines
68 KiB
Plaintext
2300 lines
68 KiB
Plaintext
;
|
|
; File: HALc96Routines.a
|
|
;
|
|
; Contains: 53c96 Hardware-Specific routines
|
|
;
|
|
; Written by: Paul Wolf
|
|
;
|
|
; Copyright: © 1990-1994 by Apple Computer, Inc., all rights reserved.
|
|
;
|
|
; Change History (most recent first):
|
|
;
|
|
; <SM42> 2/3/94 DCB EnableSCSIIRQ was being called when DataDTask didn't run.
|
|
; <SM40> 2/1/94 DCB In my last checkin I introduced a re-entrancy problem in the
|
|
; deferred task. Now I block 68K ints instead of SCSI its before
|
|
; checking the semaphore.
|
|
; <SM39> 2/1/94 DCB Addressed a "minor" ordering problem with the DTPending
|
|
; semaphore which could cause sync wait hangs if somebody made a
|
|
; synchronous request from interrupt time.
|
|
; <SM38> 1/31/94 DCB Poured concrete over what I hope are the last remaining holes in
|
|
; the Q9x0 dual bus support.
|
|
; <SM37> 1/29/94 DCB Fixed several holes for dual bus psuedo DMA machines wherein we
|
|
; could clear the int at the VIA and not check the SCSI chip for
|
|
; the other bus.
|
|
; <SM36> 1/25/94 DCB Whoops, what's a little btst,bne between friends?
|
|
; <SM35> 1/25/94 DCB In order to allow our deferred task to run when user code is
|
|
; disabled I added a patch to the Deferred Task Manager which
|
|
; maintains our own private VM safe deferred task queue.
|
|
; <SM34> 1/19/94 DCB Slam interrupts to level 7 before leaving the deferred task.
|
|
; This prevents the stack overflow problem reported with the
|
|
; installer.
|
|
; <SM32> 12/19/93 DCB Major changes. The DeferAndWait function now installs a
|
|
; deferred task and then exits the SIM. We re-enter when the
|
|
; deferred task executes. This gets rid of the interrupt blocking
|
|
; and mouse freezing on pseudo-DMA machines.
|
|
; <SM31> 11/22/93 pdw Rolling in from <MCxx>.
|
|
; <MC6> 10/29/93 pdw Added the same sort of interrupt level lowering code around the
|
|
; call to ReselectISR that I added around the call to MyDT.
|
|
; <MC8> 11/8/93 pdw Added support for different inquiry data for different machines.
|
|
; <MC5> 10/28/93 pdw Fixed some Target mode stuff. Changed ISRs to drop interrupt
|
|
; level to previous level (determined by looking at the actual
|
|
; interrupt stack frame).
|
|
; <SM30> 11/19/93 chp Vectorize test of SCSI IE. Rework Install_ISR one more time.
|
|
; Implement “standard” VIA IRQ primitives as subroutines
|
|
; (basically copied from macros in HALc96equ.a).
|
|
; <SM29> 11/17/93 DCB Changing TestFor_GrandCentralExists so that it works on
|
|
; pre-SuperMario ROMs. This is necessary for the INIT version of
|
|
; the code.
|
|
; <SMG2> 9/22/93 chp Add Grand Central interrupt handler registration mechanism.
|
|
; <SM27> 11/8/93 DCB Disabling <SM26> until we get SCSIBusy fixed.
|
|
; <SM26> 10/29/93 DCB Using Deferred tasks to drop the interrupt level if we did
|
|
; indeed interrupt a level one task.
|
|
; <MC5> 10/28/93 pdw Fixed some Target mode stuff. Changed ISRs to drop interrupt
|
|
; level to previous level (determined by looking at the actual
|
|
; interrupt stack frame).
|
|
; <SM25> 10/15/93 DCB Getting rid of a debug trap in the bus error handler.
|
|
; <SM24> 10/14/93 pdw <MC> roll-in.
|
|
; <MC3> 10/12/93 pdw Added support for Synchronous data transfers, rewrote State
|
|
; Machine, message handling etc.
|
|
; <MC2> 9/26/93 pdw Changes to G_State usage from bit flags to enumeration.
|
|
; <SM23> 9/9/93 pdw Lots of little changes. Name changes, temporary cache_bug
|
|
; stuff.
|
|
; <SM22> 8/19/93 DCB Improving the bus error handler so that disconnects at
|
|
; non-polled bytes will work properly.
|
|
; <SM21> 8/13/93 pdw Removed PutXferPhase, instead stuffing phase after every
|
|
; interrupt.
|
|
; <SM20> 7/20/93 pdw Fixed a Cold Fusion IRQ registration problem by passing in the
|
|
; IRQ bit number so that InstallISR knows which slot in VIA2DT to
|
|
; use. Changed intDREQbitNum to a byte.
|
|
; <SM19> 7/17/93 pdw Lots of little things.
|
|
; <SM18> 7/8/93 pdw Changing record data to StkLowPt to help Kurt with debugging
|
|
; stack overflow bug.
|
|
; <SM17> 6/29/93 pdw Massive checkins: Change asynchronicity mechanism to CallMachine
|
|
; stack switching mechanism. Adding support for Cold Fusion.
|
|
; Rearranging HW/SW Init code. Some code optimizations. Resolving
|
|
; with my Ludwig sources.
|
|
; <SM16> 5/25/93 DCB Rollin from Ludwig. (The next item below)
|
|
; <LW11> 5/21/93 PW Added target mode stuff.
|
|
; <SM15> 5/6/93 PW Adding NOP to stop asm warnings.
|
|
; <SM14> 5/6/93 RC Killed some warnings in the build
|
|
; <SM13> 5/5/93 PW Converted names to meanies-friendly names. Updated with latest
|
|
; from Ludwig stuff.
|
|
; <SM12> 4/8/93 DCB Fixed up the BusErr Handler for Quadras.
|
|
; <LW9> 4/14/93 DCB Added parity checking in the interrupt handler.
|
|
; <LW8> 3/26/93 PW Changed dreqIn32bit to dreqNeedsSwapMMU and other minor name
|
|
; changes.
|
|
; <LW7> 3/8/93 PW Removed unnecessary use of D0 for restoring previous interrupt
|
|
; level.
|
|
; <SM11> 3/22/93 RC Fixed warning problem with useless bne.s
|
|
; <SM10> 3/20/93 PW Removed some of the PDMDebug stuff that's not needed.
|
|
; <LW6> 2/17/93 PW Added dual-bus/single-interrupt stuff for Quadra support.
|
|
; <SM9> 1/31/93 PW Update from the latest of Ludwig. Also changes required for PDM
|
|
; (will update Ludwig with these as needed myself).
|
|
; <LW5> 1/27/93 PW Disabled dropping of int level to 1 in HAL_ISR to help the old
|
|
; call from new call completion hang.
|
|
; <LW4> 1/27/93 PW Added HALIntPoll routine. Changed intEnable and intDisable
|
|
; calling around - now I disable going into MyDT and enable coming
|
|
; out and I disable going into HALAction and enable coming out.
|
|
; <LW3> 1/12/93 DCB Fixed the constant ISR! debugger breaks by moving bclr before
|
|
; returning int level to previous.
|
|
; <LW2> 1/8/93 PW Fixed interrupt reentrancy bug by slamming int level to 7 around
|
|
; critical sequence. Also added semaphore to check for reentrant
|
|
; ISR. This will not occur unless the level 7 fix is disabled.
|
|
; <SM8> 12/5/92 PW Add ability to handle reset interrupts - except for the Curio
|
|
; bus.
|
|
; <SM7> 10/30/92 DCB Lots of changes to reduce interrupt latency
|
|
; <SM6> 10/14/92 PW Removed some unused code and changed some comments.
|
|
; <SM5> 10/8/92 PW Lots of trivial name changes.
|
|
; <SM4> 8/31/92 PW Changed register and command definitions to reflect changes to
|
|
; SCSIEqu53c96.
|
|
; <SM3> 8/6/92 PW Added removal of BusErrHandler at the termination of the
|
|
; BusErrHandler. It used to be in the second half of Transfer.
|
|
; <SM2> 7/28/92 PW Resolved diffs between Clinton's and Paul's sources.
|
|
; <SM1> 7/25/92 PW Initial check-in.
|
|
;
|
|
;==========================================================================
|
|
|
|
|
|
MACHINE MC68020 ; '020-level
|
|
BLANKS ON ; assembler accepts spaces & tabs in operand field
|
|
PRINT OFF ; do not send subsequent lines to the listing file
|
|
; don't print includes
|
|
|
|
PostNOP EQU 1 ;
|
|
|
|
|
|
LOAD 'StandardEqu.d' ; from StandardEqu.a and for building ROMs
|
|
INCLUDE 'HardwarePrivateEqu.a' ;
|
|
INCLUDE 'UniversalEqu.a'
|
|
INCLUDE 'SysPrivateEqu.a' ;
|
|
INCLUDE 'MC680x0.a'
|
|
INCLUDE 'Debug.a' ; for NAME macro
|
|
|
|
INCLUDE 'ACAM.a'
|
|
INCLUDE 'SCSI.a'
|
|
INCLUDE 'XPTEqu.a'
|
|
INCLUDE 'SCSIEqu53c96.a'
|
|
INCLUDE 'SIMCoreEqu.a'
|
|
|
|
INCLUDE 'HALc96equ.a'
|
|
|
|
PRINT ON ; do send subsequent lines to the listing files
|
|
CASE OBJECT
|
|
|
|
|
|
HALc96Routines PROC EXPORT
|
|
|
|
EXPORT GetReconnectInfo, SizeOfGlobals
|
|
EXPORT GetSelectInfo
|
|
EXPORT WtForFIFOData
|
|
EXPORT Ck4SCSIInt
|
|
EXPORT Wt4SCSIInt
|
|
EXPORT HALIntPoll, Install_ISR
|
|
EXPORT ClearVIASCSIIRQ, EnableVIASCSIIRQ, DisableVIASCSIIRQ, TestVIASCSIIE
|
|
EXPORT HAL_SingleISR
|
|
EXPORT HAL_DualISR
|
|
EXPORT Ck4DREQ
|
|
EXPORT BusErrHandler96, InstallBEH96, RemoveBEH96
|
|
EXPORT GetInitiatorID, ReadInitiatorID
|
|
EXPORT HandleSelected, Disconnected, UnexpectedDisc
|
|
EXPORT DataDTask,DeferAndWait
|
|
EXPORT ResumeIntRegs, QuickIntCheck, FullIntRegs
|
|
EXPORT ci_jDisptch, ci_jDisptch_Vers
|
|
IMPORT ENQUEUEHEAD, VMEnableUserCode, VMDisableUserCode
|
|
IMPORT DEQUEUETRAP
|
|
|
|
IMPORT RecordEvent, RecordError, AsmInit53c9xHW
|
|
|
|
|
|
WITH HALc96GlobalRecord
|
|
WITH SCSI_IO, HALactions, SCSIPhase
|
|
|
|
;==========================================================================
|
|
|
|
SizeOfGlobals ; required for C to Asm connection because we have no complete C global structure
|
|
;——————————————————————————————————————————————————————————————————————————
|
|
move.l #HALc96GlobalRecord.GlobalSize, D0 ; put global size into return value
|
|
rts ;
|
|
|
|
NAME 'SizeOfGlobals'
|
|
|
|
|
|
;==========================================================================
|
|
|
|
GetReconnectInfo
|
|
;_______________________________________________________________________
|
|
|
|
btst #bReselected, int_rINT(A5) ; reselected int?
|
|
beq.s @ck4selected
|
|
move.w #HALresult.kHALreselected, HALactionPB.result(A4)
|
|
bra.s @valid
|
|
@ck4selected
|
|
btst #bSelected, int_rINT(A5) ; selected maybe?
|
|
beq.s @noSuchThing ; nope, bogus dude!
|
|
move.w #HALresult.kHALselectedAsTarget, HALactionPB.result(A4)
|
|
@valid
|
|
clr.b gotInt(A5) ; we now took care of int
|
|
move.b r_selectingID(A5), d0 ; get ID bits (both ours and theirs)
|
|
move.b rCF1(a3), d1 ; get our ID number
|
|
and.b #$07, d1
|
|
bclr d1, d0 ; clear our ID bit (theirs left)
|
|
move.w #7, d1
|
|
@IDloop
|
|
lsl.b #1, d0
|
|
dbcs d1, @IDloop ; decrement d1 and loop if not CARRY
|
|
bcc.s @noIDonBus ; if no carry then no ID on bus
|
|
; bne.s @extraIDonBus ; if not zero, then an extra ID on bus
|
|
|
|
move.w d1, HALactionPB.selectorID(A4)
|
|
|
|
moveq.l #iPhaseMsk, d3 ; load mask bits for phase value
|
|
and.b rSTA(a3), d3 ; get phase value
|
|
move.b D3, currentPhase(A5)
|
|
cmpi.b #iMsgIn, d3 ; are we in MsgIn phase still?
|
|
bne.s @notMsgInPhase
|
|
move.b #SCSIphase.kMessageInPhaseNACK, currentPhase(A5)
|
|
|
|
btst #bSelected, int_rINT(A5) ; selected int?
|
|
bne.s @exit ; -> we're all done
|
|
;
|
|
; Get the information describing the Reconnect and put into HALactionPB
|
|
;
|
|
move.b r_selectingMsg1(A5), HALactionPB.msg(A4)
|
|
move.b r_selMsgLen(A5), HALactionPB.msgInLen(A4)
|
|
move.b r_selPhase(A5), currentPhase(A5) ; update currentPhase
|
|
|
|
@notMsgInPhase
|
|
@exit
|
|
rts
|
|
|
|
|
|
;
|
|
; Error cases
|
|
;
|
|
@noIDonBus ; no ID on bus (beside ours)
|
|
IfDebugStr 'Reselect with no ID on bus (beside ours)'
|
|
bra.s @errorExit
|
|
|
|
@extraIDonBus ; an extra ID on bus
|
|
IfDebugStr 'Reselect with extra ID on bus'
|
|
bra.s @errorExit
|
|
|
|
@noSuchThing
|
|
IfDebugStr 'GetReconnectInfo has no info'
|
|
@errorExit
|
|
cmp.w #HALresult.kHALselectedAsTarget, HALactionPB.result(A4)
|
|
beq.s @exit ; if select, leave it as is (ignore bogus ID stuff)
|
|
move.w #HALresult.kHALreselectBogus, HALactionPB.result(A4)
|
|
bra.s @exit
|
|
|
|
RTSNAME 'GetReconnectInfo'
|
|
|
|
;_______________________________________________________________________
|
|
GetSelectInfo
|
|
IfDebugStr 'GetSelectInfo called! Huh?'
|
|
moveq #dsIOCoreErr, D0
|
|
_SysError
|
|
rts
|
|
|
|
NAME 'GetSelectInfo'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
|
|
STRING ASIS
|
|
InquiryDataPDM
|
|
dc.b $23 ; Processor type, no physical device currently attached
|
|
dc.b 0 ; no device-type modifier
|
|
dc.b 0 ; might/might not be ISO, ECMA, ANSI standard (not likely)
|
|
dc.b 2 ; inquiry data is SCSI-2 compliant
|
|
dc.b InquiryDataLen-5
|
|
dc.b 0, 0 ; reserved
|
|
dc.b 0 ; no RelAdr, WBus32,16, Sync, Linked, CmdQue, SftRe
|
|
dc.b 'APPLE ' ; vendor identification
|
|
dc.b 'PDM (PDM,CF,CS) '
|
|
dc.b '04.3' ; revision level
|
|
dc.b '{wolfware} & {gecko}'
|
|
InquiryDataLen equ *-InquiryDataPDM
|
|
|
|
RTSNAME 'InquiryDataPDM'
|
|
|
|
InquiryDataCyclone
|
|
dc.b $23 ; Processor type, no physical device currently attached
|
|
dc.b 0 ; no device-type modifier
|
|
dc.b 0 ; might/might not be ISO, ECMA, ANSI standard (not likely)
|
|
dc.b 2 ; inquiry data is SCSI-2 compliant
|
|
dc.b InquiryDataLen-5
|
|
dc.b 0, 0 ; reserved
|
|
dc.b 0 ; no RelAdr, WBus32,16, Sync, Linked, CmdQue, SftRe
|
|
dc.b 'APPLE ' ; vendor identification
|
|
dc.b 'Cyclone '
|
|
dc.b '04.3' ; revision level
|
|
dc.b '{wolfware} & {gecko}'
|
|
|
|
RTSNAME 'InquiryDataCyclone'
|
|
|
|
InquiryDataQuadra
|
|
dc.b $23 ; Processor type, no physical device currently attached
|
|
dc.b 0 ; no device-type modifier
|
|
dc.b 0 ; might/might not be ISO, ECMA, ANSI standard (not likely)
|
|
dc.b 2 ; inquiry data is SCSI-2 compliant
|
|
dc.b InquiryDataLen-5
|
|
dc.b 0, 0 ; reserved
|
|
dc.b 0 ; no RelAdr, WBus32,16, Sync, Linked, CmdQue, SftRe
|
|
dc.b 'APPLE ' ; vendor identification
|
|
dc.b 'Quadra '
|
|
dc.b '04.3' ; revision level
|
|
dc.b '{wolfware} & {gecko}'
|
|
|
|
RTSNAME 'InquiryDataQuadra'
|
|
|
|
InquiryDataTNT
|
|
dc.b $23 ; Processor type, no physical device currently attached
|
|
dc.b 0 ; no device-type modifier
|
|
dc.b 0 ; might/might not be ISO, ECMA, ANSI standard (not likely)
|
|
dc.b 2 ; inquiry data is SCSI-2 compliant
|
|
dc.b InquiryDataLen-5
|
|
dc.b 0, 0 ; reserved
|
|
dc.b 0 ; no RelAdr, WBus32,16, Sync, Linked, CmdQue, SftRe
|
|
dc.b 'APPLE ' ; vendor identification
|
|
dc.b 'TNT '
|
|
dc.b '04.3' ; revision level
|
|
dc.b 'wolfwaregeckocraiger'
|
|
|
|
RTSNAME 'InquiryDataTNT'
|
|
|
|
|
|
SenseData
|
|
dc.b $70 ; current error
|
|
dc.b 0 ; no segment number
|
|
dc.b $05 ; Sense Key = Illegal Request
|
|
dc.b 0,0,0,0 ; Information (none)
|
|
dc.b SenseDataLen-8
|
|
dc.b 0,0,0,0 ; Command-specific information
|
|
dc.b $25 ; ASC = Logical Unit Not Supported
|
|
dc.b 0 ; ASCQ = 0
|
|
dc.b 0 ; no FRU code
|
|
dc.b 0,0,0 ; sense-key specific (none)
|
|
SenseDataLen equ *-SenseData
|
|
|
|
RTSNAME 'SenseData'
|
|
|
|
STRING PASCAL
|
|
|
|
;_______________________________________________________________________
|
|
; HandleSelected( HALg)
|
|
;
|
|
; Inputs: A5 HALg
|
|
;
|
|
; Outputs: none
|
|
;
|
|
;
|
|
;_______________________________________________________________________
|
|
;
|
|
HandleSelected
|
|
IF RECORD_ON THEN
|
|
pea 'Targ' ;
|
|
pea '****' ;
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
;
|
|
; If cmd is 6 bytes long and we handle it, do so, otherwise, send Ck Cond status
|
|
;
|
|
move.b rcvdCommandLen(A5), D0
|
|
beq @returnCkCondition ; no command received -> return ckCond status
|
|
cmp.b #6, D0
|
|
bne @returnCkCondition ; not 6 byte cdb -> return ckCond status
|
|
|
|
move.b rcvdCommand(A5), D0 ; get Command byte
|
|
|
|
beq @DoTURCommand ; 00 = Test Unit Ready
|
|
|
|
cmp.b #$12, D0 ; 12 = Inquiry
|
|
beq.s @DoInquiryCommand
|
|
|
|
cmp.b #$03, D0 ; 03 = Request Sense
|
|
beq.s @DoRequestSenseCommand
|
|
|
|
bra @returnCkCondition ; unknown
|
|
|
|
;
|
|
; Inquiry command - send Inquiry data
|
|
;
|
|
@DoInquiryCommand
|
|
tst.b rcvdCommand+1(A5) ; test logical unit and EVPD
|
|
bne @returnCkCondition
|
|
tst.b rcvdCommand+2(A5) ; test page code
|
|
bne.s @returnCkCondition ; either nonzero -> return ck cond status
|
|
|
|
moveq.l #0, D1
|
|
move.b rcvdCommand+4(A5), D1 ; get requested count
|
|
cmp.b #InquiryDataLen, D1
|
|
bls.s @inqOK
|
|
move.b #InquiryDataLen, D1
|
|
@inqOK
|
|
; This should be changed to be decoder based
|
|
cmp.b #dmaTypeAMIC, dmaType(A5)
|
|
bne.s @cyclone
|
|
lea InquiryDataPDM, A0 ; point to our inquiry data
|
|
bra.s @sendData
|
|
@cyclone
|
|
cmp.b #dmaTypePSC, dmaType(A5)
|
|
bne.s @quadra
|
|
lea InquiryDataCyclone, A0 ; point to our inquiry data
|
|
bra.s @sendData
|
|
@quadra
|
|
cmp.b #dmaTypeNone, dmaType(A5)
|
|
bne.s @tnt
|
|
lea InquiryDataQuadra, A0 ; point to our inquiry data
|
|
bra.s @sendData
|
|
@tnt
|
|
; cmp.b #dmaTypeGC, dmaType(A5)
|
|
; bne.s @
|
|
lea InquiryDataTNT, A0 ; point to our inquiry data
|
|
bra.s @sendData
|
|
|
|
;
|
|
; Request Sense command - send Sense data
|
|
;
|
|
@DoRequestSenseCommand
|
|
moveq.l #0, D1
|
|
move.b rcvdCommand+4(A5), D1 ; get requested count
|
|
cmp.b #SenseDataLen, D1
|
|
bls.s @senseOK
|
|
move.b #SenseDataLen, D1
|
|
@senseOK
|
|
lea SenseData, A0 ; point to our request sense data
|
|
bra.s @sendData
|
|
|
|
;
|
|
; Send the D0 number of data bytes from address A0 by loading each into the FIFO
|
|
; then issuing the cSendData command and waiting for the interrupt. When complete,
|
|
; send good status and CmdComplete message.
|
|
;
|
|
@sendDataTop
|
|
nop
|
|
move.b (A0)+, rFIFO(A3)
|
|
nop
|
|
tst.b rSTA(A3)
|
|
nop
|
|
move.b #cSendData, rCMD(A3)
|
|
nop
|
|
@1
|
|
tst.b rSTA(A3) ; this is just to avoid ∞ 'Ck4S' events in tape
|
|
bpl.s @1 ; wait for int
|
|
bsr Ck4SCSIInt
|
|
@sendData
|
|
dbra D1, @sendDataTop
|
|
bra.s @returnGoodStatus
|
|
|
|
;
|
|
; Test Unit Ready command - send Check condition status
|
|
;
|
|
@DoTURCommand
|
|
@returnCkCondition
|
|
move.b #$02, rFIFO(A3)
|
|
nop
|
|
move.b #$00, rFIFO(A3)
|
|
nop
|
|
bra.s @TerminateSeq
|
|
|
|
@returnGoodStatus
|
|
move.b #$00, rFIFO(A3) ; status
|
|
nop
|
|
move.b #$00, rFIFO(A3) ; message
|
|
nop
|
|
@TerminateSeq
|
|
move.b #cTerminateSeq, rCMD(A3)
|
|
nop
|
|
@2
|
|
bsr.s Ck4SCSIInt ; completes with FC and Disc interrupts
|
|
beq.s @2
|
|
; DoIntRegs takes care of phase
|
|
rts
|
|
|
|
NAME 'HandleSelected'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; PCto32Bit
|
|
; Inputs: none
|
|
; Destroys: A0, D0
|
|
; Calls: _Translate24To32
|
|
;
|
|
; Function: Converts the PC to a 32-bit address.
|
|
;
|
|
; Routine from EdiskDrvr.a
|
|
;_______________________________________________________________________
|
|
|
|
_Translate24To32 OPWORD $A091 ;
|
|
|
|
PCto32Bit
|
|
move.l (sp), d0 ; put return address in d0
|
|
_Translate24to32 ; convert the address to 32-bit mode
|
|
move.l d0, (sp) ; save the new return address
|
|
rts ;
|
|
|
|
NAME 'PCto32Bit'
|
|
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; WtForFIFOData - wait for 256mS for the data to show up in the FIFO
|
|
;
|
|
; a3 -> SCSI chip read base address - SERIALIZED
|
|
; d0 <- number of bytes in FIFO (zero if timed out)
|
|
; Z <- .eq.=timed out .ne.=bytes available
|
|
;
|
|
WtForFIFOData
|
|
movem.l d1-d2, -(sp)
|
|
move.w TimeSCSIDB, d1 ; get # of DBRAs
|
|
lsl.l #8, d1 ; multiply by 256
|
|
move.l d1, d2 ; ...a 256mS wait
|
|
swap d2
|
|
@1
|
|
moveq.l #mFIFOCount, d0
|
|
and.b rFIFOflags(a3), d0 ; read FIFO flags regr
|
|
dbne d1, @1 ; loop until data or timeout
|
|
dbne d2, @1 ;
|
|
movem.l (sp)+,d1-d2
|
|
rts ;
|
|
|
|
NAME 'WtForFIFOData'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Ck4SCSIInt - just check right now (once) for a SCSI intrp.
|
|
;
|
|
Ck4SCSIInt
|
|
IF RECORD_ON and 1 THEN
|
|
pea 'Ck4S'
|
|
move.l 4(sp), -(sp) ; and caller's addr
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
tst.b gotInt(A5) ; if there was already an interrupt waiting
|
|
bne.s @gotSCSIInt ;
|
|
|
|
bsr DoIntRegs ; check bINT bit rSTA for c96 interrupt
|
|
tst.b gotInt(A5) ; check for an int that we care about
|
|
beq.s @noSCSIInt ;
|
|
|
|
@gotSCSIInt
|
|
bsr GotSCSIInt
|
|
moveq.l #1, D0
|
|
rts
|
|
@noSCSIInt
|
|
moveq.l #0, D0
|
|
rts
|
|
|
|
NAME 'Ck4SCSIInt'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Wt4SCSIInt - infinite loop to wait for a SCSI intrp. <SM7> PDW (the whole routine changed)
|
|
;
|
|
; Uses: D0, A0, A1, D5 (return)
|
|
;
|
|
; On exit: D5 = rFOS|rINT|rSQS|rSTA, byte values from the Seq.Step, Status & INT regrs.
|
|
;
|
|
; CCR.Z, (EQ) means NOT Disconnect interrupt (i.e. either FC or BS)
|
|
; CCR.z, (NE) Disconnect interrupt
|
|
|
|
allOurRegs REG D0-D7/A0-A6
|
|
numAllRegs equ 15
|
|
|
|
Wt4SCSIInt
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Wt4S'
|
|
move.l 4(sp), -(sp) ; and caller's addr
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
tst.b gotInt(A5) ; if reselect occurred during select prelims
|
|
bne.w GotSCSIInt ; we would have already had an interrupt
|
|
|
|
@noIntYet
|
|
; ---- save regs on stack
|
|
movem.l allOurRegs, -(sp)
|
|
|
|
bsr DoIntRegs ; check bINT bit rSTA for c96 interrupt
|
|
move.w sr, D0
|
|
ori.w #$0700, sr ; \/ \/ \/ Block Ints \/ \/ \/
|
|
tst.b gotInt(A5) ; if there was no int we should care about
|
|
beq.s GoAsyncDude ; bra - go asynchronous (rts to client)
|
|
move.w D0, sr ; if we got an int, Unblock ints /\ /\ /\
|
|
movem.l (sp)+, allOurRegs
|
|
bra.w GotSCSIInt
|
|
|
|
nop
|
|
RTSNAME 'Wt4SCSIInt'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
GoAsyncDude
|
|
bset #waiting4int, intFlags(A5) ; defer if it comes in now
|
|
move.w D0, sr ; /\ /\ /\ Unblock ints /\ /\ /\
|
|
|
|
GoAsyncNeck
|
|
move.l sp, suspendedSP(A5) ; suspend SCSI thread
|
|
IF DEBUGGING THEN
|
|
move.b #2, privStackState(A5)
|
|
ENDIF
|
|
IF STACK_RECORD_ON THEN
|
|
pea 'Pub<'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
move.l publicSP(A5), sp ; resume previous thread
|
|
move.l publicStkLowPt(A5), StkLowPt ; resume stack sniffing
|
|
IF STACK_RECORD_ON THEN
|
|
pea '<Pub'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
moveq.l #0, D0
|
|
rts ; return to HALaction (or MyDT if already deferred)
|
|
|
|
NAME 'GoAsyncDude'
|
|
|
|
|
|
DeferAndWait
|
|
movem.l d0-d2/a0-a2,-(sp) ; save some regs
|
|
jsr DeferAndWaitInner
|
|
movem.l (sp)+,d0-d2/a0-a2 ; restore some regs
|
|
rts
|
|
; Defer further actions until interrupts are disabled. This is mainly used for data transfer
|
|
; which really screws up interrupt latency on pseudo DMA machines.
|
|
; IMPORTANT!
|
|
; We are using our own deferred task manager!! The real one doesn't operate with VMUserCode
|
|
; disabled. The jDisptch vector does get called however if there is something in the queue.
|
|
; Therefore we just stuff an entry in the DTQueue which points at an RTS and then when jDisptch
|
|
; gets called we take over and run our real deferred task regardless of whether user code is
|
|
; disabled.
|
|
DeferAndWaitInner
|
|
WITH SCSIGlobalsRec
|
|
move.w sr, D0 ; what interrupt level are we?
|
|
move.w D0, D1
|
|
and.w #$700, D1
|
|
beq.w ForgetIt ; if 0 -> getouttahere
|
|
|
|
btst #fromRealInt,dataDTFlags(a5) ; are we here as a result of a real interruptHandlers.a
|
|
; generated interrupt? ie is it safe to assume that our
|
|
; deferred task is going to be executed?
|
|
beq.w ForgetIt ; nope, don't defer
|
|
|
|
move.l SCSIGlobals, A0 ;
|
|
move.l privDTQHead(A0), D0 ; is Q empty?
|
|
bne.w ForgetIt ; no -> don't defer
|
|
|
|
btst #InDTQ, privDTQFlags(A0) ; already in dispatcher?
|
|
bne.w ForgetIt ; yes, can't defer
|
|
|
|
JustDoIt
|
|
IF RECORD_ON THEN
|
|
pea 'DtDf'
|
|
move.l a0, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
tst.l DTskQHdr ; Is there something in the queue?
|
|
bne.b @bogus_is_busy ; nope, no need to enqueue it then
|
|
|
|
lea DTQueue, A1 ; get ptr to real DT queue
|
|
lea dataDT_Null(A5), A0 ; get the DT task version that does nothing
|
|
; except mark dataDeferStuff clear
|
|
bsr.l ENQUEUEHEAD ; add the element
|
|
|
|
@bogus_is_busy
|
|
move.l SCSIGlobals, A0 ;
|
|
|
|
lea privDTQueue(A0), A1 ; get ptr to our private queue
|
|
lea dataDT(A5), A0 ; Get our deferred task record
|
|
|
|
bset.b #pendingDTask, dataDTFlags(a5) ; Remember that a deferred task is pending
|
|
|
|
bsr.l ENQUEUEHEAD ; go add element
|
|
|
|
movem.l allOurRegs, -(sp)
|
|
bra.w GoAsyncNeck
|
|
|
|
DataPollDTask
|
|
move.l a5, -(sp)
|
|
|
|
move.w sr, -(sp) ; for use down below
|
|
ori.w #$0700, sr ; \/ \/ \/ Block Ints \/ \/ \/
|
|
|
|
bclr.b #pendingDTask, dataDTFlags(a5) ; Should we be here?
|
|
beq.b @noGo ; nope, don't do anything
|
|
|
|
DisableSCSIIRQ ; disable scsi ints
|
|
move.w (sp)+, sr ; /\ /\ /\ Unblock ints /\ /\ /\
|
|
|
|
movem.l allOurRegs, -(sp) ; save all of our registers
|
|
|
|
;a5 is already setup by HalIntPoll
|
|
|
|
jsr DataDTaskInner ; Magic Stack Switch!
|
|
|
|
; Since we may have cleared an interrupt for the OTHER bus (9x0) at the VIA we need to
|
|
; poll for interrupts on the other bus until we don't have any more.
|
|
|
|
cmp.b #SHARED_VIA,intTypeSCSI(A5) ; is this a 900?
|
|
bne.b @noShare ; nope, good we don't have to do this
|
|
|
|
bclr #fromRealInt,dataDTFlags(A5) ; remember it is NOT safe to defer our data xfer routine
|
|
|
|
@0
|
|
move.l otherHALg(A5), A5
|
|
move.l baseRegAddr(A5), A3 ; A3 points to c96
|
|
|
|
bsr.w IntCommonFromDeferred
|
|
|
|
tst.b D0
|
|
bne.s @0
|
|
|
|
@1
|
|
move.l otherHALg(A5), A5
|
|
move.l baseRegAddr(A5), A3 ; A3 points to c96
|
|
|
|
bsr.w IntCommonFromDeferred
|
|
|
|
tst.b D0
|
|
bne.s @0 ; repeat until there are 2 no-ints in a row
|
|
@noShare
|
|
|
|
movem.l (sp)+, allOurRegs ; restore our registers
|
|
|
|
EnableSCSIIRQ ; re-enable ints
|
|
|
|
move.l (sp)+, a5
|
|
|
|
rts ; back to HalIntPoll
|
|
@noGo
|
|
move.w (sp)+, sr ; /\ /\ /\ Unblock ints /\ /\ /\
|
|
move.l (sp)+, a5
|
|
|
|
rts ; back to HalIntPoll
|
|
|
|
DataDTask
|
|
IF RECORD_ON THEN
|
|
pea 'Dts>'
|
|
move.l a5, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l a5, -(sp)
|
|
move.l A1, A5 ; restore globals from dtParm
|
|
|
|
move.w sr, -(sp) ; for use down below
|
|
movem.l allOurRegs, -(sp) ; save all of our registers
|
|
|
|
; Note that if ints are enabled (ie we are at level 1 or 0 then
|
|
; DisableUserCode/Disable Ints needs to be atomic. Otherwise we
|
|
; could get an interrupt with user code disabled or if we reversed
|
|
; the order of the two we could get a page fault with ints blocked
|
|
; either way it could be bad...
|
|
ori.w #$0700, sr ; \/ \/ \/ Block Ints \/ \/ \/
|
|
|
|
bclr.b #pendingDTask, dataDTFlags(a5) ; Should we be here?
|
|
beq.w @noDT ; nope, don't do anything
|
|
|
|
jsr VMDisableUserCode ; disallow page faults
|
|
DisableSCSIIRQ ; disable ints
|
|
|
|
move.w numAllRegs * 4(sp), sr ; /\ /\ /\ Unblock ints /\ /\ /\
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Dts#'
|
|
move.l a5, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
jsr DataDTaskInner ; Magic Stack Switch!
|
|
|
|
; Since we may have cleared an interrupt for the OTHER bus (9x0) at the VIA we need to
|
|
; poll for interrupts on the other bus until we don't have any more.
|
|
|
|
cmp.b #SHARED_VIA,intTypeSCSI(A5) ; is this a 900?
|
|
bne.b @noShare ; nope, good we don't have to do this
|
|
|
|
bclr #fromRealInt,dataDTFlags(A5) ; remember it is NOT safe to defer our data xfer routine
|
|
|
|
@0
|
|
move.l otherHALg(A5), A5
|
|
move.l baseRegAddr(A5), A3 ; A3 points to c96
|
|
|
|
bsr.w IntCommonFromDeferred
|
|
|
|
tst.b D0
|
|
bne.s @0
|
|
|
|
@1
|
|
move.l otherHALg(A5), A5
|
|
move.l baseRegAddr(A5), A3 ; A3 points to c96
|
|
|
|
bsr.w IntCommonFromDeferred
|
|
|
|
tst.b D0
|
|
bne.s @0 ; repeat until there are 2 no-ints in a row
|
|
|
|
@noShare
|
|
ori.w #$0700, sr ; \/ \/ \/ Block 68K Ints (let DTManager lower it) \/ \/ \/
|
|
|
|
jsr VMEnableUserCode ; allow page faults
|
|
|
|
EnableSCSIIRQ ; re-enable SCSI ints at the VIA
|
|
|
|
@noDT
|
|
|
|
movem.l (sp)+, allOurRegs ; restore our registers
|
|
|
|
move.w (sp)+, d0 ; bit bucket old status register
|
|
; and let the DT Manager restore the int level
|
|
|
|
@noGo
|
|
IF RECORD_ON THEN
|
|
pea 'Dts<'
|
|
move.l a5, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l (sp)+, a5
|
|
|
|
rts ; back to deferred task mgr
|
|
|
|
DataDTaskInner
|
|
move.w sr, D0
|
|
ori.w #$0700, sr ; \/ \/ \/ Block Ints \/ \/ \/
|
|
|
|
IF STACK_RECORD_ON THEN
|
|
pea '>Prv'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l StkLowPt, publicStkLowPt(A5) ; remember old StkLowPt
|
|
clr.l StkLowPt ; and disable stack sniffer
|
|
move.l sp, publicSP(A5) ; suspend interrupt thread
|
|
IF DEBUGGING THEN
|
|
move.b #3, privStackState(A5)
|
|
ENDIF
|
|
move.l suspendedSP(A5), sp ; resume SCSI thread
|
|
|
|
move.w D0, sr ; /\ /\ /\ Unblock ints /\ /\ /\
|
|
|
|
movem.l (sp)+, allOurRegs ; restore regs that we saved in DeferAndWait
|
|
|
|
ForgetIt
|
|
rts
|
|
NAME 'DataDTask'
|
|
|
|
;______________________________________________________________________
|
|
;
|
|
; DisptchTsk Patch, mostly stolen from DeferredTaskMgr.a
|
|
; Regs D0-D3 and A0-A3 are saved prior to call.
|
|
;
|
|
;______________________________________________________________________
|
|
|
|
; This is a patch to the jDisptch vector for the Deferred Task Manager. It works exactly like
|
|
; the real deferred task manager except that it doesn't check inVBL before executing the task.
|
|
; This allows it to work with VM user code disabled running which obviously we need if we are
|
|
; the backing store. If the version tag is in the SCSI globals it is installed. If you want
|
|
; to remove it first look at jDisptch. If it is there remove it and stuff your new one in. If
|
|
; not then somebody else has chained their own patch in. To get rid of it in this case you should
|
|
; change the version number in the globals and use a completely different set of variables in SCSI
|
|
; Globals for the private DT Queue etc. Thus if nothing gets enqueued in the old queue the old code
|
|
; will never run. This saves some "Am I running code" which is good since this gets executed a lot
|
|
; if FileShare or AppleShare is running. Also note that this assumes that nobody is going to do a
|
|
; replacement patch to this vector. If they do it will fail heinously.
|
|
;
|
|
; Note that since we drop the int level after leaving the real deferred task manager we have
|
|
; to check to see if any more got enqueued before leaving here.
|
|
;
|
|
|
|
ci_jDisptch_Vers dc.l 'gcko' ; !!! Change this if you modify the patch!
|
|
ci_jDisptch
|
|
move.l SCSIGlobals, A3 ; get our globals
|
|
bset.b #InDTQ,privDTQFlags(A3) ; already in dispatcher?
|
|
bne.s @Exit ; yup, run real deferred tasks and get out
|
|
bra.b @DspStart ; nope, run our deferred tasks
|
|
@DspLoop
|
|
movea.l D0,A0 ; else setup ptr for use
|
|
lea privDTQueue(A3) ,A1 ; get ptr to queue
|
|
jsr DEQUEUETRAP ; dequeue task to be executed
|
|
movea.l DTAddr(A0),A2 ; get ptr to first task
|
|
movea.l DTParm(A0),A1 ; get optional parameter
|
|
andi.w #$F8FF,SR ; enable all ints
|
|
jsr (A2) ; and go do task
|
|
move.l SCSIGlobals, A3 ; get our globals
|
|
@DspStart
|
|
ori.w #HiIntMask,SR ; disable all ints
|
|
move.l privDTQHead(A3) ,D0 ; get queue head
|
|
bne.s @DspLoop ; loop if tasks exist
|
|
|
|
bclr.b #InDTQ,privDTQFlags(A3) ; clear indicator
|
|
@Exit
|
|
move.l oldjDisptch(A3), A0 ; get the old jDisptch
|
|
jsr (A0) ; and run it.
|
|
|
|
move.l SCSIGlobals, A3 ; get our globals
|
|
btst.b #InDTQ,privDTQFlags(A3) ; are we busy?
|
|
bne.b @rts ; yes, get out!
|
|
tst.l privDTQHead(A3) ; anything more to do?
|
|
bne.b ci_jDisptch ; yep, go do it
|
|
@rts
|
|
rts ; else exit
|
|
NAME 'ci_jDisptch'
|
|
|
|
ENDWITH ;SCSIGlobalsRec
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; GotSCSIInt - Come here when:
|
|
; 1: Ck4SCSIInt call found an int
|
|
; 2: Wt4SCSIInt call found an int already there
|
|
; 3: getRegsBack is 'bra'd to, from Wt4 or MyDT
|
|
;
|
|
GotSCSIInt
|
|
clr.b gotInt(A5) ; we now took care of int
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'GotI'
|
|
move.l A2, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
IF GROSS_CHECK THEN
|
|
btst #bGrossError, int_rSTA(A5)
|
|
beq.s @skipGEdebugger
|
|
DebugStr 'Gross Error in GotSCSIInt'
|
|
@skipGEdebugger
|
|
ENDIF
|
|
|
|
IF PARITY_ENABLED THEN
|
|
btst #bParityError,D5 ; check for parity error
|
|
beq.s @goodParity
|
|
and.b #~mCF1_EnableParity,rCF1(a3) ; reset the parity bit so we don't get more errors
|
|
NOT!
|
|
move.l a0,-(sp) ; !!! Temp until I figure out if A0 needs to be saved
|
|
move.l HALactionPB.ioPtr(A4), A0 ; ptr to SCSI_IO pb
|
|
or.w #kBadParity,SIM_IO.ioEvent(a0) ; set the bad parity bit
|
|
move.l (sp)+,a0
|
|
_debugger
|
|
@goodParity
|
|
ENDIF
|
|
|
|
btst #bDisconnected, int_rINT(A5) ; check for disconnected
|
|
beq.s @exit ; returns .EQ as well
|
|
|
|
move.b #kBusFreePhase, currentPhase(A5) ; returns .NE as well
|
|
@exit
|
|
rts
|
|
|
|
NAME 'GetRegsBack_GotInt'
|
|
|
|
|
|
;——————————————————————————————————————————————————————————————————————————————————————
|
|
;
|
|
; UnexpectedDisc - return from an unexpected disconnect
|
|
;
|
|
UnexpectedDisc
|
|
move.w #SCResults.scsiUnexpectedBusFree, HALactionPB.result(A4)
|
|
rts
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Disconnected - does what needs to be done whenever we get a Disc int from c96
|
|
;
|
|
; Notes: This routine needs to flush the FIFO and then enable reselection.
|
|
; The flush may not be needed but should be safe here. It used to be
|
|
; performed just before the Select command was sent to the chip but that
|
|
; isn't safe because a reselect interrupt could come in while the move.b
|
|
; of the cFlushFIFO is executing. This would cause a flushing of the two
|
|
; reselection bytes - the (re)selection ID and the first message byte before
|
|
; the interrupt routine would have a chance to pull them out of the FIFO.
|
|
;
|
|
; We also set up the currentPhase here to kBusFree because a later
|
|
; reading of the rSTA register cannot determine if the bus is Free or Data—Out.
|
|
;
|
|
Disconnected
|
|
IF RECORD_ON THEN
|
|
pea 'Disc'
|
|
move.l sp, -(sp)
|
|
; move.w #$00, -(sp)
|
|
; move.w busID(A5), -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
RecCmd $01, 'R',$00
|
|
move.b #cFlushFIFO, rCMD(a3) ; Flush FIFO
|
|
nop
|
|
|
|
move.b #0, rSyncOffset(A3) ; no sync data anymore
|
|
and.b #~mCF3_FastSCSI, rCF3(A3) ; clr fast bit
|
|
|
|
IF PARITY_ENABLED THEN
|
|
and.b #~mCF1_EnableParity,rCF1(a3) ; reset the parity bit
|
|
ENDIF
|
|
|
|
RecCmd $44, 'R',$02
|
|
move.b #cEnableR_sel, rCMD(a3) ; enable selection/reselection
|
|
eieio
|
|
|
|
rts
|
|
|
|
NAME 'Disconnected'
|
|
|
|
|
|
|
|
;—————————————————————————————————————————————————————————————————————————————————————
|
|
;
|
|
; HAL_ISR, HALIntPoll
|
|
;
|
|
;—————————————————————————————————————————————————————————————————————————————————————
|
|
;
|
|
CregsToSave REG D3-D7/A2-A6
|
|
NumCRegsToSave EQU 10
|
|
|
|
; Return 1 if we handled a _real_ interrupt or zero if nothing or just a deferred task
|
|
|
|
HALIntPoll
|
|
movem.l CregsToSave, -(sp)
|
|
move.l 4+4*NumCRegsToSave(sp), A5 ; get HALg off of stack (passed by SIMCore)
|
|
move.l baseRegAddr(A5), A3 ; a3 points to c96
|
|
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'ItP0', D0
|
|
add.b 1+busID(A5), D0
|
|
move.l D0, -(sp)
|
|
move.w sr, -(sp)
|
|
move.w $160, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
moveq.l #0, D0 ; default to "didn't handle an interrupt
|
|
|
|
moveq.l #0, D2 ; OR sr with 0 for NO EFFECT
|
|
moveq.l #-1, D3 ; AND sr with FFFF for NO EFFECT
|
|
|
|
; if SCSI IRQ is disabled that means that SIM/HAL doesn't want to be reentered
|
|
|
|
jsr ([HALc96GlobalRecord.jvTestSCSIIE,A5])
|
|
beq.s @skipDTask
|
|
|
|
bclr #fromRealInt,dataDTFlags(A5); remember it is NOT safe to defer our data xfer routine
|
|
bsr.w InterruptCommon
|
|
|
|
; Now check to see if we have a pending deferred task in the deferred task queue. If we do
|
|
; then we need to execute it since we got here from a sync wait loop and the interrupt level
|
|
; may not be reduced any time soon.
|
|
;
|
|
; Also note that UserCode is disabled here which is why there is a separate function for calling
|
|
; the DT from here as opposed to a real Deferred task.
|
|
|
|
|
|
btst.b #pendingDTask, dataDTFlags(a5) ; Are we expecting to execute the dTask?
|
|
beq.b @skipDTask
|
|
move.l D0, -(sp) ; save return from InterruptCommon
|
|
jsr DataPollDTask ; Execute it (and set semaphore so we don't do it again)
|
|
move.l (sp)+, D0 ; and restore it.
|
|
|
|
@skipDTask
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'IP<0', -(sp)
|
|
move.l D0, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
movem.l (sp)+, CregsToSave
|
|
rts
|
|
|
|
RTSNAME 'HALIntPoll'
|
|
|
|
|
|
;————————————————————————————————————
|
|
; Note:IntRegs reg D0-D3/A0-A3 ; registers saved by InterruptHandlers.a
|
|
|
|
regsToSave REG D4-D7/A4-A6
|
|
NumRegsToSave EQU 7
|
|
|
|
HAL_DualISR
|
|
movem.l regsToSave, -(sp)
|
|
|
|
move.l 4+4*NumRegsToSave(sp), A5 ; get HALg off of stack (passed by SIMCore)
|
|
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'Duo>', -(sp)
|
|
move.w sr, -(sp)
|
|
move.w $160, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
@0
|
|
bsr.w HAL_ISRCommon
|
|
|
|
move.l otherHALg(A5), A5
|
|
tst.b D0
|
|
bne.s @0
|
|
|
|
@1
|
|
bsr.s HAL_ISRCommon
|
|
|
|
move.l otherHALg(A5), A5
|
|
tst.b D0
|
|
bne.s @0 ; repeat until there are 2 no-ints in a row
|
|
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'Duo<',-(sp)
|
|
move.l D0, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
movem.l (sp)+, regsToSave
|
|
rts
|
|
|
|
NAME 'HAL_DualISR'
|
|
|
|
HAL_SingleISR
|
|
movem.l regsToSave, -(sp)
|
|
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'Sgl>', -(sp)
|
|
move.w sr, -(sp)
|
|
move.w $160, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
|
|
move.l 4+4*NumRegsToSave(sp), A5 ; get HALg off of stack (passed by SIMCore)
|
|
|
|
bsr.s HAL_ISRCommon
|
|
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'Sgl<', -(sp)
|
|
move.l D0, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
movem.l (sp)+, regsToSave
|
|
rts
|
|
|
|
NAME 'HAL_SingleISR'
|
|
|
|
|
|
HAL_ISRCommon
|
|
IF 0 AND RECORD_ON THEN
|
|
move.l #'ISR0', D0
|
|
add.b 1+busID(A5), D0
|
|
move.l D0, -(sp)
|
|
move.w sr, -(sp)
|
|
move.w $160, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
bset #fromRealInt,dataDTFlags(A5); remember that it IS safe to defer our data xfer routine
|
|
|
|
move.l baseRegAddr(A5), A3 ; A3 points to c96
|
|
; moveq.l #0, D2 ; OR sr with 0 for NO EFFECT
|
|
; moveq.l #-1, D3 ; AND sr with FFFF for NO EFFECT
|
|
move.w #$0700, D2 ; OR sr with 0700 for Block All Ints
|
|
move.w #$FFFF-$0600, D3 ; AND sr with FFFF for Drop to Level 1
|
|
|
|
; fall thru to InterruptCommon
|
|
|
|
;————————————————————————————————————
|
|
InterruptCommon
|
|
bsr.w DoIntRegs
|
|
beq.s @notOurInt ; bra if the intrp isn't for this bus
|
|
|
|
;——— Check if some code is Waiting for this (i.e. Wt4SCSIInt)
|
|
|
|
tst.b gotInt(A5) ; if there was no int we should care about
|
|
beq.s @gotAnInt ; bra - exit ISR
|
|
|
|
@check4defer
|
|
bclr #waiting4int, intFlags(A5) ; is Wt4SCSIInt waiting for this?
|
|
beq.s @gotAnInt ; no -
|
|
|
|
DisableSCSIIRQ ; disable ints
|
|
|
|
move.w SR, D0
|
|
move.w D0, -(sp) ; save current interrupt level
|
|
or.w D2, D0 ; goto level 7 (or no change)
|
|
and.w D3, D0 ; then back to level 1 (or no change)
|
|
move.w D0, SR ;
|
|
|
|
bsr MyDT ; HANDLE THE INTERRUPT
|
|
|
|
move.w (sp)+, SR ; return to level 2 (or whatever)
|
|
|
|
EnableSCSIIRQ ; enable ints after we come back out
|
|
@gotAnInt
|
|
moveq.l #1, D0
|
|
rts
|
|
|
|
@notOurInt
|
|
moveq.l #0, D0
|
|
rts
|
|
|
|
NAME 'InterruptCommon'
|
|
|
|
|
|
IntCommonFromDeferred ; Same as above but for use from a deferred task
|
|
; but is simpler since it doesn't change the
|
|
; interrupt levell.
|
|
|
|
bsr.w DoIntRegs
|
|
beq.s @notOurInt ; bra if the intrp isn't for this bus
|
|
|
|
;——— Check if some code is Waiting for this (i.e. Wt4SCSIInt)
|
|
|
|
tst.b gotInt(A5) ; if there was no int we should care about
|
|
beq.s @gotAnInt ; bra - exit ISR
|
|
|
|
@check4defer
|
|
bclr #waiting4int, intFlags(A5) ; is Wt4SCSIInt waiting for this?
|
|
beq.s @gotAnInt ; no -
|
|
|
|
DisableSCSIIRQ ; disable ints
|
|
|
|
bsr MyDT ; HANDLE THE INTERRUPT
|
|
|
|
EnableSCSIIRQ ; enable ints after we come back out
|
|
|
|
@gotAnInt
|
|
moveq.l #1, D0
|
|
rts
|
|
|
|
@notOurInt
|
|
moveq.l #0, D0
|
|
rts
|
|
|
|
NAME 'IntCommonFromDeferred'
|
|
|
|
|
|
;—————————————————————————————————————————————————————————————————————————————————————
|
|
DoIntRegs
|
|
;———————————————————————————————————————————————————————————————————————
|
|
|
|
move.l D1, -(sp) ; save D1
|
|
|
|
move.w SR, -(sp) ; bump up interrupt level to 7 <LW2> pdw
|
|
or.w #$0700, sr
|
|
|
|
;———— Determine if interrupt was for this HBA
|
|
|
|
move.b rSTA(A3), D1 ; test intrp bit of status regr
|
|
bpl itsNotOurInt ; bra if the intrp isn't for this bus
|
|
|
|
;———— Got an interrupt
|
|
|
|
@isOurInt
|
|
cmp.b #EDGE, intSensSCSI(A5) ; if we are EDGE sensitive,
|
|
bne.s @1
|
|
ClearSCSIIRQ ; clear the VIA int source
|
|
@1
|
|
|
|
;———— Get the status and sequence
|
|
|
|
move.b rCMD(A3), D1 ; D1 = x | x | x | rSQS
|
|
;; move.b rSQS(A3), D1 ; D1 = x | x | x | rSQS
|
|
move.b rSQS(A3), selectedRegSQS(A5) ; save rSQS for later (minimize changes)
|
|
lsl.w #8, D1 ; D1 = x | x | rSQS | x
|
|
move.b rSTA(A3), D1 ; D1 = x | x | rSQS | rSTA
|
|
swap D1 ; D1 = rSQS | rSTA | x | x
|
|
move.b rFIFOflags(A3), D1 ; D1 = rSQS | rSTA | x | rFOS
|
|
lsl.w #8, D1 ; D1 = rSQS | rSTA | rFOS | x
|
|
; next move.b clears rSQS, rSTA & rINT
|
|
move.b rINT(A3), D1 ; D1 = rSQS | rSTA | rFOS | rINT
|
|
bne.s @3
|
|
|
|
IfDebugStr 'no c9x interrupt cause bits set'
|
|
moveq #dsIOCoreErr, D0
|
|
_SysError
|
|
|
|
@3
|
|
cmp.b #STICKYBIT, intSensSCSI(A5) ; if we are STICKYBIT sensitive
|
|
bne.s @2
|
|
ClearSCSIIRQ ; clear the VIA int source
|
|
@2
|
|
move.w (sp)+, SR ; return to original level
|
|
|
|
ResumeIntRegs ; If we did a quick int check then we may
|
|
; need to come here if we got something we
|
|
; didn't expect
|
|
IF RECORD_ON THEN
|
|
move.l D0, -(sp)
|
|
move.w sr, D0
|
|
lsr.w #8, D0
|
|
and.l #$F, D0
|
|
add.l #'IRg0', D0
|
|
move.l D0, -(sp)
|
|
move.l D1, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
move.l (sp)+, D0
|
|
ENDIF
|
|
IF 0 AND STACK_RECORD_ON THEN
|
|
pea 'SP= '
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
IF DEBUGGING THEN
|
|
move.l olderRegsRead(A5), oldestRegsRead(A5)
|
|
move.l oldRegsRead(A5), olderRegsRead(A5)
|
|
move.l newRegsRead(A5), oldRegsRead(A5)
|
|
move.l D1, newRegsRead(A5)
|
|
ENDIF
|
|
|
|
|
|
;———— Normal or NonNormal interrupt? ————
|
|
|
|
move.b D1, D0
|
|
and.b #mNonNormalInt, D0
|
|
bne.s @nonNormalInt
|
|
|
|
|
|
;———— A normal interrupt - fastest path
|
|
|
|
@normalInt
|
|
st.b gotInt(A5)
|
|
move.l D1, intRegsRead(A5)
|
|
|
|
@doneWithRegularInt
|
|
moveq.l #iPhaseMsk, d0 ; load mask for phase bits
|
|
and.b rSTA(A3), d0 ; are we in data-in phase?
|
|
move.b D0, currentPhase(A5) ; update currentPhase
|
|
@doneWithInt
|
|
move.l (sp)+, D1 ; get D1 back
|
|
moveq.l #1, D0 ; set return value - "interrupt handled"
|
|
rts
|
|
|
|
|
|
;———— Reselected?
|
|
|
|
@nonNormalInt
|
|
btst #bReselected, D1
|
|
beq.s @ck4Illegal
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Rsl-'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
st.b gotInt(A5)
|
|
move.l D1, intRegsRead(A5)
|
|
|
|
move.b rFIFO(A3), r_selectingID(A5) ; reselecting ID
|
|
|
|
btst #bBusService, D1 ; was phase message in?
|
|
beq.s @gotMsg ; yes -> get message byte
|
|
@noMsg
|
|
clr.b r_selMsgLen(A5) ; no message
|
|
moveq.l #iPhaseMsk, D0 ; calc what phase we are in
|
|
and.b int_rSTA(A5), D0
|
|
move.b D0, r_selPhase(A5)
|
|
bra.s @callReselectISR
|
|
@gotMsg
|
|
move.b rFIFO(A3), r_selectingMsg1(A5) ; 1st msg byte from recon sequence
|
|
move.b #1, r_selMsgLen(A5)
|
|
move.b #kMessageInPhaseNACK, r_selPhase(A5)
|
|
|
|
@callReselectISR
|
|
move.l HALc96GlobalRecord.SIMstaticPtr(A5), -(sp) ; push parameter (ptr to SIMglobals)
|
|
move.l ReconnectISRptr(A5), A0
|
|
jsr (A0)
|
|
addq.l #4, sp
|
|
|
|
IF RECORD_ON THEN
|
|
pea '-Rsl'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
bra.s @doneWithInt
|
|
|
|
|
|
;———— Illegal Cmd, Selected or ResetDetected?
|
|
|
|
@ck4Illegal
|
|
btst #bIllegalCmd, D1
|
|
beq.s @ck4Select
|
|
move.l D1, illCmdRegsRead(A5)
|
|
bra.s @doneWithInt ; don't do anything with it
|
|
|
|
@ck4Select
|
|
move.l D1, intRegsRead(A5)
|
|
|
|
btst #bSelected, D1
|
|
bne.s selected
|
|
btst #bSelectedWAtn, D1
|
|
bne.s selected
|
|
|
|
|
|
;———— Reset?
|
|
|
|
IF DEBUGGING THEN
|
|
btst #bResetDetected, D1
|
|
IfDebugStr 'SCSI Bus Reset detected'
|
|
ENDIF
|
|
st.b gotInt(A5)
|
|
bsr.w AsmInit53c9xHW
|
|
move.b #kBusFreePhase, currentPhase(A5) ; update currentPhase
|
|
bra.w @doneWithInt
|
|
|
|
|
|
itsNotOurInt
|
|
IF RECORD_ON and 0 THEN
|
|
pea 'NoIt'
|
|
move.l A3, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.w (sp)+, SR ; return to original level
|
|
|
|
move.l (sp)+, D1 ; get D1 back
|
|
moveq.l #0, D0
|
|
rts
|
|
|
|
NAME 'DoIntRegs'
|
|
|
|
|
|
|
|
;———— Selected as a Target
|
|
|
|
selected
|
|
st.b gotInt(A5)
|
|
move.b rFIFO(A3), r_selectingID(A5) ; reselecting ID
|
|
|
|
btst #bSelected, D1
|
|
beq.s @SelectWAtn
|
|
;
|
|
; Select W/O Atn - decode sequence step to determine how many command bytes we got
|
|
;
|
|
@SelectWOAtn
|
|
clr.b rcvdMessageLen(A5) ; never get any msg w/o atn
|
|
move.b rFIFO(A3), r_selectingMsg1(A5) ; null msg byte (c96 filler)
|
|
move.b selectedRegSQS(A5), D1 ; get sequence step register
|
|
and.b #7, D1
|
|
@WOsqs0
|
|
bne.s @WOsqs1to7
|
|
clr.b rcvdCommandLen(A5) ; didn't get any cmd bytes
|
|
bra.w @sqsDn
|
|
@WOsqs1to7
|
|
bra.w @getCommandBytes ; get however many command bytes
|
|
|
|
;
|
|
; Select W/Atn - decode sequence to determine how many command and msg bytes we got
|
|
;
|
|
@SelectWAtn
|
|
move.b rFIFO(A3), r_selectingMsg1(A5) ; 1st msg byte
|
|
move.b r_selectingMsg1(A5), rcvdMessage(A5) ; store here also
|
|
move.b selectedRegSQS(A5), D1 ; get sequence step register
|
|
and.b #7, D1
|
|
@sqs0
|
|
bne.s @sqs1
|
|
move.b #1, rcvdMessageLen(A5) ; only 1 message byte
|
|
clr.b rcvdCommandLen(A5) ; and no command bytes
|
|
bra.s @sqsDn
|
|
@sqs1
|
|
sub.b #1, D1
|
|
bne.s @sqs2
|
|
move.b #1, rcvdMessageLen(A5) ; only 1 message byte
|
|
bra.s @getCommandBytes ; get however many command bytes
|
|
|
|
@sqs2
|
|
sub.b #1, D1
|
|
bne.s @sqs3
|
|
move.b #1, rcvdMessageLen(A5) ; only 1 message byte
|
|
bra.s @getCommandBytes ; get however many command bytes
|
|
|
|
@sqs3
|
|
sub.b #1, D1
|
|
bne.s @sqs4
|
|
bra.s @forceDisconnect ; no such sequence step
|
|
|
|
@sqs4
|
|
sub.b #1, D1
|
|
bne.s @sqs5to7
|
|
moveq.l #mFIFOCount, D0
|
|
and.b rFIFOflags(A3), D0 ; how many bytes in FIFO
|
|
bne.s @sqs4_1
|
|
bra.s @forceDisconnect ; at least 2 message bytes
|
|
@sqs4_1
|
|
sub.b #1, D0 ; 1 byte in FIFO?
|
|
bne.s @sqs4_2 ; no -> must be 2 left
|
|
move.b #2, rcvdMessageLen(A5) ; 2 message bytes total
|
|
move.b rFIFO(A3), rcvdMessage+1(A5) ; 2nd msg byte
|
|
bra.s @sqsDn
|
|
@sqs4_2
|
|
move.b #3, rcvdMessageLen(A5) ; 3 message bytes total
|
|
move.b rFIFO(A3), rcvdMessage+1(A5) ; 2nd msg byte
|
|
move.b rFIFO(A3), rcvdMessage+2(A5) ; 3rd msg byte
|
|
bra.s @sqsDn
|
|
|
|
@sqs5to7
|
|
move.b #3, rcvdMessageLen(A5) ; 3 message bytes total
|
|
move.b rFIFO(A3), rcvdMessage+1(A5) ; 2nd msg byte
|
|
move.b rFIFO(A3), rcvdMessage+2(A5) ; 3rd msg byte
|
|
;; bra.s @getCommandBytes
|
|
|
|
;
|
|
; All remaining bytes in FIFO are the command bytes - get them
|
|
;
|
|
@getCommandBytes
|
|
moveq.l #mFIFOCount, D0
|
|
and.b rFIFOflags(A3), D0 ; how many bytes in FIFO
|
|
move.b D0, rcvdCommandLen(A5)
|
|
lea rcvdCommand(A5), A0
|
|
bra.s @cmdLpBtm
|
|
@cmdLpTop
|
|
move.b rFIFO(A3), (A0)+ ; move cmd byte into rcvdCommand
|
|
@cmdLpBtm dbra D0, @cmdLpTop
|
|
|
|
;
|
|
; At this point, we should have gotten everything out of the FIFO from the Select process
|
|
;
|
|
@sqsDn
|
|
moveq.l #mFIFOCount, D0
|
|
and.b rFIFOflags(A3), D0 ; how many bytes in FIFO
|
|
beq.s @callSIMisr
|
|
@forceDisconnect
|
|
RecCmd cDisconnect, 'R',$04
|
|
move.b #cDisconnect, rCMD(A3)
|
|
bra.w @doneWithInt
|
|
|
|
@callSIMisr
|
|
IF RECORD_ON THEN
|
|
pea 'Tgt-'
|
|
move.l rcvdIDBits(A5), -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l HALc96GlobalRecord.SIMstaticPtr(A5), -(sp) ; push parameter (ptr to SIMglobals)
|
|
move.l ReconnectISRptr(A5), a0
|
|
jsr (a0)
|
|
addq.l #4, sp
|
|
|
|
IF RECORD_ON THEN
|
|
pea '-Tgt'
|
|
move.l rcvdCommandLen(A5), -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
@doneWithInt
|
|
move.l (sp)+, D1 ; get D1 back
|
|
moveq.l #1, D0 ; set return value - "interrupt handled"
|
|
rts
|
|
|
|
RTSNAME 'R_Selected'
|
|
|
|
|
|
QuickIntCheck
|
|
;
|
|
; Fastest possible way to check for an interrupt from a data xfer routine
|
|
; Note we don't clear the int at the VIA in this routine or block ints because
|
|
; we are assuming ints are blocked at the VIA!
|
|
;
|
|
; On Entry
|
|
; d0 -> Expected Int
|
|
;
|
|
; On Exit
|
|
; d0 -> return value
|
|
; d1 -> the int regs
|
|
;
|
|
; returns 1 if we got an int
|
|
; returns 0 otherwise
|
|
|
|
moveq #0, D1
|
|
|
|
move.l #8, D0 ; setup for dbra
|
|
@checkInt
|
|
move.b rSTA(A3), D1 ; D1 = x | x | x | rSTA
|
|
bmi @gotInt ; bra if we got an int
|
|
|
|
dbra d0, @checkInt ; loop until int or we exceed our threshold
|
|
|
|
bra.b @noIntYet
|
|
@gotInt
|
|
lsl.w #8, D1 ; D1 = x | x | rSTA | x
|
|
move.b rFIFOflags(A3), D1 ; D1 = x | x | rSTA | rFOS
|
|
lsl.l #8, D1 ; D1 = x | rSTA | rFOS | x
|
|
; next move.b clears rSQS, rSTA & rINT
|
|
move.b rINT(A3), D1 ; D1 = x | rSTA | rFOS | rINT
|
|
bne.s @3
|
|
|
|
IfDebugStr 'no c9x interrupt cause bits set'
|
|
moveq #dsIOCoreErr, D0
|
|
_SysError
|
|
@3
|
|
move.l #1, D0
|
|
rts
|
|
@noIntYet
|
|
move.l #0, D0
|
|
rts
|
|
|
|
RTSNAME 'QuickIntCheck'
|
|
|
|
FullIntRegs
|
|
IF RECORD_ON THEN
|
|
pea 'FITR'
|
|
move.l a3, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
ClearSCSIIRQ ; clear the VIA int source since we haven't
|
|
; been doing it in QuickIntCheck
|
|
pea @retrn ; return address
|
|
move.l D1, -(sp) ; setup stack correctly for doIntRegs
|
|
jmp ResumeIntRegs
|
|
@retrn bra.w GotSCSIInt ; and handle the int normally
|
|
rts
|
|
RTSNAME 'FullIntRegs'
|
|
|
|
;==========================================================================
|
|
; At this point, the return address (to the Deferred Task Manager) is the
|
|
; only thing on the stack. We first get our globals pointer then restore
|
|
; the stack to where it was when we deferred this thing. Then we just
|
|
; jmp to GetRegsBack and it restores all of our regs. When the task
|
|
; REALLY completes (and does the callback to Machine), it will simply do
|
|
; an RTS which will return back to the Deferred Task Manager.
|
|
;--------------------------------------------------------------------------
|
|
|
|
MyDT
|
|
IF STACK_RECORD_ON THEN
|
|
pea '>Prv'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l StkLowPt, publicStkLowPt(A5) ; remember old StkLowPt
|
|
clr.l StkLowPt ; and disable stack sniffer
|
|
move.l sp, publicSP(A5) ; suspend interrupt thread
|
|
IF DEBUGGING THEN
|
|
move.b #3, privStackState(A5)
|
|
ENDIF
|
|
move.l suspendedSP(A5), sp ; resume SCSI thread
|
|
|
|
IF STACK_RECORD_ON THEN
|
|
pea 'Prv>'
|
|
move.l sp, -(sp)
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
movem.l (sp)+, allOurRegs ; restore regs that we saved in Wt4SCSIInt
|
|
bra.w GotSCSIInt
|
|
|
|
RTSNAME 'MyDT'
|
|
|
|
|
|
;=======================================================================
|
|
|
|
|
|
;_______________________________________________________________________
|
|
; Install ISR
|
|
|
|
Install_ISR
|
|
move.l XPT_ISRptr(A5), A0 ; we need to install XPT's ISR (it calls ours)
|
|
|
|
TestFor VIA2Exists
|
|
bnz.b @via2SCSI
|
|
|
|
@gcSCSI cmp.b #GRAND_CENTRAL,intTypeSCSI(A5)
|
|
bne.b @xInstISR
|
|
lea 0, A1 ; interrupt handler reference constant in A1
|
|
moveq #0, D0
|
|
move.w intOSNumberSCSI(A5), D0
|
|
_GCRegisterHandler
|
|
rts
|
|
|
|
@via2SCSI moveq #0, D0
|
|
move.b intIRQbitNum(A5), D0
|
|
lea VIA2DT, A1
|
|
move.l A0, (A1,D0.W*4) ; called by an intr on the CB2 pin of VIA2
|
|
|
|
@xInstISR rts
|
|
|
|
NAME 'Install_ISR'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
|
|
ClearVIASCSIIRQ
|
|
move.b HALc96GlobalRecord.clearIRQvalue(A5), ([HALc96GlobalRecord.intFlagSCSIAddr,A5])
|
|
rts
|
|
|
|
NAME 'ClearVIASCSIIRQ'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
|
|
EnableVIASCSIIRQ
|
|
move.b HALc96GlobalRecord.enableIRQvalue(A5), ([HALc96GlobalRecord.intEnableSCSIAddr,A5])
|
|
rts
|
|
|
|
NAME 'EnableVIASCSIIRQ'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
|
|
DisableVIASCSIIRQ
|
|
move.b HALc96GlobalRecord.disableIRQvalue(A5), ([HALc96GlobalRecord.intEnableSCSIAddr,A5])
|
|
rts
|
|
|
|
NAME 'DisableVIASCSIIRQ'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
|
|
TestVIASCSIIE
|
|
move.b ([HALc96GlobalRecord.intEnableSCSIAddr,A5]), D0
|
|
and.b HALc96GlobalRecord.testIRQenableValue(A5), D0
|
|
rts
|
|
|
|
NAME 'TestVIASCSIIE'
|
|
|
|
|
|
|
|
;--------------------------------------------------------------------------
|
|
;
|
|
; Wt4DREQorInt - infinite loop to wait for a DREQ signal or SCSI chip intrp.
|
|
; called by DoSelect to determine end of Selection phase (DREQ if success, Int if not)
|
|
;
|
|
; Uses: d3, d5
|
|
;
|
|
; Entry:
|
|
; --> d1 = phase to wait for (concurrent with DREQ)
|
|
;
|
|
; Exit:
|
|
; <-- d5 = rFOS|rINT|0|rSTA, byte values from the Seq.Step, Status & INT regrs.
|
|
; <-- d0 = 1 if DREQ, 0 if Int
|
|
;
|
|
;-----------------
|
|
|
|
Wt4DREQorInt
|
|
@1
|
|
; Check for interrupt first (to avoid unnecessary dog-slow DREQ check)
|
|
bsr Ck4SCSIInt
|
|
bne.s @gotAnInt ;
|
|
|
|
; If no Interrupt, check for DREQ
|
|
bsr.s Ck4DREQ
|
|
beq.s @1 ; no: try again
|
|
|
|
@gotDREQ
|
|
moveq.l #1, d0 ; return value = Got DREQ
|
|
bra.s @exit
|
|
|
|
; Get sequence and FIFO status registers into D5 (already got rSTA)
|
|
@gotAnInt
|
|
moveq.l #0, d0 ; return value = Got Interrup
|
|
|
|
@exit
|
|
rts ;
|
|
|
|
NAME 'Wt4DREQorInt'
|
|
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Ck4DREQ
|
|
; This is slow test. If the value of DREQ
|
|
; needs to be determined in a transfer loop, use something else.
|
|
;
|
|
; Trashes:
|
|
; D0
|
|
;_______________________________________________________________________
|
|
;
|
|
Ck4DREQ
|
|
movem.l D0/D5/A0, -(sp)
|
|
tst.b dreqNeedsSwapMMU(A5)
|
|
bne.s @32bit
|
|
@24bit
|
|
move.l dreqAddr(A5), a0 ; dreqAddr contains DREQ regr address
|
|
move.b (a0), d5 ; read DAFB regr
|
|
bra.s @both
|
|
@32bit
|
|
bsr PCto32Bit ; use pc relative
|
|
moveq #true32B, d0 ; switch to 32-bit mode to look at SCSI config regr
|
|
bsr.s SwapMMU ; (sets up d0 with previous mode)
|
|
|
|
move.l dreqAddr(A5), a0 ; dreqAddr contains DREQ regr address
|
|
move.b (a0), d5 ; read DAFB regr
|
|
|
|
bsr.s SwapMMU ; return to previous mode (in d0)
|
|
@both
|
|
move.b intDREQbitNum(A5), d0
|
|
btst.l d0, d5 ; check for active SCSI DREQ
|
|
movem.l (sp)+, D0/D5/A0
|
|
rts
|
|
|
|
NAME 'Ck4DREQ'
|
|
|
|
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; SwapMMU
|
|
; Swaps MMU into mode specified in D0. Returns previous
|
|
; MMUMode in D0.
|
|
;
|
|
; Registers : D0 affected as above. No others affected.
|
|
;
|
|
;_______________________________________________________________________
|
|
;
|
|
SwapMMU movem.l d1/a0, -(sp)
|
|
jsr ([jSwapMMU]) ; do it, call _SwapMMUMode jump vector(smashes d1/a0)
|
|
movem.l (sp)+, d1/a0
|
|
rts
|
|
|
|
NAME 'SwapMMU'
|
|
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; void ReadInitiatorID( HALg/hwDesc)
|
|
;
|
|
; Registers : D0,
|
|
; A0 are used but not trashed
|
|
;
|
|
;_______________________________________________________________________
|
|
;
|
|
ReadInitiatorID
|
|
clr.b -(sp) ; make room for data
|
|
move.l sp, A0 ; address of PRAM return buffer
|
|
move.l #$00010002, D0 ; read 1 byte of PRAM at offset 2
|
|
_ReadXPRam ; get the SCSI id of the Mac
|
|
moveq.l #0, D0 ; clear D0
|
|
move.b (sp)+, D0 ; get PRAM value in low byte
|
|
|
|
and.b #$07, D0 ; strip away all but ID bits
|
|
|
|
move.l 4(sp), A0 ; only parm is HALg/hwDescPtr
|
|
move.b D0, initiatorID(A0) ; put InitID value into HALglobals
|
|
rts
|
|
|
|
NAME 'ReadInitiatorID'
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; uchar GetInitiatorID( HALg) C function
|
|
;
|
|
; Registers : D0 returns ID.
|
|
;
|
|
;_______________________________________________________________________
|
|
;
|
|
GetInitiatorID
|
|
move.l 4(sp), A1 ; only parm is HALg/hwDescPtr
|
|
moveq.l #0, D0 ; clear D0
|
|
move.b initiatorID(A1), D0 ; return InitID value in low byte
|
|
rts
|
|
|
|
NAME 'GetInitiatorID'
|
|
|
|
|
|
|
|
;___________________________________________________________________________
|
|
;
|
|
; BusErrHandler
|
|
; When the SCSI Mgr is performing a blind data transfer, it patches
|
|
; out the bus error vector. The old SCSI Mgr bus error handler
|
|
; assumed that if it got called, it must be handling a SCSI bus error.
|
|
; Unfortunately, NuBus cards could bus error while the SCSI Mgr is
|
|
; installed. To be a better bus error citizen, the SCSI bus error
|
|
; handler now checks that the fault address is the SCSI chip, and if
|
|
; not, it chains to the bus error handler that it replaced.
|
|
;
|
|
; This code returns control to Transfer and not to the routine
|
|
; caused the bus error. It does this by popping off the buserr stack
|
|
; frame and then doing an RTS, so...
|
|
; DON'T PUT ANYTHING ON THE STACK IN TRANSFER ROUTINES (FastRead,
|
|
; Fast…, etc.). At least don't leave it there during periods where a
|
|
; buserr may be possible.
|
|
;
|
|
; On the off chance that we get a bus error while in the emulator we need
|
|
; to handle 030 style bus error frames as well as 040 style. This implies
|
|
; not doing write backs among other things. Since the info might not always
|
|
; be accurate in an emulated BERR we might consider never trying to resume
|
|
; an instruction but rather always try to RTE out to somewhere else just like
|
|
; a phase change.
|
|
;
|
|
___________________________________________________________________________;
|
|
|
|
WITH AEXFrame,SCSIGlobalsRec
|
|
|
|
longBEXFrameType EQU $0A ; long bus cycle fault stack frame (020/030) !!! put in 680x0.a!
|
|
faultedAdrs EQU $10 ; offset of Faulted Address for 030
|
|
savedRegs REG D0-D2/D5/A0-A1/A5 ; save these registers because we need to use them
|
|
savedRSize EQU 7*4 ; # bytes on stack for saved registers
|
|
|
|
BusErrHandler96
|
|
; Is it our fault? -----
|
|
subq.l #4, sp ; make room for return addr (@notSCSIFault)
|
|
movem.l savedRegs, -(sp)
|
|
lea savedRSize+4(sp), A0 ; make A0 our AEXFrame pointer (regs+1 LW on stack)
|
|
|
|
; XPT should install its BusErrHandler and pass a ptr to the AEXFrame and the SIMg down into
|
|
; the SIM for every one of the SIMs that have registered a BEH. The SIM must then pass the
|
|
; HALg down here. (But we'll cheat for now since only our buses will need this BEH).
|
|
|
|
move.l SCSIGlobals, A1
|
|
|
|
cmp.l berr_halg0(A1),A5 ; are we talking to our bus 0?
|
|
bne.s @ckBus1
|
|
move.l pdmaNonSerlzdAddr(A5),d0 ; get the pseudo DMA Address
|
|
|
|
bfextu FrameType(a0){0:4},d1 ; get format code from stack
|
|
cmp.b #AEXFrameType,d1 ; 040 exception frame?
|
|
beq.b @do040 ;
|
|
|
|
cmp.l faultedAdrs(a0),d0 ; compare with faulted address
|
|
beq.s @SCSIFault ; if so, start processing the bus error
|
|
@do040
|
|
cmp.l FaultAddr(A0),d0 ; compare with faulted address
|
|
beq.s @SCSIFault ; if so, start processing the bus error
|
|
@ckBus1
|
|
cmp.l berr_halg1(A1),A5 ; are we talking to our bus 1?
|
|
bne.s @notSCSIFault
|
|
move.l pdmaNonSerlzdAddr(A5),d0 ; get the pseudo DMA Address
|
|
|
|
bfextu FrameType(a0){0:4},d1 ; get format code from stack
|
|
cmp.b #AEXFrameType,d1 ; 040 exception frame?
|
|
beq.b @do040_again ;
|
|
|
|
cmp.l faultedAdrs(a0),d0 ; compare with faulted address
|
|
beq.s @SCSIFault ; if so, start processing the bus error
|
|
@do040_again
|
|
|
|
cmp.l FaultAddr(A0),d0 ; compare with faulted address
|
|
beq.s @SCSIFault ; if so, start processing the bus error
|
|
|
|
; It's not our fault ------
|
|
|
|
@notSCSIFault
|
|
move.l yeOldeBusErrVct(A1), savedRSize(sp) ; stuff old Bus Error vector
|
|
movem.l (sp)+, savedRegs ; restore regs
|
|
rts ; jump to old handler, assuming it'll RTE
|
|
|
|
NAME 'BusErrHandlerTOP'
|
|
|
|
|
|
;—————————————————————————————————————————————————————————————————————————————————————————————
|
|
; It's all our fault (blame it on us) ------
|
|
|
|
@SCSIFault
|
|
move.w sr, savedSR(a5) ; save sr
|
|
ori.w #$0700, sr ; \/ \/ \/ Block Ints \/ \/ \/
|
|
|
|
; Wait for either DREQ or INT
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Berr' ;
|
|
move.l xPC(a0),-(sp) ;
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.l HALactionPB.ioPtr(A4),a1 ; get the IOPB
|
|
move.l scsiFlags(A1), D0
|
|
and.l #scsiDirectionMask, D0 ; are we data in or out?
|
|
cmp.l #scsiDirectionIn, D0 ; We may not want to do writebacks if we are
|
|
; writing.
|
|
bne.b @notDataIn
|
|
|
|
|
|
@DataIn ;****************************************
|
|
|
|
; clean up the writebacks on the stack frame
|
|
|
|
bfextu FrameType(a0){0:4},d1 ; get format code from stack
|
|
cmp.b #AEXFrameType,d1 ; 040 exception frame?
|
|
bne.b @wtloop ; nope, don't do writebacks...
|
|
|
|
move.w WB1S(A0), d0 ; check WB1 for validity
|
|
move.l WB1A(A0), A1 ; pass WB Address
|
|
move.l WB1D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
|
|
move.w WB2S(A0), d0 ; check WB2 for validity
|
|
move.l WB2A(A0), A1 ; pass WB Address
|
|
move.l WB2D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
|
|
move.w WB3S(A0), d0 ; check WB3 for validity
|
|
move.l WB3A(A0), A1 ; pass WB Address
|
|
move.l WB3D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
|
|
@wtloop
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4DREQ ;
|
|
movem.l (sp)+, D1-D2/A0
|
|
bne.w @doRTE
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4SCSIInt ; see if we have a phase change or something
|
|
movem.l (sp)+, D1-D2/A0
|
|
bne.w @phzChange ;
|
|
bra.b @wtloop
|
|
|
|
@notDataIn ;****************************************
|
|
cmp.l #scsiDirectionOut, D0
|
|
bne.s @noDataXfer
|
|
|
|
@1
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4DREQ ;
|
|
movem.l (sp)+, D1-D2/A0
|
|
bne.s @doWBacks
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4SCSIInt ; see if we have a phase change or something
|
|
movem.l (sp)+, D1-D2/A0
|
|
bne.w @phzChange ;
|
|
bra.b @1
|
|
|
|
@doWBacks
|
|
bfextu FrameType(a0){0:4},d1 ; get format code from stack
|
|
cmp.b #AEXFrameType,d1 ; 040 exception frame?
|
|
bne.b @doRTE ; nope, don't do writebacks...
|
|
|
|
; clean up the writebacks on the stack frame
|
|
|
|
move.w WB1S(A0), d0 ; check WB1 for validity
|
|
move.l WB1A(A0), A1 ; pass WB Address
|
|
move.l WB1D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
@2
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4DREQ ;
|
|
movem.l (sp)+, D1-D2/A0
|
|
beq.s @2
|
|
|
|
move.w WB2S(A0), d0 ; check WB2 for validity
|
|
move.l WB2A(A0), A1 ; pass WB Address
|
|
move.l WB2D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
@3
|
|
movem.l D1-D2/A0, -(sp)
|
|
bsr Ck4DREQ ;
|
|
movem.l (sp)+, D1-D2/A0
|
|
beq.s @3
|
|
|
|
move.w WB3S(A0), d0 ; check WB3 for validity
|
|
move.l WB3A(A0), A1 ; pass WB Address
|
|
move.l WB3D(A0), d1 ; pass WB Data
|
|
bsr.w DoWriteBack ; to routine that takes care of it
|
|
bra.b @doRTE ; rte and retry the instruction
|
|
|
|
@noDataXfer ;——————
|
|
IfDebugStr 'In BEH but not Xfer Direction'
|
|
|
|
;
|
|
; if DREQ, retry the transfer -----
|
|
@doRTE
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Ber<' ;
|
|
move.l d4,-(sp) ;
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.w savedSR(a5),sr ; /\ /\ /\ Un Block Ints /\ /\ /\
|
|
movem.l (sp)+, savedRegs ; restore regs
|
|
addq.l #4, sp
|
|
rte ; restart
|
|
|
|
NAME 'BusErrHandlerMID'
|
|
|
|
; if phase change or timeout, cleanup and abort the transfer -----
|
|
; The could happen from a disconnect. We need to calculate the residual length and
|
|
; jump back to the Xfer routine and let it figure it out
|
|
|
|
@phzChange
|
|
movem.l (sp)+, savedRegs ; restore regs
|
|
addq.l #4, sp ; take scratch space off stack
|
|
|
|
; get any leftover bytes out of the FIFO if we were doing a FastRead
|
|
|
|
move.l HALactionPB.ioPtr(A4),a1 ; get the IOPB
|
|
move.l scsiFlags(A1), D0
|
|
and.l #scsiDirectionMask, D0 ; are we data in or out?
|
|
cmp.l #scsiDirectionIn, D0 ; we don't want to grab extra bytes from
|
|
; the fifo if we are writing.
|
|
bne.b @skipLeftovers
|
|
|
|
move.b rFIFOflags(A3), d0 ; get FIFO status - how many bytes in FIFO
|
|
and.w #mFIFOCount, d0 ;
|
|
ror.l #1, d0
|
|
bra.s @btm0
|
|
@top0
|
|
move.w rDMA(A3), (A2)+ ; get word from chip and put into user's buffer
|
|
@btm0
|
|
dbra d0, @top0
|
|
tst.l d0
|
|
bpl.s @4
|
|
move.b rFIFO(A3), (A2)+ ; get byte from chip and put into user's buffer
|
|
@4
|
|
; get rid of excp'n frame and create a throwaway frame for return to Transfer
|
|
|
|
@skipLeftovers
|
|
move.w xSR(sp), d0 ; save SR for new exception frame
|
|
bfextu FrameType(sp){0:4}, d1 ; get format code from stack
|
|
|
|
cmp.b #AEXFrameType, d1 ; check for 040 Access Error Exception Frame
|
|
beq.s @Drop040XFrame ; dispose of 040 AE exception frame
|
|
cmp.b #shortBEXFrameType, d1 ; short 020/030 exception frame?
|
|
bne.s @Drop46w ; no, so use larger frame
|
|
adda.w #shortBEXFrameSize, sp ; dispose of the 16-word frame
|
|
bra.s @DummyFrame ; and finish up
|
|
|
|
@Drop040XFrame ; 040 Bus Error frame-cleaning done here
|
|
add.w #aeXFrameSize, sp ; remove 040 Access Error Exception Frame
|
|
bra.s @DummyFrame ; and create dummy return frame
|
|
|
|
@Drop46w
|
|
add.w #46*2, sp ; size of exception frame
|
|
|
|
@DummyFrame
|
|
; Fake a format code 0 exception frame (4 words) to finish cleaning up
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Ber$' ;
|
|
move.l d4,-(sp) ;
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
move.w savedSR(a5),sr ; /\ /\ /\ Un Block Ints /\ /\ /\
|
|
|
|
clr.w -(sp) ; format code 0
|
|
pea FinishErr ; PC value
|
|
move.w d0, -(sp) ; sr value
|
|
rte ; 'return' from the fake exception
|
|
|
|
NAME 'BusErrHandlerBOTTOM'
|
|
|
|
|
|
;-----------------
|
|
FinishErr
|
|
;-----------------
|
|
swap d6 ; d6 contains the number of 64k xfers
|
|
move.w #0, D6 ; we only care about the high word
|
|
moveq #0,d0
|
|
move.b rXCM(A3),d0 ; TC regr (most-sig. byte)
|
|
lsl.l #8, d0 ; shift to get lower byte from the c96
|
|
move.b rXCL(A3),d0 ; TC regr (least-sig. byte) <- d4.b
|
|
add.l d0,d6 ; and get the total number of bytes left
|
|
|
|
move.l d6,d1 ; return the number of bytes xferred
|
|
|
|
move.l HALactionPB.ioPtr(A4),a1 ; get the IOPB
|
|
move.l scsiFlags(A1), D0
|
|
and.l #scsiDirectionMask, D0 ; are we data in or out?
|
|
cmp.l #scsiDirectionIn, D0 ; We don't need to check the FIFO if we
|
|
; are reading.
|
|
beq.b @ErrorDone
|
|
|
|
moveq.l #mFIFOCount, D0
|
|
and.b rFIFOflags(A3), D0 ; how many bytes in FIFO
|
|
move.b #cFlushFIFO, rCMD(A3) ; we should flush them out of there
|
|
|
|
add.l D0,D1 ; we didn't transfer these bytes
|
|
sub.l D0,A2 ; adjust the buffer ptr as well
|
|
|
|
@ErrorDone
|
|
moveq.l #scPhaseErr, d0 ; return premature phase change
|
|
bsr.w RemoveBEH96
|
|
|
|
IF RECORD_ON THEN
|
|
pea 'Ber!' ;
|
|
move.l d1,-(sp) ;
|
|
bsr RecordEvent
|
|
addq.l #8, sp
|
|
ENDIF
|
|
|
|
rts ; return status in d0 to the Transfer routine
|
|
|
|
NAME 'FinishBusErr'
|
|
|
|
;-----------------
|
|
DoWriteBack
|
|
;-----------------
|
|
btst #bValid, d0 ; if this writeback valid?
|
|
beq.s @wbDone ; no - done
|
|
|
|
and.w #SIZE_MSK, d0 ; yes, transfer proper size
|
|
|
|
cmp.w #WB_BYTE, d0
|
|
bne.s @1
|
|
move.B d1, (a1) ; move Byte
|
|
bra.s @wbDone
|
|
@1
|
|
cmp.w #WB_WORD, d0
|
|
bne.s @2
|
|
move.W d1, (a1) ; move Word
|
|
bra.s @wbDone
|
|
@2
|
|
cmp.w #WB_LONG, d0
|
|
bne.s @wbDone
|
|
move.L d1, (a1) ; move LongWord
|
|
@wbDone
|
|
rts
|
|
|
|
ENDWITH
|
|
|
|
NAME 'DoWriteBack'
|
|
|
|
|
|
InstallBEH96
|
|
|
|
WITH SCSIGlobalsRec
|
|
|
|
movem.l D0/A0-A1, -(sp)
|
|
move.l SCSIGlobals, A1
|
|
move.b numBEHs(A1), D0 ; how many installed?
|
|
bne.s @inc ; at least one? -> don't install any more
|
|
move.l BusErrVct, yeOldeBusErrVct(A1) ; save previous Bus Error vector
|
|
lea BusErrHandler96, A0 ; get address of our BEH
|
|
|
|
move.l A0, BusErrVct ; put ours in there
|
|
|
|
@inc add.b #1, D0
|
|
move.b D0, numBEHs(A1)
|
|
movem.l (sp)+, D0/A0-A1
|
|
rts
|
|
|
|
NAME 'InstallBEH96'
|
|
|
|
|
|
RemoveBEH96
|
|
|
|
movem.l D0/A0-A1, -(sp)
|
|
move.l SCSIGlobals, A1
|
|
sub.b #1, numBEHs(A1)
|
|
blt.s @aackBEH
|
|
bne.s @dontRemove
|
|
lea BusErrHandler96, A0 ; get address of our BEH
|
|
cmp.l BusErrVct, A0 ; is current one ours?
|
|
bne.s @dontRemove ; if not, -> don't remove it
|
|
|
|
move.l yeOldeBusErrVct(A1), BusErrVct ; restore previous Bus Error vector
|
|
@dontRemove
|
|
movem.l (sp)+, D0/A0-A1
|
|
rts
|
|
@aackBEH
|
|
_debugger ; AACK! we went negative
|
|
|
|
RTSNAME 'RemoveBEH96'
|
|
|
|
;==========================================================================
|
|
|
|
ENDWITH
|
|
ENDWITH
|
|
|
|
END
|