	.IF	LIST4
	.LIST
	.PAGE
	.ELSE
	.NOLIST
	.ENDC
;****************************************************************************
;
;	Real Time & Scheduler Routines.
;
;	File:	SAGE.BIOS4.TEXT
;	Date:	23-Mar-83
;	Issue:	2
;
;
;	COPYRIGHT (c) 1982, 1983 SAGE Computer Technology
;	All Rights Reserved
;
;****************************************************************************
;
;	History:
;
;	1     13-Jun-82 Initial release.
;	1A    24-Aug-82 Added ESCHED1 direct entry point.
;	1B    26-Oct-82 Fixed disable and enable of wrong interrupt control
;			bit in RDREAL.	Added trigger of foreground interrupt
;			when new schedule already timed out.
;	2     23-Mar-83 Initial SAGE IV release.
;
;****************************************************************************


;****************************************************************************
;
;	Scheduler Table Entry Format:
;
;	Each entry to the scheduler has three long words.  The address
;	passed to the scheduler is the base of the entry.
;
;	Long word 0:	Reserved for scheduler linkage.
;	Long word 1:	Address of scheduled routine (see detail below).
;	Long word 2:	Relative time of timeout from schedule entry
;			(low word is in 1/64000 second intervals,  high
;			 word is in seconds).
;
;	The scheduled routine is entered with registers D0, D1, D2, A0, A1,
;	and A2 saved on the stack.  On termination of a scheduled routine
;	the program should do an RTS where the registers are
;	restored and the Foreground exit taken.
;
;****************************************************************************

;	Timer constants:
C64000	.WORD	32000.+32000.	;Real time low stage count
C63999	.WORD	32000.+31999.	;Real time low stage count minus one


.PAGE
;****************************************************************************
;
;	Real Time Clock Interrupt
;
;	Driven from 8259 interrupt processing routine.
;	The low word of D0 is saved on the stack.
;
;****************************************************************************

RTINT
	ADDQ.W	#1,CLKOVER	;Advance clock overflow word
	MOVE.B	#00,TIMER+6	;Latch the counter value
	MOVE.B	TIMER,D0	;Get the low byte
	ROL.W	#8,D0
	MOVE.B	TIMER,D0
	ROL.W	#8,D0
	MOVE.B	D0,TIMER	;Restore the value
	ROL.W	#8,D0
	MOVE.B	D0,TIMER
	MOVE.W	(A7)+,D0	;Restore the working register
	RTE


.PAGE
;****************************************************************************
;
;	Scheduler Timer Interrupt.
;
;	Driven from 8259 interrupt processing routine.
;	The low word of D0 is saved on the stack.
;
;****************************************************************************

SCHINT
	BSET	#0,P8259+2	;Disable the scheduler timer channel
	BSET	#FGSCH_B,FGTASKS+1 ;Set request flag for scheduler task
	MOVE.B	#5,SI8255	;Start the foreground interrupt
	MOVE.W	(A7)+,D0	;Restore the working register
	RTE


.PAGE
;****************************************************************************
;
;	Scheduler Timeout Task & Exit Process.
;
;	This is a foreground task initiated by the scheduler interrupt
;	routine when the TIMER has been decremented to zero.
;	The low word of D0 has been saved on the stack.
;
;****************************************************************************

SCHEDTO
	BCLR	#FGSCH_B,FGTASKS+1 ;Clear scheduler task request flag
	MOVEM.L D0-D2/A0-A2,-(A7) ;Save working registers
	MOVEA.L SCH_QUE,A2	;Get address of top entry
	MOVE.L	A2,D0		;Just to set flags
	BEQ.S	$20		;Scheduler queue is empty, don't reenable
	BSR.S	RDREAL		;Get clock value
	CMP.L	8(A2),D1
	BMI.S	$40		;Top entry has not timed out yet, reschedule
	MOVEA.L (A2),A1		;Set up next entry
	MOVE.L	A1,SCH_QUE
	BEQ.S	$10		;No move schedules
	BSR	WTSCH		;Set up timer for next schedule
$10	MOVEA.L 4(A2),A1	;Go to scheduled routine
	JSR	(A1)

;	Scheduler Exit
$20	MOVEM.L (A7)+,D0-D2/A0-A2 ;Restore working registers
	BRA	FGEXIT		;Exit this foreground task

;	Set up next time
$30	MOVEA.L A2,A1
	BSR	WTSCH		;Set up timer value
	BRA.S	$20

;	Reschedule the top entry
$40	MOVE.L	8(A2),D0
	BSR	WTSCH1		;Special schedule entry
	BRA.S	$20

.PAGE
;****************************************************************************
;
;	Read the Real Time Clock Value.
;
;	This routine should always be called from within Foreground.
;	The routine keeps reading the timer ports until a stable value is
;	reached on the middle two bytes.
;
;	Values returned:
;	  D1 (low  word) is from low  counter (modified to count up)
;	  D1 (high word) is from high counter (modified to count up)
;	  D2 (low  word) is from high counter (modified to count up)
;	  D2 (high word) is from the ram extension word.
;
;	Register D0 is also modified
;
;****************************************************************************

RDREAL
	CLR.W	D0
	BSET	#6,P8259+2	;Disable the clock interrupt
	MOVE.B	D0,BRATE+6	;Latch data in first counter
	MOVE.B	D0,TIMER+6	;Latch data in second counter
	TST.B	BRATE		;Throw away low byte of first counter
	MOVE.B	TIMER,D0	;Get low byte of second counter
	MOVE.B	BRATE,D1	;Get high byte of first counter
	MOVE.B	TIMER,D2	;Get high byte of second counter
	ASL.W	#8,D2		;Combine bytes of second counter
	ADD.W	D0,D2
	SWAP	D2		;Get high order overflow word
	MOVE.W	CLKOVER,D2
	SWAP	D2
	MOVE.B	#00,BRATE+6	;Latch first counter again
	MOVE.B	#00,TIMER+6	;Latch second counter again
	MOVE.B	BRATE,D0	;Get low byte of first counter
	CMP.B	TIMER,D2	;Check low byte of second counter
	BNE.S	$10		;Found mismatch, get ready for retry
	CMP.B	BRATE,D1	;Check high byte of first counter
	BNE.S	$20		;Found mismatch, get ready for retry
	ASL.W	#8,D1		;Combine bytes for first counter
	ADD.W	D0,D1
	TST.B	TIMER		;Assume didn't change high byte of second ctr
	BCLR	#6,P8259+2	;Re-enable clock interrupt
	NEG.W	D2
	NEG.W	D1		;D1 := 64000. - D1
	ADD.W	C64000,D1
	SWAP	D1		;Install high word
	MOVE.W	D2,D1
	SWAP	D1
	RTS

;	Get ready for retry
$10	TST.B	BRATE		;Ignore high byte of first counter
$20	TST.B	TIMER		;Ignore high byte of second counter
	BCLR	#6,P8259+2	;Re-enable clock interrupt
	BRA	RDREAL		;Try again


.PAGE
;****************************************************************************
;
;	Read the Real Time Clock in 1/60ths of a second.
;
;	This routine should always be called from within Foreground.
;	The routine divides the low order word range (0 - 63999) into
;	equal 1/60th intervals.	 This is done by first dividing the value
;	by 3200 to get 1/20th intervals.  The remainder of this division
;	is then divided by 1067 to split it into thirds, getting reasonably
;	even 1/60th intervals.	The D2 long word second count is multiplied
;	by 60 and summed with the previous result to get the total number
;	of 1/60th second intervals.
;
;	The long word value is returned in D1.
;
;	Registers D0, D2, and D3 are also modified
;
;****************************************************************************

RDT60
	BSR	RDREAL		;Get real time clock value
	CLR.L	D0
	MOVE.W	D1,D0
	DIVU	#3200.,D0
	CLR.L	D1
	MOVE.W	D0,D1		;D1 := quotient * 3
	ADD.W	D0,D1
	ADD.W	D0,D1
	CLR.W	D0		;Divide remainder by 1067
	SWAP	D0
	DIVU	#1067.,D0
	ADD.W	D0,D1		;Combine low order part
	
	MOVEQ	#60.,D3
	MULU	D2,D3		;Low word * 60.
	SWAP	D2
	MOVEQ	#60.,D0
	MULU	D2,D0		;High word * 60.
	SWAP	D0		;Shift high word result to high position
	CLR.W	D0
	ADD.L	D0,D3		;Combine all parts
	ADD.L	D3,D1		;Final result
	RTS


.PAGE
;****************************************************************************
;
;	Read One Second Counter
;
;	This routine should always be called from within a Foreground
;	activity.  It returns the one second count in D0.  All other
;	registers are preserved.
;
;****************************************************************************

RD1SEC	CLR.W	D0
	MOVE.B	D0,TIMER+6	;Latch data in second counter
	MOVE.B	TIMER,D0	;Get low byte
	ASL.W	#8,D0
	MOVE.B	TIMER,D0	;Get high byte
	ROL.W	#8,D0		;Reverse byte order
	NEG.W	D0		;Value is down counter
	RTS


.PAGE
;****************************************************************************
;
;	Enter a Schedule.
;
;	This routine should always be called from within Foreground.
;
;	On entry: A0 = Pointer to the queue entry to be scheduled.
;
;	All register are preserved.
;
;****************************************************************************

ESCHED
	MOVEM.L D0-D2/A1/A2,-(A7) ;Save working registers
	BSR	RDREAL		;Read the real time clock

;	Direct entry for exact interval reschedule
ESCHED1 
	MOVE.L	D1,D0
	SWAP	D1		;High word of time
	ADD.W	10.(A0),D0	;Add up 1/64000s of a second
	BCC.S	$1		;No carry to high word
	ADD.W	#600H,D0	;Adjust for wrap at 64000
	ADDQ.W	#1,D1		;Carry to high word
$1	CMP.W	C63999,D0
	BLS.S	$5		;No wrap at 64000
	SUB.W	C64000,D0	;Adjust for wrap
	ADDQ.W	#1,D1		;Carry to high word
$5	ADD.W	8(A0),D1	;Add seconds
	SWAP	D0
	MOVE.W	D1,D0		;Install high count
	SWAP	D0
	MOVE.L	D0,8(A0)	;Save timeout time
	MOVEA.L SCH_QUE,A1	;Check if scheduler already active
	MOVE.L	A1,D2		;Just to set flags
	BEQ.S	$50		;Scheduler not currently active
	CMP.L	8(A1),D0
	BMI.S	$50		;Current top entry is superceeded
$10	MOVEA.L A1,A2		;Save address of current entry
	MOVEA.L (A1),A1		;Get address of next entry
	MOVE.L	A1,D2		;Just to set flags
	BEQ.S	$20		;End of queue, append new entry
	CMP.L	8(A1),D0
	BPL.S	$10		;Current entry is before new entry
$20	MOVE.L	A1,(A0)		;Insert new entry into queue
	MOVE.L	A0,(A2)
$30	MOVEM.L (A7)+,D0-D2/A1/A2 ;Restore working registers
	RTS

;	New entry will be first to timeout
$50	MOVE.L	A1,(A0)		;Insert at beginning of queue
	MOVE.L	A0,SCH_QUE
	MOVEA.L A0,A1
	BSR.S	WTSCH		;Apply time to hardware
	BRA.S	$30


.PAGE
;****************************************************************************
;
;	Cancel a Schedule.
;
;	This routine should always be called from within Foreground.
;
;	On entry: A0 = Pointer to the queue entry to be scheduled.
;
;	All register are preserved.
;
;****************************************************************************

CSCHED
	MOVEM.L D0-D2/A1/A2,-(A7) ;Save working registers
	MOVEA.L SCH_QUE,A1	;Check if scheduler active
	MOVE.L	A1,D0		;Just to set flags
	BEQ.S	$20		;Scheduler is inactive
	CMPA.L	A0,A1
	BEQ.S	$30		;Cancel top entry
$10	MOVEA.L A1,A2		;Save address of current entry
	MOVEA.L (A1),A1		;Get address of next entry
	MOVE.L	A1,D0		;Just to set flags
	BEQ.S	$20		;End of queue, cannot find entry
	CMPA.L	A0,A1
	BNE.S	$10		;Target entry not found
	MOVE.L	(A0),(A2)	;Dequeue target entry
$20	MOVEM.L (A7)+,D0-D2/A1/A2 ;Restore working registers
	RTS

;	Target entry is on top of queue
$30	MOVEA.L (A0),A1		;Get address of next entry
	MOVE.L	A1,SCH_QUE	;Put at top of queue
	BEQ.S	$40		;No more entries, cancel scheduler
	BSR.S	WTSCH		;Apply time to hardware
	BRA.S	$20

;	Turn off scheduler timer
$40	BSET	#0,P8259+2
	BRA.S	$20


.PAGE
;****************************************************************************
;
;	Apply Scheduled Time Interval to TIMER.
;
;	If the timeout is >= 2 seconds, the first stage is set up to count
;	seconds and the second stage is loaded with the integral number
;	of seconds (minus 1 for loading count) before the timeout.  If a
;	partial second remains after this timeout, the timer is rescheduled
;	using the method for < 2 seconds.
;
;	If the timeout is < 2 seconds, the first stage is set up to divide
;	by 2 (resulting in a 32000 hz drive to the second stage).  The low word
;	(1/64000 second count) is divided by 2 and if the high word is
;	one second, an additional 32000 counts are added.  This count is then
;	applied to the second stage.
;
;	This routine should always be called from within Foreground.
;
;	On entry: A1 = Pointer to the queue entry to be scheduled.
;
;	Registers D0, D1, and D2 are modified.
;
;	WTSCH1 is a special entry point for SCHEDTO where the current time
;	is already in D1 and the timeout time is in D0.
;
;****************************************************************************

WTSCH
	BSR	RDREAL		;Get current time into D1
	MOVE.L	8(A1),D0

WTSCH1
	SUB.W	D1,D0
	BCC.S	$10		;No borrow
	ADD.W	C64000,D0
	SWAP	D0
	SUBQ.W	#1,D0		;Take away 1 from high word
	BRA.S	$20

$10	SWAP	D0
$20	SWAP	D1
	SUB.W	D1,D0
	BCC.S	$30		;No borrow
;	Already timed out
	BSET	#0,P8259+2	;Turn off timer
$25	BSET	#FGSCH_B,FGTASKS+1 ;Request scheduler activity
	MOVE.B	#5,SI8255	;Force a foreground interrupt in case
				;routine is called in background (with
				;foreground disabled).
	RTS

$30	BSET	#0,P8259+2	;Disable timer interrupt
	MOVE.B	#074H,TIMER+6	 ;Set up first stage with mode 2
	MOVE.B	#0B0H,TIMER+6	;Set up second stage with mode 0
	CMPI.W	#2,D0		;Check if >= 2 seconds
	BGE.S	$50		;Timeout is >= 2 seconds
	CLR.W	D1		;Set up default bias
	TST.W	D0
	BEQ.S	$40		;No second count
	MOVE.W	#32000.,D1	;Set up bias count for one second
$40	SWAP	D0
	LSR.W	#1,D0		;Divide 1/64000s by 2
	ADD.W	D1,D0		;Add in possible second bias
	BEQ.S	$25		;Ready to timeout now
	MOVEQ	#2,D1		;Set up count for first stage
$45	MOVE.B	D0,TIMER+4	;Apply count to second stage counter
	LSR.W	#8,D0
	MOVE.B	D0,TIMER+4
	MOVE.B	D1,TIMER+2	;Apply count to first stage counter
	LSR.W	#8,D1
	MOVE.B	D1,TIMER+2
	BCLR	#0,P8259+2	;Enable timer interrupt
	RTS

;	Set up second count
$50	SUBQ.W	#1,D0		;Adjust for one clock to load count
	MOVE.W	C64000,D1	;Count for first stage
	BRA.S	$45		;Apply counts

                                                                                                                                                                                   