;
;	File:		IOPKernel.aii
;
;	Contains:	I/O Processor Kernel for SWIM IOP.
;
;	Written by:	Gary G. Davidian
;
;	Copyright:	© 1987-1990 by Apple Computer, Inc., all rights reserved.
;
;	Change History (most recent first):
;
;		 <2>	  1/2/90	GGD		NEEDED FOR ZONE-5: Adding call to IdleLoop to run any IdleTasks
;									that drivers may have.
;	   <1.5>	11/19/89	GGD		Needed for Zone-5 Expanded the code that is just below the
;									interrupt vectors to make saving and restoring state even easier
;									(for fremont diags).
;	   <1.4>	 9/21/89	GGD		Increased the number of BCPBs from 4 to 6. Return zero (equal)
;									in BCPBcompRel if operation is not bcCompare. Moved the
;									PatchWait loop to a fixed address below the interrupt vectors to
;									make it easier for the diagnostics to save and restore the IOP
;									state.
;	   <1.3>	  7/8/89	CCH		Added EASE comments to file.
;	  <•1.2>	 6/15/89	GGD		Updated to use equates for the latest rev of the IOP chip,
;									re-formated tab stops in source.
;	   <1.1>	  2/9/89	GGD		Fixed bug in timer code that could cause a timer to go off much
;									later than requested.
;	   <1.0>	 11/9/88	CCH		Adding to EASE.
;
;	To Do:
;
				eject
				title	'IOP Kernel - I/O Processor Kernel for SWIM IOP'

				machine m65C02
				codechk off
				datachk off
				longa	off
				longi	off
				print	nomdir
				include 'IOPDefs.aii'

				import	InitDrivers
				import	RunIdleTasks
				import	HandleSCCInt
				import	HandleDMA1Int
				import	HandleDMA2Int
				import	HandleXMTMsg2
				import	HandleXMTMsg3
				import	HandleXMTMsg4
				import	HandleXMTMsg5
				import	HandleXMTMsg6
				import	HandleXMTMsg7
				import	HandleRCVMsg1
				import	HandleRCVMsg2
				import	HandleRCVMsg3
				import	HandleRCVMsg4
				import	HandleRCVMsg5
				import	HandleRCVMsg6
				import	HandleRCVMsg7

				entry	NonMaskable
				entry	Reset
				entry	UnknownInt
				entry	PatchWait
				entry	PatchDone
				entry	HandleHSTInt
				entry	HandleTMRInt
				entry	Interrupt
				entry	InitBlockCopyDriver
				entry	InitDelayTimerDriver
				entry	IntDecode
				entry	XMTMsgDecode
				entry	RCVMsgDecode
				entry	MsgNumTimes2
				entry	HandleXMTMsg1
				entry	StartTimer
				entry	StopTimer
				title	'IOP Kernel - Initialization'

				seg 	'vectors'
Vectors 		proc
				entry	PatchWait

;	This code is at a fixed address ($7FE5) below the vectors to allow
;	context saving and restoring of a running IOP (used by diagnostics).

RestoreState	ldx 	#$FF				; stack base is $01FF
				txs 						; initialize stack
				lda 	#'A'				; initialize a-register
				ldy 	#'Y'				; initialize y-register
PatchWait		sei 						; sync up with host, with interrupts disabled
				ldx 	#MessageCompleted	; let host know we can now be patched
				stx 	PatchReq			; update the patch request message state
@WaitLoop		ldx 	PatchReq			; wait for patching to be completed
				assert	Idle=$00
				bne 	@WaitLoop			; loop until patching complete (until Idle)
				jmp 	PatchDone			; patching done, enable interrupts

;	Interrupt Vectors follow at $7FFA

				dc.w	NonMaskable 		; Non-Maskable Interrupt
				dc.w	Reset				; processor reset
				dc.w	Interrupt			; Interrupt Request

				endproc

; Reset and initialization code

				seg 	'code'
Reset			proc						; Reset Handler
				ldx 	#$FF				; stack base is $01FF
				txs 						; initialize stack
				stz 	DMA1Control 		; initialize DMA1
				stz 	DMA2Control 		; initialize DMA2
				stz 	TimerDPLLctl		; initialize TimerHostDPLL
				stz 	HostControl 		; initialize Host Control reg
				stz 	TimerCounterL		; initialize TimerCounterL
				stz 	TimerCounterH		; initialize TimerCounterH
				stx 	InterruptMask		; allow all interrupts
				stx 	InterruptReg		; clear all interrupts

				jsr 	InitBlockCopyDriver
				jsr 	InitDelayTimerDriver
				jsr 	InitDrivers 		; initialize the drivers

				lda 	#'A'				; initialize a-register
				ldy 	#'Y'				; initialize y-register
				entry	PatchDone
PatchDone		cli 						; enable interrupts
IdleLoop		ldx 	#'X'				; initialize x-register
				cpa 	#'A'				; see if a-reg got destroyed
				bne 	IdleRegError		; Crash if a-reg changed
				cpy 	#'Y'				; see if y-reg got destroyed
				bne 	IdleRegError		; Crash if y-reg changed
				cpx 	#'X'				; see if x-reg got destroyed
				bne 	IdleRegError		; Crash if x-reg changed
				tsx 						; get stack pointer
				cpx 	#$FF				; should be at stack base
				bne 	IdleStackError		; Crash if stack incorrect
				stx 	Alive				; Let mom know we're still alive
				jsr		RunIdleTasks		; Run any idle loop tasks that drivers have
				ldx 	PatchReq			; see if host wants to patch iop memory
				assert	Idle=$00
				beq 	IdleLoop			; not a patch request (still Idle)
				jmp		PatchWait			; go to wait loop at fixed address

IdleRegError	brk 						; register destroyed while idle

IdleStackError	brk 						; stack became skewed while idle

				endproc
				title	'IOP Kernel - Interrupt Handlers'

;_______________________________________________________________________
;
;  Routine: 	Interrupt
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	none
;  Cycles:		50 + Interrupt Handler
;  Calls:		Interrupt Handler
;  Called by:	65C02 Hardware
;
;  Function:	Decodes and prioritizes the pending interrupt, and calls
;				the handler for the highest priority interrupt.
;				This routine saves all register state, so that the
;				interrupt handlers may use all registers. Interrupt
;				handlers should return to this routine using an RTS
;				instruction (don't use RTI).
;
;_______________________________________________________________________

				seg 	'code'
Interrupt		proc						; Interrupt Handler

; it takes 7 clocks for the processor get here after the last instruction
; finished executing. (pushing state for RTI to pop).

				pha 						; save reg A
				phx 						; save reg X
				phy 						; save reg Y

				jsr 	CallIntHandler		; handle the interrupt

				ply 						; restore reg Y
				plx 						; restore reg X
				pla 						; restore reg A
				rti 						; restore PSW and PC


CallIntHandler	ldx 	InterruptReg		; get the interrupts
				jmp 	(IntDecode,x)		; run the interrupt handler


; Non-Maskable Interrupt Handler
				entry	NonMaskable
NonMaskable 	brk 						; shouldn't be any non-maskable ints


; Unknown Interrupt Handler

				entry	UnknownInt
UnknownInt		tsx 						; get stack pointer
				lda 	StackPage+6,x		; get the psw from the stack
				bit 	#pswB				; test the b bit
				bne 	BRKinterrupt		; if it was a BRK interrupt
				rts 						; no interrupt found, just ignore it


; If we received a BRK interrupt, we will just loop forever, scanning the stack
; so that we can read the stack on a logic analyzer.

BRKinterrupt	tsx 						; get the stack pointer
ScanStack		inx 						; point to next stack entry
				beq 	BRKinterrupt		; scan the stack forever
				lda 	StackPage,x 		; get the stack data
				bra 	ScanStack			; process next entry

				endproc

;		Interrupt decode and priority table.
;		Interrupt priority order is as follows (highest to lowest)
;		SCC, DMA1, DMA2, TMR, HST
;											;		D D
;											; T H S M M
;											; M S C A A
;											; R T C 2 1
;											; I I I I I
;											; n n n n n
				seg 	'WordConsts'		; t t t t t
IntDecode		proc						; ---------
				dc.w	UnknownInt			; 0 0 0 0 0
				dc.w	HandleDMA1Int		; 0 0 0 0 1
				dc.w	HandleDMA2Int		; 0 0 0 1 0
				dc.w	HandleDMA1Int		; 0 0 0 1 1
				dc.w	HandleSCCInt		; 0 0 1 0 0
				dc.w	HandleSCCInt		; 0 0 1 0 1
				dc.w	HandleSCCInt		; 0 0 1 1 0
				dc.w	HandleSCCInt		; 0 0 1 1 1
				dc.w	HandleHSTInt		; 0 1 0 0 0
				dc.w	HandleDMA1Int		; 0 1 0 0 1
				dc.w	HandleDMA2Int		; 0 1 0 1 0
				dc.w	HandleDMA1Int		; 0 1 0 1 1
				dc.w	HandleSCCInt		; 0 1 1 0 0
				dc.w	HandleSCCInt		; 0 1 1 0 1
				dc.w	HandleSCCInt		; 0 1 1 1 0
				dc.w	HandleSCCInt		; 0 1 1 1 1
				dc.w	HandleTMRInt		; 1 0 0 0 0
				dc.w	HandleDMA1Int		; 1 0 0 0 1
				dc.w	HandleDMA2Int		; 1 0 0 1 0
				dc.w	HandleDMA1Int		; 1 0 0 1 1
				dc.w	HandleSCCInt		; 1 0 1 0 0
				dc.w	HandleSCCInt		; 1 0 1 0 1
				dc.w	HandleSCCInt		; 1 0 1 1 0
				dc.w	HandleSCCInt		; 1 0 1 1 1
				dc.w	HandleTMRInt		; 1 1 0 0 0
				dc.w	HandleDMA1Int		; 1 1 0 0 1
				dc.w	HandleDMA2Int		; 1 1 0 1 0
				dc.w	HandleDMA1Int		; 1 1 0 1 1
				dc.w	HandleSCCInt		; 1 1 1 0 0
				dc.w	HandleSCCInt		; 1 1 1 0 1
				dc.w	HandleSCCInt		; 1 1 1 1 0
				dc.w	HandleSCCInt		; 1 1 1 1 1

				endproc
				title	'IOP Kernel - Host Interrupt Handler'

;_______________________________________________________________________
;
;  Routine: 	jsr 	HandleHSTInt
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Cycles:		40 + 12*(XMTMsgMax+RCVMsgMax) + 28 per service routine called
;  Calls:		Message service routines
;  Called by:	Interrupt
;
;  Function:	Scans the transmit and receive message state lists looking
;				for a message that needs service and calls the service
;				routine for each message that needs service.
;
;_______________________________________________________________________

				seg 	'code'
HandleHSTInt	proc
				lda 	#HSTINT 			; get the host interrupt bit
				sta 	InterruptReg		; clear the host interrupt
				trb 	InterruptMask		; disable host interrupts
				cli 						; allow higher priority interrupts

;				service Transmit Message Completed first

				ldy 	XMTMsgMax			; get highest XMT message number
				lda 	#MessageCompleted	; get the desired message state
XMTMsgLoop		cpa 	XMTMsg1State-1,y	; see if the message completed
				bne 	XMTMsgNext			; if no service needed
				phy 						; preserve loop counter
				ldx 	MsgNumTimes2,y		; convert message number to table index
				sei 						; disable interrupts before calling handler
				jsr 	CallXMTHandler		; call the handler
				cli 						; re-enable interrupts after handler returns
				ply 						; restore the message number
				lda 	#MessageCompleted	; restore desired message state
XMTMsgNext		dey 						; point to previous message
				bne 	XMTMsgLoop			; loop through all messages

;				service Receive New Message Sent last

				ldy 	RCVMsgMax			; get highest RCV message number
				lda 	#NewMessageSent		; get the desired message state
RCVMsgLoop		cpa 	RCVMsg1State-1,y	; see if message just arrived
				bne 	RCVMsgNext			; if no service needed
				phy 						; preserve loop counter
				ldx 	MsgNumTimes2,y		; convert message number to table index
				sei 						; disable interrupts before calling handler
				jsr 	CallRCVHandler		; call the handler
				cli 						; re-enable interrupts after handler returns
				ply 						; restore the message number
				lda 	#NewMessageSent 	; restore desired message state
RCVMsgNext		dey 						; point to previous message
				bne 	RCVMsgLoop			; loop through all messages

				sei 						; disable interrupts
				lda 	#HSTINT 			; get the host interrupt bit
				tsb 	InterruptMask		; re-enable host interrupts
				rts 						; all done


CallXMTHandler	jmp 	(XMTMsgDecode-2,x)	; go to the proper handler

CallRCVHandler	jmp 	(RCVMsgDecode-2,x)	; go to the proper handler

				endproc

;		Transmit message Request Completed Handlers

				seg 	'WordConsts'
XMTMsgDecode	proc
				dc.w	HandleXMTMsg1
				dc.w	HandleXMTMsg2
				dc.w	HandleXMTMsg3
				dc.w	HandleXMTMsg4
				dc.w	HandleXMTMsg5
				dc.w	HandleXMTMsg6
				dc.w	HandleXMTMsg7


;		Receive message Start New Request Handlers

				entry	RCVMsgDecode
RCVMsgDecode	dc.w	HandleRCVMsg1
				dc.w	HandleRCVMsg2
				dc.w	HandleRCVMsg3
				dc.w	HandleRCVMsg4
				dc.w	HandleRCVMsg5
				dc.w	HandleRCVMsg6
				dc.w	HandleRCVMsg7

				endproc


;		Table for computing Message Number * 2

				seg 	'constants'
MsgNumTimes2	proc
				dc.b	0,2,4,6,8,10,12,14

				endproc
				title	'IOP Kernel - Cross Processor Block Copy'

				seg 	'data'
BCPBdef 		record
; Block Copy Parameter Block (BCPB)

MaxBCPBs		equ 	6					; (range 1..8)								<1.4>

BCPBnext		ds.b	1					; next BCPB on list
BCPBstate		ds.b	1					; request state

; BCPB cmd, byteCount, hostAddr, and iopAddr must be filled in before calling MakeBCPBreq
				export	BCPBcmd
				export	BCPBbyteCountH
				export	BCPBbyteCountL
				export	BCPBhostAddrB0
				export	BCPBhostAddrB1
				export	BCPBhostAddrB2
				export	BCPBhostAddrB3
				export	BCPBiopAddrH
				export	BCPBiopAddrL
; BCPB CompRel is returned from host
				export	BCPBcompRel 		; output
BCPBcmd 		ds.b	1					; copy command
BCPBbyteCountH	ds.b	1					; byte count (high)
BCPBbyteCountL	ds.b	1					; byte count (low)
BCPBhostAddrB0	ds.b	1					; HOST RAM Address (byte 0)
BCPBhostAddrB1	ds.b	1					; HOST RAM Address (byte 1)
BCPBhostAddrB2	ds.b	1					; HOST RAM Address (byte 2)
BCPBhostAddrB3	ds.b	1					; HOST RAM Address (byte 3)
BCPBiopAddrH	ds.b	1					; IOP RAM Address (high)
BCPBiopAddrL	ds.b	1					; IOP RAM Address (low)
BCPBcompRel 	ds.b	1					; compare result (relationship)

BCPBpcH 		ds.b	1					; completion routine PC high
BCPBpcL 		ds.b	1					; completion routine PC low
BCPBsaveA		ds.b	1					; reg A save
BCPBsaveX		ds.b	1					; reg X save

BCPBsize		equ 	*-BCPBnext			; bytes per BCPB
				ds.b	BCPBsize*(MaxBCPBs-1)	; allocate remaining BCPBs

nilBCPB 		equ 	$FF 				; nil index value
availBCPBhead	ds.b	1					; head of BCPB free list
bcReqQhead		ds.b	1					; head of BCPB request queue
bcReqQtail		ds.b	1					; tail of BCPB request queue
activeBCPB		ds.b	1					; BCPB currently active
				endr
				title	'IOP Kernel - Init Block Copy Driver'

;_______________________________________________________________________
;
;  Routine: 	jsr 	InitBlockCopyDriver
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, n, v, z, c
;  Cycles:		31 + 14 per BCPB
;  Calls:		none
;  Called by:	Reset
;
;  Function:	Initializes the Block Copy data structures.
;
;  NOTE:		This routine must be called with interrupts DISABLED.
;
;_______________________________________________________________________

				seg 	'code'
InitBlockCopyDriver 	proc
				with	BCPBdef
last			equ 	BCPBsize*(MaxBCPBs-1)

				lda 	#0					; index of first BCPB
				sta 	availBCPBhead		; all BCPBs are now available
				clc 						; clear carry for additions
loop			tax 						; setup index register
				adc 	#BCPBsize			; point to next BCPB
				sta 	BCPBnext,x			; have previous point to next
				cpa 	#last				; check for last BCPB
				blt 	loop				; process next BCPB
				lda 	#nilBCPB			; mark empty lists
				sta 	last+BCPBnext		; mark end of free list
				sta 	bcReqQhead			; request queue head is empty
				sta 	bcReqQtail			; request queue tail is empty
				sta 	activeBCPB			; no BCPB currently active
				rts
				endwith
				endproc
				title	'IOP Kernel - Get BCPB'

;_______________________________________________________________________
;
;  Routine: 	jsr 	GetBCPB
;  Inputs:		none
;  Outputs: 	Y - BCPB index
;				n - 1 if no free BCPBs available
;  Destroys:	A, Y, n, z
;  Cycles:		32 if BCPB was available
;				13 if BCPB was not available
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Returns the index of a free Block Copy Parameter Block
;				(BCPB), and removes it from the list of free BCPBs.
;				A flag is returned to indicate if a free BCPB existed.
;
;  NOTE:		This routine must be called with interrupts DISABLED.
;
;_______________________________________________________________________

				seg 	'code'
GetBCPB 		proc	export				; Get a free BCPB
				with	BCPBdef
				ldy 	availBCPBhead		; get the available list head
				bmi 	exit				; if list empty
				lda 	BCPBnext,y			; get successor
				sta 	availBCPBhead		; update list head
				lda 	#MessageCompleted	; initialize to completed state
				sta 	BCPBstate,y 		; initialize the state
				lda 	#$00				; indicate no completion address
				sta 	BCPBpcH,y			; initialize the PC page
exit			rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Free BCPB'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FreeBCPB
;  Inputs:		Y - BCPB index
;  Outputs: 	none
;  Destroys:	A, n, z, c
;  Cycles:		27
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Adds the BCPB that was passed in, to the list of available
;				BCPBs.	If the BCPB is currently active, the system crashes
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
FreeBCPB		proc	export
				with	BCPBdef
				lda 	availBCPBhead		; get the available list head
				sta 	BCPBnext,y			; link it to end of new entry
				sty 	availBCPBhead		; new entry is new list head
				lda 	BCPBstate,y 		; check the state
				cpa 	#newMessageSent		; see if copy was in progress
				beq 	IsActive			; crash if was active
				rts 						; all done
IsActive		brk 						; FreeBCPB on avtive BCPB
				endwith
				endproc
				title	'IOP Kernel - Wait BCPB done'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WaitBCPBdone
;  Inputs:		Y - BCPB index
;  Outputs: 	none
;  Destroys:	n, z, c
;  Cycles:		54 - if has to wait
;				24 - if no wait needed
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	If the BCPB is complete, this routine simply returns to
;				its caller.  If it is not complete, then it returns to
;				its callers caller, and will return to its caller when
;				the BCPB request has completed.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
WaitBCPBdone	proc	export
				with	BCPBdef
				sta 	BCPBsaveA,y 		; save reg a
				lda 	BCPBstate,y 		; get the state
				cpa 	#MessageCompleted
				beq 	exit				; if already done
				txa 						; prepare to save reg x
				sta 	BCPBsaveX,y 		; save reg x
				lda 	BCPBpcH,y			; see if already waiting
				bne 	MultipleWaits		; crash if already waiting
				pla 						; get pcL
				sta 	BCPBpcL,y			; save pcL
				pla 						; get pcH
				sta 	BCPBpcH,y			; save pcH
exit			lda 	BCPBsaveA,y 		; restore reg a
				rts 						; all done
MultipleWaits	brk 						; multiple waits on BCPB
				endwith
				endproc
				title	'IOP Kernel - Check BCPB done'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CheckBCPBdone
;  Inputs:		Y - BCPB index
;  Outputs: 	z - 1 if BCPB request has completed
;				z - 0 if BCPB request in progress, or not started
;  Destroys:	A, n, c
;  Cycles:		12
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Returns an indication as to whether the BCPB request has
;				completed.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
CheckBCPBdone	proc	export
				with	BCPBdef
				lda 	BCPBstate,y 		; get the state
				cpa 	#MessageCompleted	; see if it is done
				rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Make BCPB request'

;_______________________________________________________________________
;
;  Routine: 	jsr 	MakeBCPBreq
;  Inputs:		Y - BCPB index
;  Outputs: 	none
;  Destroys:	A, n, v, z, c
;  Cycles:		41 - if queue is not empty
;				41 - if queue is empty, but message slot busy
;				122 - if queue is empty, and message slot available
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Sends the block copy request to the host precessor, or queues
;				the request if the host processor is still processing
;				a prior request. The cmd, byteCount, hostAddr, and iopAddr fields
;				of the BCPB must be filled in prior to this call.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
MakeBCPBreq 	proc	export
				with	BCPBdef
				lda 	#nilBCPB			; mark entry as end of list
				sta 	BCPBnext,y			; BCPBnext := nil
				lda 	#NewMessageSent		; get the new message state
				sta 	BCPBstate,y 		; mark the BCPB as active
				lda 	bcReqQhead			; get request queue head
				bmi 	QueueEmpty			; if nobody queued
				tya 						; save BCPB index
				ldy 	bcReqQtail			; get old tail
				sta 	BCPBnext,y			; link to end of queue
				sta 	bcReqQtail			; establish new tail
				rts 						; return with BCPB queued

QueueEmpty		lda 	BCxmtState			; check the message state
				assert	Idle=$00
				beq 	SendReq 			; if idle, send it now
				sty 	bcReqQhead			; otherwise it is Qhead
				sty 	bcReqQtail			; and Qtail
				rts 						; return with BCPB queued

SendReq 		lda 	BCPBcmd,y			; get the command
				sta 	bcReqCmd			; move it to the request packet
				lda 	BCPBbyteCountH,y	; copy the byte count
				sta 	bcReqByteCount+0
				lda 	BCPBbyteCountL,y
				sta 	bcReqByteCount+1
				lda 	BCPBhostAddrB0,y	; copy the host address
				sta 	bcReqHostAddr+0
				lda 	BCPBhostAddrB1,y
				sta 	bcReqHostAddr+1
				lda 	BCPBhostAddrB2,y
				sta 	bcReqHostAddr+2
				lda 	BCPBhostAddrB3,y
				sta 	bcReqHostAddr+3
				lda 	BCPBiopAddrH,y		; copy the IOP address
				sta 	bcReqIopAddr+0
				lda 	BCPBiopAddrL,y
				sta 	bcReqIopAddr+1
				lda 	#NewMessageSent 	; get the new message state
				sta 	BCxmtState			; set the new message state
				sty 	activeBCPB			; remember who it was for
				lda 	#NewMsgSentINT		; prepare to interrupt the host
				sta 	HostControl 		; interrupt the host
				rts
				title	'IOP Kernel - Handle XMT Message 1'

;_______________________________________________________________________
;
;  Routine: 	jsr 	HandleXMTMsg1
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Cycles:		49 - nothing queued, no completion routine
;				73 - nothing queued, has completion routine
;				165 - entry was queued, no completion routine
;				189 - entry was queued, has completion routine
;				+ 3 clocks if bcCompare
;  Calls:		SendReq, BCPB completion handler
;  Called by:	HandleHSTInt
;
;  Function:	Dequeues and sends the next block copy request, if any
;				to the host precessor and then calls the completion
;				routine, if any, for the request that had just completed.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				entry	HandleXMTMsg1		; a block copy has completed
HandleXMTMsg1
				assert	Idle=$00
				stz 	BCxmtState			; Acknowledge the message
				ldy 	activeBCPB			; find who completed
				lda 	BCPBcmd,y			; get the command that completed
				assert	(bcIOPtoHOST>>1)=0
				assert	(bcHOSTtoIOP>>1)=0
				assert	(bcCompare>>1)<>0
				lsr		a					; test for bcCompare
				beq		@compRelDone		; if not compare, return zero result (equal)
				lda 	bcReqCompRel		; get the compare result
@compRelDone	sta 	BCPBcompRel,y		; return it in the BCPB
				lda 	#MessageCompleted	; mark the BCPB as completed
				sta 	BCPBstate,y 		; mark the BCPB as done
				lda 	bcReqQhead			; see if anything to be sent
				bmi 	CheckCompletion		; if nothing queued
				phy 						; save completed BCPB
				tay 						; point to queued BCPB
				lda 	BCPBnext,y			; get next in queue
				sta 	bcReqQhead			; and make it the new head
				jsr 	SendReq 			; send old queue head request
				ply 						; restore completed BCPB
CheckCompletion lda 	BCPBpcH,y			; get high byte of comp. addr
				beq 	NoComp				; if no completion routine
				pha 						; restore return pcH
				lda 	BCPBpcL,y			; get low byte of comp. addr
				pha 						; restore return pcL
				lda 	#$00				; indicate no completion address
				sta 	BCPBpcH,y			; to indicate that the wait completed
				lda 	BCPBsaveA,y 		; restore reg-a
				ldx 	BCPBsaveX,y 		; restore reg-y
NoComp			rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Delay Timer Management'

				seg 	'data'
TMPBdef 		record
; TiMer Parameter Block (TMPB)

MaxTMPBs		equ 	4					; (range 1..15)

TMPBnext		ds.b	1					; next TMPB on list
TMPBstate		ds.b	1					; request state

; TMPBtimeH/L are to be filled in by user before calling StartTMPBtimer
				export	TMPBtimeH
				export	TMPBtimeL
TMPBtimeH		ds.b	1					; High byte of 2 byte timer
TMPBtimeL		ds.b	1					; Low byte of 2 byte timer

TMPBpcH 		ds.b	1					; completion routine PC high
TMPBpcL 		ds.b	1					; completion routine PC low
TMPBsaveA		ds.b	1					; reg A save
TMPBsaveX		ds.b	1					; reg X save

TMPBsize		equ 	*-TMPBnext			; bytes per TMPB
				ds.b	TMPBsize*(MaxTMPBs-1)	; allocate remaining TMPBs

nilTMPB 		equ 	$FF 				; nil index value
availTMPBhead	ds.b	1					; head of TMPB free list
tmReqQhead		ds.b	1					; head of TMPB request queue
tmIndexTemp 	ds.b	1					; temp used for indexing
				endr
				title	'IOP Kernel - Init Delay Timer Driver'

;_______________________________________________________________________
;
;  Routine: 	jsr 	InitDelayTimerDriver
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, n, v, z, c
;  Cycles:		45 + 14 per TMPB
;  Calls:		none
;  Called by:	Reset
;
;  Function:	Initializes the Delay Timer data structures.
;
;  NOTE:		This routine must be called with interrupts DISABLED.
;
;_______________________________________________________________________

				seg 	'code'
InitDelayTimerDriver	proc
				with	TMPBdef
last			equ 	TMPBsize*(MaxTMPBs-1)

				lda 	#0					; index of first TMPB
				sta 	availTMPBhead		; all TMPBs are now available
				clc 						; clear carry for additions
loop			tax 						; setup index register
				adc 	#TMPBsize			; point to next TMPB
				sta 	TMPBnext,x			; have previous point to next
				cpa 	#last				; check for last TMPB
				blt 	loop				; process next TMPB
				lda 	#nilTMPB			; mark empty lists
				sta 	last+TMPBnext		; mark end of free list
				sta 	tmReqQhead			; request queue head is empty
				lda 	#TMRENI 			; disable timer interrupts
				trb 	InterruptMask		; update the interrupt mask
				lda 	#CONT				; prepare to force one shot mode
				trb 	TimerDPLLctl		; reset the bit
				lda 	#$80				; low byte latch is a constant
				sta 	TimerLatchL 		; initialize the low timer latch
				rts
				endwith
				endproc
				title	'IOP Kernel - Get TMPB'

;_______________________________________________________________________
;
;  Routine: 	jsr 	GetTMPB
;  Inputs:		none
;  Outputs: 	Y - TMPB index
;				n - 1 if no free TMPBs available
;  Destroys:	A, Y, n, z
;  Cycles:		34 if BCPB was available
;				13 if BCPB was not available
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Returns the index of a free TiMer Parameter Block
;				(TMPB), and removes it from the list of free TMPBs.
;				A flag is returned to indicate if a free TMPB existed.
;
;  NOTE:		This routine must be called with interrupts DISABLED.
;
;_______________________________________________________________________

				seg 	'code'
GetTMPB 		proc	export				; Get a free TMPB
				with	TMPBdef
				ldy 	availTMPBhead		; get the available list head
				bmi 	exit				; if list empty
				lda 	TMPBnext,y			; get successor
				sta 	availTMPBhead		; update list head
				lda 	#MessageCompleted	; initialize to completed state
				sta 	TMPBstate,y 		; initialize the state
				lda 	#$00				; indicate no completion address
				sta 	TMPBpcH,y			; initialize the PC page
exit			rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Free TMPB'

;_______________________________________________________________________
;
;  Routine: 	jsr 	FreeTMPB
;  Inputs:		Y - TMPB index
;  Outputs: 	none
;  Destroys:	A, n, z, c
;  Cycles:		27
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Adds the TMPB that was passed in, to the list of available
;				TMPBs.	If the TMPB is currently active, the system crashes
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
FreeTMPB		proc	export
				with	TMPBdef
				lda 	availTMPBhead		; get the available list head
				sta 	TMPBnext,y			; link it to end of new entry
				sty 	availTMPBhead		; new entry is new list head
				lda 	TMPBstate,y 		; check the state
				cpa 	#newMessageSent		; see if copy was in progress
				beq 	IsActive			; crash if was active
				rts							; all done
IsActive		brk 						; FreeTMPB on avtive TMPB
				endwith
				endproc
				title	'IOP Kernel - Wait TMPB done'

;_______________________________________________________________________
;
;  Routine: 	jsr 	WaitTMPBdone
;  Inputs:		Y - TMPB index
;  Outputs: 	none
;  Destroys:	n, z, c
;  Cycles:		54 - if has to wait
;				24 - if no wait needed
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	If the TMPB is complete, this routine simply returns to
;				its caller.  If it is not complete, then it returns to
;				its callers caller, and will return to its caller when
;				the TMPB request has completed.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
WaitTMPBdone	proc	export
				with	TMPBdef
				sta 	TMPBsaveA,y 		; save reg a
				lda 	TMPBstate,y 		; get the state
				cpa 	#MessageCompleted
				beq 	exit				; if already done
				txa 						; prepare to save reg x
				sta 	TMPBsaveX,y 		; save reg x
				lda 	TMPBpcH,y			; see if already waiting
				bne 	MultipleWaits		; crash if already waiting
				pla 						; get pcL
				sta 	TMPBpcL,y			; save pcL
				pla 						; get pcH
				sta 	TMPBpcH,y			; save pcH
exit			lda 	TMPBsaveA,y 		; restore reg a
				rts 						; all done
MultipleWaits	brk 						; multiple waits on TMPB
				endwith
				endproc
				title	'IOP Kernel - Check TMPB done'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CheckTMPBdone
;  Inputs:		Y - TMPB index
;  Outputs: 	z - 1 if TMPB request has completed
;				z - 0 if TMPB request in progress, or not started
;  Destroys:	A, n, c
;  Cycles:		12
;  Calls:		none
;  Called by:	Drivers
;
;  Function:	Returns an indication as to whether the TMPB request has
;				completed.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
CheckTMPBdone	proc	export
				with	TMPBdef
				lda 	TMPBstate,y 		; get the state
				cpa 	#MessageCompleted	; see if it is done
				rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Start TMPB timer'

;_______________________________________________________________________
;
;  Routine: 	jsr 	StartTMPBtimer
;  Inputs:		Y - TMPB index
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Cycles:		39 + StartTimer - if timer queue was empty
;				142 + StopTimer + StartTimer +..., otherwise
;  Calls:		StopTimer, StartTimer
;  Called by:	Drivers
;
;  Function:	Starts running the timer in the TMPB.
;				The timeH and timeL fields of the TMPB must be filled in prior
;				to this call.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
StartTMPBtimer	proc	export
				with	TMPBdef
				lda 	#NewMessageSent 	; get the new message state
				sta 	TMPBstate,y 		; mark the TMPB as active
				ldx 	tmReqQhead			; get index of head of list
				bpl 	QueueNotEmpty		; if queue already has entries
				sty 	tmReqQhead			; new entry is queue head
				lda 	#nilTMPB			; mark this entry as end of list
				sta 	TMPBnext,y			; indicate no successor
				lda 	#TMRENI 			; enable timer interrupts
				tsb 	InterruptMask		; update the interrupt mask
				tya 						; get TMPB index
				tax 						; setup TMPB index for StartTimer
				bra 	StartTimer			; start the timer and return

QueueNotEmpty	jsr 	StopTimer			; stop the currently running timer
				bmi 	loop				; if head is negative, begin after it
				ldx 	#tmReqQhead-TMPBnext; fake index to tmReqQhead
loop			stx 	tmIndexTemp 		; remember index of previous TMPB
				lda 	TMPBnext,x			; get index to next TMPB
				tax 						; load index register
				bmi 	exit				; exit if end of list (nil)
				sec 						; set carry for subtract
				lda 	TMPBtimeL,y 		; TMPBtime,y := TMPBtime,y - TMPBtime,x
				sbc 	TMPBtimeL,x
				sta 	TMPBtimeL,y
				lda 	TMPBtimeH,y
				sbc 	TMPBtimeH,x
				sta 	TMPBtimeH,y
				bpl 	loop				; loop until TMPBtime,y is < 0
				clc 						; clear carry for addition
				lda 	TMPBtimeL,y 		; TMPBtime,y := TMPBtime,y + TMPBtime,x
				adc 	TMPBtimeL,x
				sta 	TMPBtimeL,y
				lda 	TMPBtimeH,y
				adc 	TMPBtimeH,x
				sta 	TMPBtimeH,y
				sec 						; set carry for subtract
				lda 	TMPBtimeL,x 		; TMPBtime,x := TMPBtime,x - TMPBtime,y
				sbc 	TMPBtimeL,y
				sta 	TMPBtimeL,x
				lda 	TMPBtimeH,x
				sbc 	TMPBtimeH,y
				sta 	TMPBtimeH,x
exit			txa 						; prepare to store x
				sta 	TMPBnext,y			; add rest of list after new entry
				ldx 	tmIndexTemp 		; get index of entry before new one
				tya 						; prepare to store y
				sta 	TMPBnext,x			; add new entry after beginning of list
				ldx 	tmReqQhead			; get index of head of list
* Falls into StartTimer, which follows below
*				bra 	StartTimer			; start the timer for the list head
				endwith
				endproc
				title	'IOP Kernel - Start Timer'

;_______________________________________________________________________
;
;  Routine: 	jsr 	StartTimer
;  Inputs:		X - tmReqQhead TMPB index
;  Outputs: 	none
;  Destroys:	A, n, v, z, c
;  Cycles:		26 - when timer in range $8000..$FFFF
;				33 - when timer in range $0000..$007F
;				40 - when timer in range $0080..$00FF
;				43/49 - when timer in range $0100..$7FFF
;  Calls:		none
;  Called by:	StartTMPBtimer, HandleTMR2Int, CancelTMPBtimer
;
;  Function:	Loads the hardware timer with the amount of time remaining
;				for the timer at the head of the timer queue, or $0080 if
;				the amount is less than 1, or $7F80 if the value is greater
;				than $007F.  Adjusts the value in the TMPB to reflect the
;				value loaded into the hardware time.  The timer is then
;				started and will interrupt when it reaches zero.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
StartTimer		proc
				with	TMPBdef
				lda 	TMPBtimeH,x 		; get high bits
				bne 	OutOfRangeH 		; if way too big
				lda 	TMPBtimeL,x 		; get low bits
				bmi 	OutOfRangeL 		; not in range $0000..$007F
				stz 	TMPBtimeL,x 		; clear low bits
				sta 	TimerCounterH		; start the counter
				lda 	#$FF				; Set latch to $FF to re-load timer
				sta 	TimerLatchH 		; with $FF when timer runs out
				rts

OutOfRangeH 	bmi 	OutOfRangeNeg		; if negative
				lda 	#$7F				; load timer with $7F80
				sta 	TimerCounterH		; start the counter
				lda 	#$FF				; Set latch to $FF to re-load timer
				sta 	TimerLatchH 		; with $FF when timer runs out
				sec 						; set carry for subtract
				lda 	TMPBtimeL,x 		; get low bits
				sbc 	#$7F				; TMPBtime,x := TMPBtime,x - $007F
				sta 	TMPBtimeL,x
				bcs 	exit				; if no borrow
				dec 	TMPBtimeH,x 		; adjust for borrow
exit			rts


OutOfRangeNeg	stz 	TimerCounterH		; use $0080 if negative
				lda 	#$FF				; Set latch to $FF to re-load timer
				sta 	TimerLatchH 		; with $FF when timer runs out
				rts

OutOfRangeL 	sec 						; set carry for subtract
				sbc 	#$7F				; TMPBtime,x := TMPBtime,x - $007F
				sta 	TMPBtimeL,x
				lda 	#$7F				; load timer with $7F80
				sta 	TimerCounterH		; start the counter
				lda 	#$FF				; Set latch to $FF to re-load timer
				sta 	TimerLatchH 		; with $FF when timer runs out
				rts
				endwith
				endproc
				title	'IOP Kernel - Stop Timer'

;_______________________________________________________________________
;
;  Routine: 	jsr 	StopTimer
;  Inputs:		X - tmReqQhead TMPB index
;  Outputs: 	n - indicates sign of TMPBtimer,x
;  Destroys:	A, v, z, c
;  Cycles:		38..40	+ 9 if close to rollover
;  Calls:		none
;  Called by:	StartTMPBtimer, HandleTMR2Int, CancelTMPBtimer
;
;  Function:	Reads the hardware timer, and updates the head of the
;				timer queue to reflect the actual amount of time remaining.
;				It also returns an indication of the sign of the timer
;				remaining for the queue head.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
StopTimer		proc
				with	TMPBdef
				lda 	TimerCounterL		; read low byte of counter
				cpa 	#$10				; see if close to rollover
				blt 	StopTimer			; wait if close to rollover
				lda 	TimerCounterH		; read high byte of counter
				clc 						; clear carry for addition
				bpl 	ZeroExtended		; if high byte was positive
				adc 	TMPBtimeL,x 		; add to timer remaining for the
				sta 	TMPBtimeL,x 		; timer at head of the timer list
				bcs 	TestResultSign		; if no borrow from high byte
				dec 	TMPBtimeH,x 		; adjust high byte
				rts 						; all done

TestResultSign	lda 	TMPBtimeH,x 		; return the timer sign
				rts 						; all done

ZeroExtended	adc 	TMPBtimeL,x 		; add to timer remaining for the
				sta 	TMPBtimeL,x 		; timer at head of the timer list
				bcc 	TestResultSign		; if no carry into high byte
				inc 	TMPBtimeH,x 		; adjust high byte
				rts 						; all done
				endwith
				endproc
				title	'IOP Kernel - Handle TMR Interrupt'

;_______________________________________________________________________
;
;  Routine: 	jsr 	HandleTMRInt
;  Inputs:		none
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Cycles:		15 + StopTimer + StartTimer, if timer not fully expired
;				60 + StopTimer, if expired, no more queued,
;												no completion handler
;				84 + StopTimer, if expired, no more queued,
;												has completion handler
;				62 + StopTimer + StartTimer, if expired, and more queued
;												no completion handler
;				86 + StopTimer + StartTimer, if expired, and more queued
;												has completion handler
;  Calls:		StopTimer, StartTimer, TMPB completion handler
;  Called by:	Interrupt
;
;  Function:	Handles timer interrupts, determines if the timer at the
;				head of the timer queue has fully expired, and calls the
;				completion handler for that timer if one exists. Start
;				any remaining timers on the timer queue.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
HandleTMRInt	proc						; timer interrupt handler
				with	TMPBdef
				ldx 	tmReqQhead			; get index of head of list
				bmi 	unknownTMRint		; if timer queue is empty
				jsr 	StopTimer			; stop timer and update queue head
				bpl 	StartTimer			; if not expired, restart timer and exit
				phx 						; save pointer to dequeued entry
				lda 	TMPBnext,x			; get next queue entry
				sta 	tmReqQhead			; dequeue head
				bmi 	DisableTimer		; if no successor
				tax 						; get pointer to new queue head
				jsr 	StartTimer			; start next timer
				bra 	RunCompletion		; go run the completion routine
DisableTimer	lda 	#TMRENI 			; disable timer interrupts
				trb 	InterruptMask		; update the interrupt mask
RunCompletion	ply 						; restore pointer to dequeued entry
				lda 	#MessageCompleted	; mark the TMPB as completed
				sta 	TMPBstate,y 		; mark the TMPB as done
				lda 	TMPBpcH,y			; get high byte of comp. addr
				beq 	NoComp				; if no completion routine
				pha 						; restore return pcH
				lda 	TMPBpcL,y			; get low byte of comp. addr
				pha 						; restore return pcL
				lda 	#$00				; indicate no completion address
				sta 	TMPBpcH,y			; to indicate that the wait completed
				lda 	TMPBsaveA,y 		; restore reg-a
				ldx 	TMPBsaveX,y 		; restore reg-y
NoComp			rts 						; all done
unknownTMRint	brk 						; TMR interrupt with empty Q
				endwith
				endproc
				title	'IOP Kernel - Cancel TMPB timer'

;_______________________________________________________________________
;
;  Routine: 	jsr 	CancelTMPBtimer
;  Inputs:		Y - TMPB index
;  Outputs: 	none
;  Destroys:	A, X, Y, n, v, z, c
;  Cycles:		15 - if it is already completed
;				50 - if it is the only running timer
;				98 + StopTimer + StartTimer - if head, with successors
;				65..94+ - if not head
;  Calls:		StopTimer, StartTimer
;  Called by:	Drivers
;
;  Function:	Cancels the timer in the indicated TMPB, and removes it
;				from the timer queue.
;
;  NOTE:		This routine must be called with interrupts DISABLED
;
;_______________________________________________________________________

				seg 	'code'
CancelTMPBtimer proc	export
				with	TMPBdef
				lda 	#MessageCompleted	; prepare to check for done
				cpa 	TMPBstate,y 		; see if already completed
				beq 	exit				; exit if already completed
				sta 	TMPBstate,y 		; indicate completed
				lda 	#$00				; indicate no completion address
				sta 	TMPBpcH,y			; to indicate that the wait completed
				cpy 	tmReqQhead			; see if it is running
				bne 	SearchQueue 		; if not head of queue
				lda 	TMPBnext,y			; check for successor
				bpl 	HasSuccessor		; if not the only entry
				sta 	tmReqQhead			; set head to nil
				lda 	#TMRENI 			; disable timer interrupts
				trb 	InterruptMask		; update the interrupt mask
				rts 						; all done

HasSuccessor	ldx 	tmReqQhead			; get queue head
				jsr 	StopTimer			; stop the head timer
				ldy 	tmReqQhead			; get index of entry to dequeue
				ldx 	TMPBnext,y			; get index of successor
				stx 	tmReqQhead			; successor is new head
				jsr 	AdjustTime			; adjust the delay time
				jmp 	StartTimer			; start new head timer

SearchQueue 	lda 	#tmReqQhead-TMPBnext; fake index to tmReqQhead
				sty 	tmIndexTemp 		; save index of canceled timer
loop			tax 						; load index register
				bmi 	exit				; exit if end of list (nil)
				lda 	TMPBnext,x			; get index to next TMPB
				cpa 	tmIndexTemp 		; check for a match
				bne 	loop				; loop if not a match
				lda 	TMPBnext,y			; prepare to un-link entry
				sta 	TMPBnext,x			; finish unlinking it
				bmi 	exit				; if entry has no successor
				tax
AdjustTime		clc 						; clear carry for addition
				lda 	TMPBtimeL,x 		; TMPBtime,x := TMPBtime,x + TMPBtime,y
				adc 	TMPBtimeL,y
				sta 	TMPBtimeL,x
				lda 	TMPBtimeH,x
				adc 	TMPBtimeH,y
				sta 	TMPBtimeH,x
exit			rts 						; all done
				endwith
				endproc

				end
