;
; coRoutine.s
;
; Basic coroutine 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:alloc
		GET	sapphire:sapphire
		GET	sapphire:seh
		GET	sapphire:suballoc

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

		AREA	|Sapphire$$Code|,CODE,READONLY

coRout__stkSize	EQU	1024

; --- coRout_create ---
;
; On entry:	R0 == pointer to coroutine
;		R1 == R10 value to pass to coroutine
;		R2 == R12 value to pass to coroutine
;		R3 == size of stack to pass (0 for default)
;
; On exit:	R0 == coroutine handle
;		May return an error
;
; Use:		Creates a new coroutine.  It may be given control using
;		coRout_switch.  Its registers are on entry:
;
;		R0 == its coroutine handle
;		R10 == value passed to coRout_create in R1
;		R12 == value passed to coRout_create in R2
;		R13 == pointer to the stack created for it

		EXPORT	coRout_create
coRout_create	ROUT

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

		; --- Allocate a coroutine block ---

		MOV	R0,#coRout__blkSize	;Get the block size ready
		BL	sub_alloc		;Allocate the block
		BVS	%99coRout_create	;Quit if it failed
		MOV	R4,R0			;Keep the block pointer

		; --- Allocate a stack ---

		CMP	R3,#0			;Does he want default stack?
		MOVEQ	R3,#coRout__stkSize	;Yes -- give it to him
		MOV	R0,R3			;Get the size in R0
		BL	alloc			;Try to allocate the stack
		BLCS	alloc_error		;If it failed, get an error
		BCS	%98coRout_create	;And quit if it didn't work
		STR	R0,[R4,#coRout__stack]	;Save the stack pointer away

		; --- Set up the try list ---

		MOV	R14,#0			;Must clear the list head
		STR	R14,[R4,#coRout__tryList] ;Save that away for later

		; --- Fix everything up right ---

		ADD	R0,R0,R3		;Point to the stack top
		ADR	R3,coRout__start	;Point to the start routine
		MOV	R14,PC			;Get the processor flags
		AND	R14,R14,#&FC000003	;Leave only the flags
		ORR	R14,R3,R14		;And add them on to it
		MOV	R3,R2			;Get coroutine's R12 in R3
		MOV	R2,R11			;Point to scratchpad in R2
		STMFD	R0!,{R1-R3,R14}		;And save all that lot away
		SUB	R0,R0,#32		;Don't care about other regs
		LDR	R14,[R13,#0]		;Load the coroutine address
		STMFD	R0!,{R4,R14}		;Save them in R0 and R1
		STR	R0,[R4,#coRout__sp]	;And save its stack pointer

		; --- Return to caller ---

		MOV	R0,R4			;Point to the coroutine block
		ADD	R13,R13,#4		;Don't restore caller's R0
		LDMFD	R13!,{R1-R4,R14}	;And return to caller
		BICS	PC,R14,#V_flag

		; --- Handle errors ---

98coRout_create	MOV	R3,R0			;Save the error pointer
		MOV	R0,R4			;Point to the coroutine block
		MOV	R1,#coRout__blkSize	;Get the block's size
		BL	sub_free		;Free the block nicely
		MOV	R0,R3			;Restore the error pointer

99coRout_create	ADD	R13,R13,#4		;Don't restore caller's R0
		LDMFD	R13!,{R1-R4,R14}	;And return to caller
		ORRS	PC,R14,#V_flag

		LTORG

; --- coRout_switch ---
;
; On entry:	R0 == coroutine to switch to, or 0 for main
;
; On exit:	--
;
; Use:		Switches context to another coroutine.

		EXPORT	coRout_switch
coRout_switch	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers
		WSPACE	coRout__wSpace		;Find my workspace address
		LDR	R14,coRout__current	;Get the current coroutine
		CMP	R14,R0			;Switch to current one?
		LDMEQFD	R13!,{R12,PC}^		;Silly sausage!

		; --- Perform a context switch ---

		STMFD	R13!,{R0-R11}		;Save all the other registers
		MOV	R1,R14			;Move this to another reg
		STR	R0,coRout__current	;Save the new coroutine
		BL	seh_setListBase		;Use this try list now
		CMP	R1,#0			;Are we running main routine?
		ADREQ	R1,coRout__main		;Yes -- point to dummy block
		CMP	R0,#0			;Do we switch to main routine
		ADREQ	R0,coRout__main		;Yes -- point to dummy block
		STR	R13,[R1,#coRout__sp]	;Save the new stack pointer
		LDR	R13,[R0,#coRout__sp]	;Get a new stack pointer
		LDMFD	R13!,{R0-R12,PC}^	;And switch context to it

		LTORG

; --- coRout_destroy ---
;
; On entry:	R0 == coroutine handle to destroy
;
; On exit:	--
;
; Use:		Destroys a coroutine.

		EXPORT	coRout_destroy
coRout_destroy	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers away
		WSPACE	coRout__wSpace		;Find my workspace address
		LDR	R14,coRout__current	;Get the current coroutine
		CMP	R14,R0			;Terminate the current one?
		LDMEQFD	R13!,{R12,R14}		;Yes -- unstack registers
		BEQ	coRout_end		;And end it normally

		; --- Now just destroy a coroutine ---

		STMFD	R13!,{R0,R1}		;Save some registers
		MOV	R1,R0			;Keep the coroutine handle
		LDR	R0,[R1,#coRout__stack]	;Find the stack's address
		BL	free			;Free up the space it took
		MOV	R0,R1			;Point at the coroutine blk
		MOV	R1,#coRout__blkSize	;Get the coroutine block size
		BL	sub_free		;And dispose of that too
		LDMFD	R13!,{R0,R1,R12,PC}^	;And return to caller

		LTORG

; --- coRout__start ---
;
; On entry:	R0 == coroutine handle
;		R1 == pointer to coroutine code
;
; On exit:	R14 == pointer to coRout_end
;
; Use:		Sets up a coroutine so that when it returns, it calls
;		coRout_end and dies properly.

coRout__start	ROUT

		; --- Set up a handler to kill the coroutine ---

		ADR	R0,coRout__catch	;Point to our catch handler
		BL	seh_try			;Register that nicely

		; --- Now start up the coroutine proper ---

		MOV	R14,PC			;Set up the return address
		MOV	PC,R1			;And call the main code

		; --- Fall through into coRout_end when done ---

; --- coRout_end ---
;
; On entry:	--
;
; On exit:	Doesn't.
;
; Use:		Terminates the current coroutine.

		EXPORT	coRout_end
coRout_end	ROUT

		; --- Destroy the current coroutine ---

		WSPACE	coRout__wSpace		;Find my workspace
		LDR	R13,coRout__main+coRout__sp ;Return to main stack
		LDR	R1,coRout__current	;Get the current coroutine
		LDR	R0,[R1,#coRout__stack]	;Find the stack's address
		BL	free			;Free up the space it took
		MOV	R0,R1			;Point at the coroutine blk
		MOV	R1,#coRout__blkSize	;Get the coroutine block size
		BL	sub_free		;And dispose of that too

		; --- Now rejoin the main routine ---

		MOV	R0,#0			;Going back to main routine
		STR	R0,coRout__current	;So remember that
		BL	seh_setListBase		;Use main exception list
		LDMFD	R13!,{R0-R12,PC}^	;And rejoin everything nicely

		LTORG

; --- Exception handling ---

coRout__catch	MOVS	PC,R14			;No need for tidy-up
		DCD	-1			;Catch all exceptions
		B	coRout__throw		;And throw them on again
		DCD	0

coRout__throw	WSPACE	coRout__wSpace		;Find my workspace
		LDR	R13,coRout__main+coRout__sp ;Return to main stack
		STMFD	R13!,{R0-R3}		;Remember the exception
		LDR	R1,coRout__current	;Load the old coroutine hnd
		LDR	R0,[R1,#coRout__stack]	;Find the stack's address
		BL	free			;Free up the space it took
		MOV	R0,R1			;Point at the coroutine blk
		MOV	R1,#coRout__blkSize	;Get the coroutine block size
		BL	sub_free		;And dispose of that too

		MOV	R0,#0			;Going back to main routine
		STR	R0,coRout__current	;So remember that
		BL	seh_setListBase		;Use main exception list
		LDMFD	R13!,{R0-R3}		;Restore exception
		B	seh_throw		;And raise it again

coRout__wSpace	DCD	0

;----- Coroutine blocks -----------------------------------------------------

		^	0
coRout__tryList	#	4			;SEH try list head for stack
coRout__stack	#	4			;Pointer to the stack
coRout__sp	#	4			;Coroutine's current R13
coRout__blkSize	#	0			;Size of a coroutine block

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

		^	0,R12
coRout__wStart	#	0

coRout__current	#	4			;The current coroutine or 0
coRout__main	#	coRout__blkSize		;Dummy coroutine block

coRout__wSize	EQU	{VAR}-coRout__wStart

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	coRout__wSize
		DCD	coRout__wSpace
		DCD	0
		DCD	0

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

		END
