;
; idle.s
;
; Idle event and alarm handling (TMA)
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Sapphire library.
;
; Sapphire is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; Sapphire is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Sapphire.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

;----- External dependencies ------------------------------------------------

		GET	sapphire:sapphire
		GET	sapphire:suballoc
		GET	sapphire:event

;----- Main code ------------------------------------------------------------

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- idle__addToList ---
;
; On entry:	R0 == Time word
;		R1 == pointer to routine to call
;		R2 == R10 value to call routine
;		R3 == R12 value to call routine with
;		R4 == pointer to list head to use
;
; On exit:	R0-R4 preserved
;
; Use:		Adds a rountine to the given list. Later added
;		routines are called first

idle__addToList	ROUT

		STMFD	R13!,{R0-R4,R14}	;Stack some registers

		; --- Allocate a block ---

		MOV	R0,#list__size		;Size to allocate
		BL	sub_alloc		;Allocate the block
		BVS	%01			;Branch ahead if error

		; --- Fill the block in ---

		LDR	R1,[R4]			;Get the list head
		STR	R1,[R0,#list__next]	;Store in next field
		STR	R0,[R4]			;Store new block at head

		LDMIA	R13,{R1-R4}		;Get parameters
		STMIB	R0,{R1-R4}		;Store them in the block

		; --- And return to user ---

		LDMFD	R13!,{R0-R4,R14}	;Load back link
		BICS	PC,R14,#V_flag		;Return without error

		; --- Barf if an error occured ---

01		ADD	R13,R13,#4		;Skip over R0,R1,R2
		LDMFD	R13!,{R1-R4,R14}	;Branch if error
		ORRS	PC,R14,#V_flag		;Return with error

		LTORG

; --- idle__removeFromList ---
;
; On entry:	R0 == Time word
;		R1 == pointer to routine to be called
;		R2 == R10 value routine is called with
;		R3 == R12 value routine is called with
;		R4 == pointer to list head to use
;
; On exit:	All registers/flags preserved
;
; Use:		Removes a routine from the given list. All values are
;		compared.

idle__removeFromList
		ROUT

		STMFD	R13!,{R0-R10,R12,R14}

		; --- Find the block ---

		MOV	R5,#0			;The previous pointer
		MOV	R12,R4			;Remember where the head is
		LDR	R4,[R4]			;Get the head of the list
01		TEQ	R4,#0			;Are we at the end?
		LDMEQFD	R13!,{R0-R10,R12,PC}^ 	;Yes -- return
		LDR	R10,[R4],#4		;Get the next pointer
		LDMIA	R4,{R6-R9}		;Load data from the block
		CMP	R6,R0			;Are routines the same?
		CMPEQ	R7,R1			;Yes -- and window/R4 handle?
		CMPEQ	R8,R2			;Yes -- R10 value?
		CMPEQ	R9,R3			;Yes -- R12 value?
		SUBNE	R5,R4,#4		;If no, remember previous
		MOVNE	R4,R10			;...get list pointer
		BNE	%01			;...and keep looking

		; --- So now the block has been found ---

		SUB	R0,R4,#4		;Put the block in R0
		MOV	R1,#list__size		;Get the size
		BL	sub_free		;Free the block
		CMP	R5,#0			;Was there a previous block
		STREQ	R10,[R12,#0]		;No -- store next blk in head
		STRNE	R10,[R5,#0]		;Yes -- store in prev next ^

		; --- And return to the user ---

		LDMFD	R13!,{R0-R10,R12,PC}^

		LTORG

; --- idle_handler ---
;
; On entry:	R0 == how frequently to call
;		R1 == pointer to routine to call
;		R2 == R10 value to call routine with
;		R3 == R12 value to call routine with
;
; On exit:	May return an error
;
; Use:		Adds a routine to the idle handler list. Later added
;		routines are called first. The idle handing routine
;		may corrupt R10 and R12.
;		A handler is only addeded if an identical one does not
;		already exist.

		EXPORT	idle_handler
idle_handler	ROUT

		BIC	R14,R14,#V_flag		;Assume it's all OK
		STMFD	R13!,{R4-R9,R14}	;Save some registers
		WSPACE	idle__wSpace,R9		;Get my workspace pointer

		; --- Check through the list ---

		LDR	R4,idle__iHandlers	;Get my idle handlers list
10idle_handler	TEQ	R4,#0			;Are we at the end
		BEQ	%20idle_handler		;Yes -- jump ahead
		LDMIA	R4,{R4-R8}		;Load parameters to pass
		CMP	R5,R0			;Is this identical?
		CMPEQ	R6,R1
		CMPEQ	R7,R2
		CMPEQ	R8,R3
		BEQ	%90idle_handler		;Yes -- return now then
		B	%10idle_handler		;Try next handler

		; --- Be careful not to alter flags ---

20idle_handler	ADR	R4,idle__iHandlers	;Get the idle handlers
		BL	idle__addToList		;Add the routine to the list
		LDMVSFD	R13!,{R4-R9,PC}		;Return if it failed

		; --- Cache the minimum return time ---

		LDR	R4,idle__minTime	;Get the current minimum time
		CMP	R4,#-1			;Is there one?
		STREQ	R0,idle__minTime	;No -- store the new one
		LDMEQFD	R13!,{R4-R9,PC}^	;...and return
		CMP	R0,R4			;Is the new time lower
		STRLE	R0,idle__minTime	;Yes -- Store the new one

		; --- Return to client ---

90idle_handler	LDMFD	R13!,{R4-R9,PC}^	;Return cunningly

; --- idle_removeHandler ---
;
; On entry:	R0 == How frequently it was called
;		R1 == pointer to routine called
;		R2 == R10 value routine is called with
;		R3 == R12 value routine is called with
;
; On exit:	--
;
; Use:		Removes a routine from the idle handler list.

		EXPORT	idle_removeHandler
idle_removeHandler
		ROUT

		STMFD	R13!,{R4-R6,R9,R14}	;Stack some registers

		; --- Free the handler ---

		WSPACE	idle__wSpace,R9		;Get my workspace pointer
		ADR	R4,idle__iHandlers	;Get the idle handlers
		BL	idle__removeFromList	;Remove routine from the list

		; --- Find the new minimum frequency ---

		MOV	R5,#-1			;Lowest time found
		LDR	R4,[R4]			;Get the first handler
00		TEQ	R4,#0			;Is it the end?
		BEQ	%01			;Yes -- finish
		LDR	R6,[R4,#list__time]	;Load the frequency
		CMP	R6,R5			;Is it lower than lowest
		MOVLO	R5,R6			;Yes -- remember it
		LDR	R4,[R4]			;Get next in list
		B	%00			;Try the rest

01		STR	R5,idle__minTime	;Store it found value
		LDMFD	R13!,{R4-R6,R9,R14}	;Load registers
		BICS	PC,R14,#V_flag		;Return without error

; --- idle_setAlarm ---
;
; On entry:	R3 == Time to call
;		R1 == pointer to routine to call
;		R2 == R10 value to call routine with
;		R3 == R12 value to call routine with
;
; On exit:	May return an error
;
; Use:		Adds a alarm to be called. The idle handing routine
;		may corrupt R10 and R12.

		EXPORT	idle_setAlarm
idle_setAlarm	ROUT

		STMFD	R13!,{R0-R4,R9,R14}	;Stack some registers
		WSPACE	idle__wSpace,R9		;Get my workspace
		MOV	R1,R0			;Put requested time in R1

		; --- Allocate a block ---

		MOV	R0,#list__size		;Size to allocate
		BL	sub_alloc		;Allocate the block
		BVS	%02idle_setAlarm	;Branch ahead if error

		; --- Work out where to put it ---

		MOV	R2,#0			;Previous alarm
		LDR	R4,idle__aHandlers	;Get the list head
00idle_setAlarm	CMP	R4,#0			;Are we at the end
		BEQ	%01idle_setAlarm	;Put it in here
		LDR	R3,[R4,#4]		;Get the time due
		CMP	R3,R1			;Compare with new alarm
		BGT	%01idle_setAlarm	;If bigger, put it here
		MOV	R2,R4			;Remember this alarm
		LDR	R4,[R4,#0]		;Get next alarm in list
		B	%00idle_setAlarm

		; --- Put the alarm in between R2 and R4 ---

01idle_setAlarm	STR	R4,[R0,#list__next]	;Store in next field
		CMP	R2,#0			;Was there a previous alarm
		STREQ	R0,idle__aHandlers	;No, store new block at head
		STRNE	R0,[R2,#0]		;Yes, store in its next field

		; --- Fill the block in ---

		LDMIA	R13,{R1-R4}		;Get parameters
		STMIB	R0,{R1-R4}		;Store them in the block

		; --- And return to user ---

		LDMFD	R13!,{R0-R4,R9,R14}	;Load back link
		BICS	PC,R14,#V_flag		;Return without error

		; --- Barf if an error occured ---

02idle_setAlarm	ADD	R13,R13,#4		;Skip over R0
		LDMFD	R13!,{R1-R4,R9,R14}	;Branch if error
		ORRS	PC,R14,#V_flag		;Return with error

		LTORG


; --- idle_removeAlarm ---
;
; On entry:	R0 == When it was to be called
;		R1 == pointer to routine called
;		R2 == R10 value routine is called with
;		R3 == R12 value routine is called with
;
; On exit:	--
;
; Use:		Removes a routine from the idle handler list. It has
;		no effect if it doesn't exist.

		EXPORT	idle_removeAlarm
idle_removeAlarm
		ROUT

		STMFD	R13!,{R4-R6,R9,R14}	;Stack some registers

		; --- Free the handler ---

		WSPACE	idle__wSpace,R9		;Get my workspace pointer
		ADR	R4,idle__aHandlers	;Get the idle handlers
		BL	idle__removeFromList	;Remove routine from the list

                ; --- And return ---

		LDMFD	R13!,{R4-R6,R9,R14}	;Load registers
		BICS	PC,R14,#V_flag		;Return without error

; --- idle_removeAllAlarms ---
;
; On entry:	R0 == R10 value to look for
;
; On exit:	--
;
; Use:		Removes all alarms with the handle that was passed to them
;		to be put into R10.  You should not remove an alarm within
;		an alarm handler.


		EXPORT	idle_removeAllAlarms
idle_removeAllAlarms
		ROUT

		STMFD	R13!,{R0-R10,R14}	;Stack some registers
		WSPACE	idle__wSpace,R9		;Get the alarm handler list
		MOV	R10,R0			;Remember handle

		; --- Find the first block with the handle block ---

		MOV	R2,#0			;The previous pointer
		LDR	R3,idle__aHandlers	;Get the head of the list
00		TEQ	R3,#0			;Are we at the end?
		LDMEQFD	R13!,{R0-R10,PC}^	;Yes -- return
		LDR	R4,[R3],#4		;Get the next pointer
		LDMIA	R3,{R5-R8}		;Load data from the block
		CMP	R7,R10			;Are handles the same?
		SUBNE	R2,R3,#4		;If no, remember previous
		MOVNE	R3,R4			;...point to the next block
		BNE	%00			;...and keep looking

		; --- So now the block has been found ---

		SUB	R0,R3,#4		;Put the block in R0
		MOV	R1,#list__size		;Get the size
		BL	sub_free		;Free the block
		CMP	R2,#0			;Was there a previous block
		STREQ	R4,idle__aHandlers	;No -- store next blk in head
		STRNE	R4,[R2,#0]		;Yes -- store in prev next ^
		MOV	R3,R4			;Point at the next block
		B	%00			;Find more alarms

	        LTORG

; --- idle__preFilter ---
;
; On entry:	R0 == reason code return from Wimp_Poll
;		R1 == pointer to block
;		R2 == earliest time to return with NULL
;
; On exit:	R2 altered as appropriate

idle__preFilter	ROUT

		STMFD	R13!,{R0,R5-R7,R9,R14}
		MOV	R9,R12

		; --- Is the user requesting idles? ---

		TST	R0,#1			;Is it masked off
		CMPEQ	R2,#0			;If no, just Wimp_Poll?
		LDMEQFD	R13!,{R0,R5-R7,R9,PC}^	;Return PDQ

		; --- Get the minimum time to return with ---

		LDR	R5,idle__iHandlers	;Get the idle handlers list
		LDR	R6,idle__aHandlers	;Get the alarm handlers list
		CMP	R5,#0			;Do we have idle handlers?
		CMPEQ	R6,#0			;If not, any alarm handlers?
		LDMEQFD	R13!,{R0,R5-R7,R9,PC}^	;No idles/alarms -- return

		; --- Get the next time the quickest idle will call ---

		LDR	R7,idle__minTime	;Get the minimum frequency
		CMP	R7,#-1			;Is there one?
		SWINE	OS_ReadMonotonicTime	;Yes -- get the current time
		ADDNE	R5,R7,R0		;...and time to return with

		; --- And the time of the next alarm ---

		CMP	R6,#0			;Are there any alarms?
		BEQ	%00			;No -- R5 contains quickest
		LDR	R6,[R6,#list__time]	;Get time of the next alarm
		CMP	R7,#-1			;Was there an idle handler?
		MOVEQ	R5,R6			;No, R6 is lowest
		BEQ	%00			;...branch ahead
		CMP	R6,R5			;Is it before next idle
		MOVLT	R5,R6			;Yes, use this time

		; --- Now set up the mask word and return time ---

00		LDR	R0,[R13],#4		;Get event mask back
		TST	R0,#1			;Is the user expecting idles
		BEQ	%01			;Yes -- Do a comparison
		BIC	R0,R0,#1		;Clear the mask bit
		MOV	R2,R5			;Put the minimum time in R2
		LDMFD	R13!,{R5-R7,R9,PC}^	;...and return

		; --- The user is expecting idles, so please him ---

01		CMP	R5,R2			;Which is bigger?
		MOVLT	R2,R5			;Swap if needed
		LDMFD	R13!,{R5-R7,R9,PC}^	;...and return

; --- idle__dispatch ---
;
; On entry:	R0  == reason code returned from Wimp_Poll
;		R1  == pointer to block
;		R12 == pointer to workspace
;
; On exit:	--

idle__dispatch  ROUT

		CMP	R0,#0			;Is it a NULL event
		MOVNES	PC,R14			;No - return
		STMFD	R13!,{R2-R5,R9,R10,R14}	;Stack some registers
		MOV	R9,R12			;Get my workspace

		; --- Go through the handlers list ---

		LDR	R2,idle__iHandlers	;Get my idle handlers list
10		TEQ	R2,#0			;Are we at the end
		BEQ	%20idle__dispatch	;Yes -- jump ahead
		LDMIA	R2,{R2,R3,R4,R10,R12}	;Load parameters to pass
		MOV	R14,PC			;Set return address
		MOV	PC,R4			;Branch to handler
		B	%10idle__dispatch	;Try next handler
20		LDMFD	R13!,{R2-R5,R9,R10,PC}^	;Load registers if claimed

		LTORG

; --- idle__dispatchAlarm ---
;
; On entry:	R0  == reason code returned from Wimp_Poll
;		R1  == pointer to block
;		R12 == pointer to workspace
;
; On exit:	R0-R1 preserved

idle__dispatchAlarm ROUT

		CMP	R0,#0			;Is it a NULL event
		MOVNES	PC,R14			;No - return
		STMFD	R13!,{R0-R5,R9,R10,R14}	;Stack some registers
		MOV	R9,R12			;Get my workspace

		; --- Go through the handlers list ---

		LDR	R2,idle__aHandlers	;Get my alarm handlers list
10		SWI	OS_ReadMonotonicTime	;Get the current time
		TEQ	R2,#0			;Are we at the end
		BEQ	%20idle__dispatchAlarm	;Yes -- jump ahead
		LDMIB	R2,{R3,R4,R10,R12}	;Load parameters to pass
		SUBS	R3,R0,R3		;Call this handler?
		BMI	%20idle__dispatchAlarm	;No -- return
		MOV	R14,PC			;Set return address
		MOV	PC,R4			;Branch to handler

		; --- Remove the handler just called ---

		LDMIA	R2,{R4}			;Get next (changed?) alarm
		MOV	R0,R2			;Get the block
		MOV	R1,#list__size		;The size of the block
		BL	sub_free		;Free the block
		STR	R4,idle__aHandlers	;Store next in list head

		; --- And keep going ---

		MOV	R2,R4			;Get next in list
		B	%10idle__dispatchAlarm	;Try next handler

		; --- Return to user ---

20		LDMFD	R13!,{R0-R5,R9,R10,PC}^	;Load registers if claimed

		LTORG

; --- idle_init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Initialises the idle system.

		EXPORT	idle_init
idle_init	ROUT

		STMFD	R13!,{R0,R1,R9,R14}	;Stack some registers
		WSPACE	idle__wSpace,R9		;Get my workspace

		; --- Are we already initialised? ---

		LDR	R0,idle__flags		;Get my flags
		TST	R0,#idle__INITED	;Are we initialised?
		LDMNEFD	R13!,{R0,R1,R9,PC}^	;Yes -- return

		ORR	R0,R0,#idle__INITED	;Set initialised flag
		STR	R0,idle__flags		;And store them back

		; --- Clear rest of workspace ---

		MOV	R0,#0			;Zero some registers
		STR	R0,idle__iHandlers	;Clear idle handler list
		STR	R0,idle__aHandlers	;Clear alarm handler list
		MOV	R0,#-1			;So minimum time yet
		STR	R0,idle__minTime	;Set the word

		; --- Initialise event system ---

		BL	event_init		;Initialise event system

		; --- Set up a post filter for the idle system ---

		ADR	R0,idle__dispatch	;Call this routine
		MOV	R1,R9			;Put my workspace in R12
		BL	event_postFilter	;Add it to post-filter list

		; --- Set up a post filter for the alarm system ---

		ADR	R0,idle__dispatchAlarm	;Call this routine
		MOV	R1,R9			;Put my workspace in R12
		BL	event_postFilter	;Add it to post-filter list

		; --- Set up the pre filter ---

		ADR	R0,idle__preFilter	;Call this routine
		MOV	R1,R9			;Put my workspace in R12
		BL	event_preFilter	;Add it to pre-filter list

		; --- That's it now ---

		LDMFD	R13!,{R0,R1,R9,PC}^	;Return

		LTORG

idle__wSpace	DCD	0			;My workspace pointer

;----- Workspace ------------------------------------------------------------

		^	0,R9
idle__wStart	#	0

idle__flags	#	4			;Flags

idle__INITED	EQU	(1<<0)			;I've been initialised

idle__iHandlers	#	4			;Idle handler list
idle__aHandlers	#	4			;Alarm handler list
idle__minTime	#	4			;Minimum time to return with

idle__wSize	EQU	{VAR}-idle__wStart

; --- list structure ---

		^	0
list__next	#	4			;The next block
list__time	#	4			;Handler code
list__proc	#	4			;The time word
list__r10	#	4			;R10 to call with
list__r12	#	4			;R12 to call with

list__size	#	0

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	idle__wSize
		DCD	idle__wSpace
		DCD	0
		DCD	idle_init

;----- That's all, folks ----------------------------------------------------

		END
