;
; except.s
;
; Sapphire exception handling (MDW)
;
;  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

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

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- except_init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Initialises the exception handler.

		EXPORT	except_init
except_init	ROUT

		STMFD	R13!,{R0,R12,R14}	;Stash registers away
		WSPACE	exc__wSpace		;Point to my workspace

		; --- Make sure I'm not already going ---

		LDR	R0,exc__flags		;Find the flags word
		TST	R0,#eFlag__inited	;Am I going yet?
		LDMNEFD	R13!,{R0,R12,PC}^	;Yes -- return right now

		; --- Start up suballocation for exit list ---

		BL	sub_init		;Make sure suballoc's going

		; --- Fill in the flags and exit list ---

		MOV	R0,#eFlag__inited	;Set the initialised flag
		STR	R0,exc__flags		;Store it away nicely

		MOV	R0,#0
		STR	R0,exc__exitList	;No atexit routines yet
		STR	R0,exc__query		;No error handler either
		STR	R11,exc__R11		;Save R11 pointer

		LDMFD	R13!,{R0,R12,PC}^	;Return to caller

		LTORG

; --- exc__setHnd ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Sets up the OS handlers so we get called when strange things
;		happen.

exc__setHnd	ROUT

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

		; --- Make sure we need to do this ---

		LDR	R0,exc__flags		;Get my current flags
		TST	R0,#eFlag__handling	;Are we now handling errors?
		LDMNEFD	R13!,{R0-R4,PC}^	;Yes -- return right now

		ADR	R4,exc__handlers	;Point to old handlers block

		; --- Set up the error handler ---

		MOV	R0,#6			;Error handler number
		ADR	R1,exc__err		;Point to my handler routine
		MOV	R2,R12			;I want my workspace pointer
		MOV	R3,R11			;Use scratchpad for error
		SWI	XOS_ChangeEnvironment	;Set the handler up
		STMIA	R4!,{R1-R3}		;Save the old handler away

		; --- Set up the exit handler ---

		MOV	R0,#11			;Exit handler number
		ADR	R1,exc__exit		;Point to my handler
		MOV	R2,R12			;Give me my workspace
		SWI	XOS_ChangeEnvironment	;Set the handler up
		STMIA	R4!,{R1-R3}		;Save the old handler away

		; --- Set up the UpCall handler ---

		MOV	R0,#16			;UpCall handler number
		ADR	R1,exc__upc		;Point to my handler
		MOV	R2,R12			;Give me my workspace
		SWI	XOS_ChangeEnvironment	;Set the handler up
		STMIA	R4!,{R1-R3}		;Save the old handler away

		; --- Done ---

		LDR	R0,exc__flags		;Get my current flags
		ORR	R0,R0,#eFlag__handling	;We are now handling errors
		STR	R0,exc__flags		;Store them away again
		LDMFD	R13!,{R0-R4,PC}^	;Return to caller

		LTORG

; --- exc__killHnd ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Releases any handlers we set up.

exc__killHnd	ROUT

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

		; --- Make sure we need to do this ---

		LDR	R0,exc__flags		;Get my current flags
		TST	R0,#eFlag__handling	;Are we now handling errors?
		LDMEQFD	R13!,{R0-R4,PC}^	;No -- return right now

		ADR	R4,exc__handlers	;Point to old handlers block

		; --- Reset the error handler ---

		MOV	R0,#6			;Error handler number
		LDMIA	R4!,{R1-R3}		;Get the old handler
		SWI	XOS_ChangeEnvironment	;Set the handler up

		; --- Reset the exit handler ---

		MOV	R0,#11			;Exit handler number
		LDMIA	R4!,{R1-R3}		;Get the old handler
		SWI	XOS_ChangeEnvironment	;Set the handler up

		; --- Reset the UpCall handler ---

		MOV	R0,#16			;UpCall handler number
		LDMIA	R4!,{R1-R3}		;Get the old handler
		SWI	XOS_ChangeEnvironment	;Set the handler up

		; --- Done ---

		LDR	R0,exc__flags		;Get my current flags
		BIC	R0,R0,#eFlag__handling	;We are not handling errors
		STR	R0,exc__flags		;Store them away again
		LDMFD	R13!,{R0-R4,PC}^	;Return to caller

		LTORG

; --- exc__error ---
;
; On entry:	R0 == pointer to workspace
;
; On exit:	Doesn't, really
;
; Use:		Handles an error, and dispatches it to the right place,
;		properly handling multiple exceptions (i.e. it falls over
;		and dies).

exc__err	ROUT

		MOV	R12,R0			;Because RISC OS is weird
		LDR	R11,exc__R11		;Find the scratchpad pointer

		; --- Am I already handling an error? ---

		LDR	R0,exc__flags		;Find the flags word
		TST	R0,#eFlag__inError	;Check the flag bit
		BNE	%50exc__err		;Yes -- skip ahead

		; --- Remember that I'm handling an error ---

		ORR	R0,R0,#eFlag__inError	;Set the bit
		STR	R0,exc__flags		;And put my flags word away

		; --- Do I have an error handler? ---

		LDR	R2,exc__query		;Find the handler function
		CMP	R2,#0			;Is it defined?
		BEQ	%20exc__err		;No -- skip ahead

		; --- Locate the error buffer and dispatch the error ---

		ADD	R0,R11,#4		;Point to the error block
		STMFD	R13!,{R12}		;Save my workspace on stack
		LDR	R12,exc__qR12		;Get the workspace they want
		MOV	R14,PC			;Get a return address
		MOV	PC,R2			;Call the handler

		; --- We now have a resume routine to call ---

		LDMFD	R13!,{R12}		;Restore my workspace pointer
		LDR	R2,exc__flags		;Find the flags word
		BIC	R2,R2,#eFlag__inError	;We're leaving the handler
		STR	R2,exc__flags		;And put my flags word away
		LDR	R13,exc__stackPtr	;Get the stack pointer
		MOV	R12,R1			;Get the resumer's wSpace
		MOV	PC,R0			;And call the resumer.

		; --- No error handler registered ---

20exc__err	LDR	R13,sapph_stackBase	;We won't be coming back
		BL	exc__killHnd		;Reset all the handlers
		BL	exc__atexits		;Perform tidy-up operations
		ADD	R0,R11,#4		;Point to the error block
		SWI	OS_GenerateError	;And report error to caller

		; --- Something went catastrophically wrong ---

50exc__err	ADD	R0,R11,#4		;Point to the error block
		B	except_fatal		;And report the error

		LTORG

exc__wSpace	DCD	0			;Pointer to my workspace

; --- except_fatal ---
;
; On entry:	R0 == pointer to an error block
;
; On exit:	Doesn't
;
; Use:		Reports an error to our /caller's/ error handler.  We quit
;		and die at this point.  Don't use unless you have absolutely
;		no choice in the matter.

		EXPORT	except_fatal
except_fatal	ROUT

		WSPACE	exc__wSpace		;Find my workspace address
		LDR	R13,sapph_stackBase	;Find a good piece of stack
		BL	exc__killHnd		;Get rid of our handlers
		SWI	OS_GenerateError	;And report the error

		LTORG

; --- exc__atexits ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Calls all the registered atexit functions

exc__atexits	ROUT

		STMFD	R13!,{R1,R10-R12,R14}	;Save the registers I want
		LDR	R10,exc__exitList	;Get the list of handlers

01exc__atexits	CMP	R10,#0			;Is the list empty
		LDMEQFD	R13!,{R1,R10-R12,PC}^	;Return to call if so
		LDR	R12,[R10,#eExit__R12]	;Get the required R12
		LDR	R1,[R10,#eExit__handler] ;Get pointer to handler
		MOV	R14,PC			;Set up return address
		MOV	PC,R1			;Call atexit routine
		LDR	R10,[R10,#eExit__next]	;Get next handler
		B	%01exc__atexits

		LTORG

; --- exc__exit ---
;
; On entry:	R12 == pointer to my workspace
;
; On exit:	Doesn't
;
; Use:		Gets called by OS_Exit

exc__exit	ROUT

		; --- Find a stack somewhere ---

		LDR	R11,exc__R11		;Load scratchpad pointer
		BL	sapphire_resetStack	;Use initial stack
		BL	exc__killHnd		;Kill existing handlers
		BL	exc__atexits		;Call things on the exit list
		SWI	XOS_Exit		;Quit the application

		LTORG

; --- exc__upc ---
;
; On entry:	R12 == pointer to my workspace
;
; On exit:	Handlers are restored
;
; Use:		Upcall handler

exc__upc	ROUT

		; --- Are we interested in this UpCall? ---

		CMP	R0,#256			;Is a new app starting?
		MOVNES	PC,R14			;No -- return to caller

		; --- Stick everything on the SVC stack ---

		STMFD	R13!,{R14}		;Save the return address
		TEQP	PC,#0			;Enter USR mode to keep the
						;atexit routines happy
		MOV	R0,R0			;Keep ARM happy too
		LDR	R11,exc__R11		;Load scratchpad pointer
		BL	sapphire_resetStack	;Use initial stack
		BL	exc__killHnd		;Restore the handlers
		BL	exc__atexits		;Close everything down now
		SWI	OS_EnterOS		;Go back to SVC mode
		LDMFD	R13!,{PC}^		;Return and be killed :-)

		LTORG

; --- except_atExit ---
;
; On entry:	R0 == pointer to routine to call on exit
;		R1 == R12 value to call with
;
; On exit:	--
;
; Use:		Registers a routine to get called when the application quits.
;		Later-registered routines are called earlier than earlier-
;		registered routines, so everything closes down in a nice
;		manner.

		EXPORT	except_atExit
except_atExit	ROUT

		STMFD	R13!,{R0-R3,R12,R14}	;Save everything on stack
		WSPACE	exc__wSpace		;Find my workspace
		BL	exc__setHnd		;Set up my handlers

		; --- Create the list item ---

		MOV	R0,#eExit__size		;Size of the block to get
		BL	sub_alloc		;Allocate the memory
		SWIVS	OS_GenerateError	;Barf if it failed
		MOV	R2,R0			;Move to a nicer register

		; --- Fill it in and link it to the list ---

		LDR	R0,exc__exitList	;Get the current list head
		STR	R0,[R2,#eExit__next]	;Store this in the link
		LDMIA	R13!,{R0,R1}		;Get the stuff from the stack
		STMIB	R2,{R0,R1}		;Store them in the block
		STR	R2,exc__exitList	;This is the new list head

		; --- Done ---

		LDMFD	R13!,{R2,R3,R12,PC}^	;Return to caller

		LTORG

; --- except_returnPt ---
;
; On entry:	R0 == pointer to exception handler routine
;		R1 == R12 value to enter routine with
;		R2 == R13 value to enter routine with
;
; On exit:	--
;
; Use:		Sets up a routine to be called whenever there's an error.
;		The idea is that it should ask the user whether to quit,
;		and if not, resume to some known (safe?) state.
;
; 		The routine is called with R0 == pointer to error block, and
;		R12 and R13 being the values set up here(*).  It should
;		return with R0 == pointer to a routine to resume at, and R1
;		being the value to pass to the resume routine in R12.  If
;		you decide to quit, just call OS_Exit -- this should tidy
;		everything up.
;
;		Note that the error is held in the scratchpad buffer, so
;		you can't use the first 256 bytes of that until you've
;		finished with the error message.
;
;		(*) Actually, R13 is 4 bytes lower because it's assumed that
;		it points to a full descending stack that we can use.  This
;		shouldn't make any difference as long as you're using R13
;		as a full descending stack pointer.

		EXPORT	except_returnPt
except_returnPt	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers
		WSPACE	exc__wSpace		;Get my workspace pointer
		BL	exc__setHnd		;Set up all the handlers
		ADR	R14,exc__query		;Point to my stack variable
		STMIA	R14,{R0-R2}		;Store the handler away
		LDMFD	R13!,{R12,PC}^		;Return to caller

		LTORG

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

		^	0,R12
exc__wStart	#	0

exc__flags	#	4			;Error handling flags
exc__handlers	#	36			;Old handlers information
exc__query	#	4			;Pointer to query routine
exc__qR12	#	4			;R12 for query routine
exc__stackPtr	#	4			;Stack pointer for handling
exc__exitList	#	4			;The list of exit routines
exc__R11	#	4			;Sapphire's R11 magic pointer

exc__wSize	EQU	{VAR}-exc__wStart	;My workspace size

eFlag__inited	EQU	(1<<0)			;Are we initialised?
eFlag__inError	EQU	(1<<1)			;Currently in error handler
eFlag__handling	EQU	(1<<2)			;We have handlers set up

; --- Exit routine block format ---

		^	0
eExit__next	#	4			;Address of next block
eExit__handler	#	4			;Address of routine to call
eExit__R12	#	4			;R12 to call handler with
eExit__size	#	0			;Size of the block

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	exc__wSize
		DCD	exc__wSpace
		DCD	256
		DCD	except_init

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

		END
