;
; xfer.xsave.s
;
; Simplified saving with coroutines (MDW)
;
;  1994 Straylight
;

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

		GET	libs:header
		GET	libs:swis

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

		GET	sapphire:alloc
		GET	sapphire:coRoutine
		GET	sapphire:fastMove
		GET	sapphire:sapphire

;----- How it all works -----------------------------------------------------
;
; xsave attempts to unify the two saving routines you have to give (the saver
; and the sender) and provide the same interface to both.  For data sending
; through RAM transfer, this has to be done using coroutines, but this is
; fairly invisible to you.
;
; There are two main routines, xsave_save and xsave_send which start save
; jobs.  They take a pointer to a save routine with R10 and R12 pointers.
; xsave_save also takes a pointer to a filename.  The actual save routine
; should be the same for both.
;
; Saving of data is done with the call xsave_block.  This just writes out
; a block of data somehow.  All the other routines are special interfaces to
; xsave_block.

;----- How the error handling works -----------------------------------------
;
; The really tricky bit is passing of errors during a RAM transfer.
; Errors can be returned at two places -- in the user code (e.g. running out
; of memory) and by save's cunning message handling if the receiver dies.
; All errors must be handled by user code, to release claimed memory etc.
;
; The error gets passed along a path like this:
;
; Error created by user code
;
; user code --> xsave__startCo --> xsave_failed
;
; Error created by remote receiver
;
; xsave_failed --> xsave__write --> user code --> xsave__startCo

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

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- xsave_save ---
;
; On entry:	R0 == pointer to saver routine
;		R1 == R10 value to pass to saver
;		R2 == R12 value to pass to saver
;		R3 == pointer to filename to save to
;		R4 == filetype of file to save
;
; On exit:	May return an error
;
; Use:		Calls a generalised saver routine to write data to a file.

		EXPORT	xsave_save
xsave_save	ROUT

		STMFD	R13!,{R0-R5,R10,R12,R14} ;Save lots of registers
		WSPACE	xsave__wSpace		;Find my workspace address

		; --- First, try to allocate a buffer ---

		BL	xsave__setBuff		;Allocate the buffer nicely
		BVS	%99xsave_save		;If we couldn't, tidy up

		; --- Set up the file for writing ---

		MOV	R1,R3			;Point at the file name
		MOV	R0,#&8F			;Lots of errors, no path
		SWI	XOS_Find		;Try to open the file
		BVS	%98xsave_save		;Tidy up if it wouldn't open
		STR	R0,xsave__file		;Save the file handle away

		; --- Set up flags and let rip ---

		LDR	R14,xsave__flags	;Load my current flags word
		AND	R14,R14,#xsFlag__inited	;Only leave inited flag
		STR	R14,xsave__flags	;Save the flags back again

		LDMIA	R13,{R0,R10,R12}	;Load the arguments out
		ADDS	R0,R0,#0		;Clear overflow and carry
		MOV	R14,PC			;Set up the return address
		MOV	PC,R0			;And call the saver routine
		WSPACE	xsave__wSpace		;Restore my workspace ptr
		BVS	%97xsave_save		;Tidy up if it failed

		; --- Write the remainder of the file ---

		ADR	R14,xsave__buffer	;Point to the buffer info
		LDMIA	R14,{R0,R1}		;Load buffer addr and size
		BL	xsave__write		;Write it out to the file
		BVS	%97xsave_save		;Tidy up if it failed

		; --- Close the file and set the filetype ---

		MOV	R0,#0			;Close an open file
		LDR	R1,xsave__file		;Get the file handle ready
		SWI	OS_Find			;Close it

		MOV	R0,#18			;Set file type
		MOV	R1,R3			;Point to the filename
		MOV	R2,R4			;And get the filetype
		SWI	OS_File			;And set the filetype

		; --- Get rid of the buffer and return ---

		BL	xsave__loseBuff		;Free up the buffer space
		LDMFD	R13!,{R0-R5,R10,R12,R14} ;Unstack all the registers
		BICS	PC,R14,#V_flag		;And return to caller

		; --- Tidy up after various catastrophes ---

97xsave_save	MOV	R10,R0			;Keep the error pointer
		MOV	R0,#0			;Close an open file
		LDR	R1,xsave__file		;Get the file handle ready
		SWI	OS_Find			;Close it
		MOV	R0,#6			;Delete the file now
		MOV	R1,R3			;Point to the filename
		SWI	XOS_File		;Delete it as much as we can
		MOV	R0,R10			;Restore the error pointer

98xsave_save	BL	xsave__loseBuff		;Free up the buffer space

99xsave_save	ADD	R13,R13,#4		;Don't restore R0 on exit
		LDMFD	R13!,{R1-R5,R10,R12,R14} ;Unstack all the registers
		ORRS	PC,R14,#V_flag		;And return to caller

; --- xsave_send ---
;
; On entry:	R0 == pointer to saver routine
;		R1 == R10 value to pass to saver
;		R2 == R12 value to pass to saver
;
; On exit:	R0 == pointer to block to send
;		R1 == size of block
;		CS if this is the last block, else CC
;		May return an error
;
; Use:		Calls a generalised saver routine to write data to another
;		application, using RAM transfer.  Note that you must call
;		this routine from your send entry point throughout the
;		save operation.

		EXPORT	xsave_send
xsave_send	ROUT

		STMFD	R13!,{R0-R4,R12,R14}	;Save some registers away
		WSPACE	xsave__wSpace		;Find my workspace address

		; --- Find out if we need to set anything up ---

		LDR	R14,xsave__flags	;Load my flags word
		TST	R14,#xsFlag__ramTran	;Are we doing RAM transfer?
		BNE	%50xsave_send		;Yes -- continue te job then

		; --- Clear all the flags for now ---

		AND	R4,R14,#xsFlag__inited	;Leave only `initialised'
		STR	R4,xsave__flags		;And save these flags back

		; --- Set things up for RAM transfer ---

		BL	xsave__setBuff		;Set up my buffer nicely
		BVS	%99xsave_send		;If it failed, tidy up

		ADR	R0,xsave__startCo	;Point to my coroutine
		MOV	R1,R13			;Point R10 at the stack
		MOV	R2,R12			;Pass workspace in R12
		MOV	R3,#0			;Use a default stack
		BL	coRout_create		;Create a coroutine
		BVS	%98xsave_send		;If it failed, tidy up
		STR	R0,xsave__coRout	;Save the coroutine handle

		; --- Set up the flags properly now ---

		ORR	R4,R4,#xsFlag__ramTran	;Say we're RAM transfering
		STR	R4,xsave__flags		;And save these flags back

		; --- Get some more data to send ---

50xsave_send	LDR	R0,xsave__coRout	;Get the coroutine handle
		BL	coRout_switch		;Switch to it for a bit

		LDR	R14,xsave__flags	;Get the newly updated flags
		TST	R14,#xsFlag__error	;Was there an error?
		LDRNE	R0,xsave__error		;Yes -- load the error ptr
		BNE	%99xsave_send		;And tidy everything up

		; --- Get the returned block ---

		ADR	R0,xsave__start		;Point to the block info
		LDMIA	R0,{R0,R1}		;Load the start and length
		TST	R14,#xsFlag__finish	;Has the transfer finished?
		ADD	R13,R13,#8		;Don't restore R0,R1
		LDMFD	R13!,{R2-R4,R12,R14}	;Unstack lots of registers
		ORRNES	PC,R14,#C_flag		;If finished, set C flag
		BICEQS	PC,R14,#C_flag		;Otherwise say more to come

		; --- Handle errors from various things ---

98xsave_send	BL	xsave__loseBuff		;Close the buffer

99xsave_send	ADD	R13,R13,#4		;Don't restore R0 on exit
		LDMFD	R13!,{R1-R4,R12,R14}	;Unstack lots of registers
		ORRS	PC,R14,#V_flag		;And return the error

		LTORG

; --- xsave__startCo ---
;
; On entry:	R0 == my coroutine handle
;		R10 == pointer to block containing (routine,R10,R12)
;		R12 == my workspace
;
; On exit:	Returns through coRout_end
;
; Use:		Starts the coroutine for sending data by RAM transfer.  It
;		handles errors reported by it, and terminates the transfer
;		properly.

xsave__startCo	ROUT

		LDMIA	R10,{R0,R10,R12}	;Load the caller's registers
		MOV	R14,PC			;Set up a return address
		MOV	PC,R0			;And call the saver routine

		; --- Handle its return values ---

		WSPACE	xsave__wSpace		;Locate my workspace address
		LDR	R14,xsave__flags	;Load the flags word
		BVS	%90xsave__startCo	;If error, handle it nicely

		; --- Just return the current buffer state ---

		ORR	R14,R14,#xsFlag__finish	;Set the finished flag
		STR	R14,xsave__flags	;Save the flags back again
		ADR	R14,xsave__buffer	;Point to the buffer info
		LDMIA	R14,{R0,R1}		;Load the start and size info
		ADR	R14,xsave__start	;Point to return information
		STMIA	R14,{R0,R1}		;Save this to be sent next
		B	coRout_end		;And terminate the coroutine

90		ORR	R14,R14,#xsFlag__error	;Set the main error flag
		STR	R14,xsave__flags	;Save the flags back again
		STR	R0,xsave__error		;And save the error pointer
		B	coRout_end		;And finish the coroutine

		LTORG

; --- xsave_done ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Tidies up after a successful save job.

		EXPORT	xsave_done
xsave_done	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers
		WSPACE	xsave__wSpace		;Find my workspace
		LDR	R14,xsave__flags	;Load my flags word
		TST	R14,#xsFlag__ramTran	;Are we using RAM transfer?
		BLNE	xsave__loseBuff		;Yes -- destroy the buffer
		LDMFD	R13!,{R12,PC}^		;Return to caller

		LTORG

; --- xsave_failed ---
;
; On entry:	R0 == pointer to error block
;
; On exit:	--
;
; Use:		Tidies up a RAM transfer after an error.

		EXPORT	xsave_failed
xsave_failed	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers
		WSPACE	xsave__wSpace		;Find my workspace
		LDR	R14,xsave__flags	;Load my flags word
		TST	R14,#xsFlag__ramTran	;Are we using RAM transfer?
		LDMEQFD	R13!,{R12,PC}^		;No -- return right now then

		; --- Find out if the user has seen the error yet ---
		;
		; We also don't want to inform the user if the save is
		; complete -- his coroutine will already be destroyed.

		STMFD	R13!,{R0}		;Save some more registers
		TST	R14,#xsFlag__finish :OR: xsFlag__error
		BNE	%30xsave_failed		;Yes -- then don't switch

		; --- Get xsave__write to return an error ---

		ORR	R14,R14,#xsFlag__error	;Set the error flag
		STR	R0,xsave__error		;And save the error pointer
		LDR	R0,xsave__coRout	;Load the coroutine handle
		BL	coRout_switch		;And switch to it

		; --- Now tidy everything up ---
		;
		; The coroutine should have killed itself by now

30xsave_failed	BL	xsave__loseBuff		;Kill off the data buffer
		LDMFD	R13!,{R0,R12,PC}^	;Return to caller

		LTORG

; --- xsave__setBuff ---
;
; On entry:	--
;
; On exit:	May return an error
;
; Use:		Sets up the buffer for a data transfer.

xsave__setBuff	ROUT

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

		; --- Allocate the buffer memory ---

		MOV	R0,#xsave__buffSize	;Get the buffer size
		BL	alloc			;Try to allocate the buffer
		BLCS	alloc_error		;Get an error if it failed
		BCS	%90xsave__setBuff	;And tidy things up

		; --- Set up los of variables ---

		ADR	R14,xsave__buffer	;Point at the buffer info
		MOV	R1,#0			;No buffer used yet
		MOV	R2,#0			;No data sent yet either
		STMIA	R14,{R0-R2}		;Save these values away
		LDMFD	R13!,{R0-R2,R14}	;Restore all the registers
		BICS	PC,R14,#V_flag		;And return with V clear

		; --- Report an error ---

90		ADD	R13,R13,#4		;Don't restore R0 on exit
		LDMFD	R13!,{R1,R2,R14}	;Restore all the registers
		ORRS	PC,R14,#V_flag		;And return with V set

		LTORG

; --- xsave__loseBuff ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Kills off the buffer.

xsave__loseBuff	ROUT

		STMFD	R13!,{R0,R14}		;Save some registers
		LDR	R0,xsave__buffer	;Load the buffer address
		BL	free			;Free the buffer
		LDR	R14,xsave__flags	;Load my current flags word
		AND	R14,R14,#xsFlag__inited	;Only leave inited flag
		STR	R14,xsave__flags	;Save the flags back again
		LDMFD	R13!,{R0,PC}^		;And return to caller

		LTORG

; --- xsave_byte ---
;
; On entry:	R0 == byte to write in lowest 8 bits
;
; On exit:	May return an error
;
; Use:		Writes a single byte to the current output.

		EXPORT	xsave_byte
xsave_byte	ROUT

		STMFD	R13!,{R0,R1,R14}	;Save some registers
		MOV	R0,R13			;Point to saved byte
		MOV	R1,#1			;Just write one byte
		BL	xsave_block		;Send it to the output
		STRVS	R0,[R13,#0]		;Save any error pointers
		LDMFD	R13!,{R0,R1,PC}		;And return to caller

		LTORG

; --- xsave_word ---
;
; On entry:	R0 == word to write
;
; On exit:	May return an error
;
; Use:		Writes a single word to the current output.

		EXPORT	xsave_word
xsave_word	ROUT

		STMFD	R13!,{R0,R1,R14}	;Save some registers
		MOV	R0,R13			;Point to saved word
		MOV	R1,#4			;Just write one word
		BL	xsave_block		;Send it to the output
		STRVS	R0,[R13,#0]		;Save any error pointers
		LDMFD	R13!,{R0,R1,PC}		;And return to caller

		LTORG

; --- xsave_string ---
;
; On entry:	R0 == pointer to a control-terminated string
;
; On exit:	May return an error
;
; Use:		Writes a control-terminated string to the current output.
;		The string is null terminated in the output file.

		EXPORT	xsave_string
xsave_string	ROUT

		STMFD	R13!,{R0,R14}		;Save some registers
00xsave_string	LDRB	R14,[R0],#1		;Load a byte from the string
		CMP	R14,#32			;Is it a control character?
		MOVLO	R14,#0			;Yes -- make it a zero then
		BL	xsave_byte		;Write it out
		BVS	%90xsave_string		;If it failed, return error
		BHS	%00xsave_string		;Otherwise go round again
		LDMFD	R13!,{R0,R14}		;Restore the registers
		BICS	PC,R14,#V_flag		;And don't return an error

90xsave_string	ADD	R13,R13,#4		;Don't restore R0 on exit
		LDMFD	R13!,{R14}		;Restore the link register
		ORRS	PC,R14,#V_flag		;And return an error

		LTORG

; --- xsave_block ---
;
; On entry:	R0 == pointer to buffer to write
;		R1 == size of buffer to write
;
; On exit:	May return an error
;
; Use:		Writes out a block of data.  Data is buffered, so this is
;		fairly quick for reading small objects.  Large data blocks
;		are sent directly to avoid buffering overhead.

		EXPORT	xsave_block
xsave_block	ROUT

		CMP	R1,#0			;Is there anything there?
		BICEQS	PC,R14,#V_flag		;No -- return with no error

		STMFD	R13!,{R0-R4,R12,R14}	;Save a load of registers
		WSPACE	xsave__wSpace		;Load my workspace address

		; --- Fix up the total data sent ---

		LDR	R14,xsave__total	;Load the total sent so far
		ADD	R14,R14,R1		;Add on the size of this one
		STR	R14,xsave__total	;And save the total back

		; --- Work out what we have to do ---

		CMP	R1,#xsave__buffSize	;Is the block really big?
		BGE	%50xsave_block		;Yes -- then deal with that

		; --- Write as much as possible to the buffer ---

		ADR	R14,xsave__buffer	;Point to buffer data
		LDMIA	R14,{R2,R3}		;Load address and size
		RSB	R4,R3,#xsave__buffSize	;How much is free in there?
		CMP	R1,R4			;Is there enough space?
		BLT	%30xsave_block		;Yes -- just add some more

		; --- Fill the buffer right up now ---

		MOV	R1,R0			;Point to the new block
		ADD	R0,R2,R3		;Point to free part of buffer
		MOV	R2,R4			;And get the size to copy
		BL	fastMove		;Copy it all over then
		LDR	R0,xsave__buffer	;Point to the buffer
		MOV	R1,#xsave__buffSize	;And get its full size
		BL	xsave__write		;Write a bufferfull out
		BVS	%99xsave_block		;Handle an error from this

		; --- Fix up all the pointers ---

		LDMIA	R13,{R0,R1}		;Reload the buffer addresses
		ADD	R0,R0,R2		;Move on the block pointer
		SUB	R1,R1,R2		;And chop off correctly
		LDR	R2,xsave__buffer	;Point to the buffer start
		MOV	R3,#0			;And now it's empty

		; --- Just top up the buffer then ---

30xsave_block	MOV	R14,R0			;Point to client's buffer
		ADD	R0,R2,R3		;Point to free part of buff
		MOVS	R2,R1			;Get the block size to copy
		MOV	R1,R14			;Set up source pointer now
		BLNE	fastMove		;Do the copy job now
		ADD	R3,R3,R2		;Add on the block size
		STR	R3,xsave__buffUsed	;Save the new buffer size
		B	%90xsave_block		;And finish it all up

		; --- Send a really big block ---

50xsave_block	ADR	R14,xsave__buffer	;Point to buffer info
		LDMIA	R14,{R0,R1}		;Load the buffer stuff
		BL	xsave__write		;Write it all out
		BVS	%99xsave_block		;Handle an error from this
		MOV	R14,#0			;Nothing left there now
		STR	R14,xsave__buffUsed	;So clear the buffer size
		LDMIA	R13,{R0,R1}		;Get caller's block size
		BL	xsave__write		;Write it out nicely
		BVS	%99xsave_block		;Handle an error from this

90xsave_block	LDMFD	R13!,{R0-R4,R12,R14}	;Restore all the registers
		BICS	PC,R14,#V_flag		;And return no errors

99xsave_block	ADD	R13,R13,#4		;Don't restore R0 on exit
		LDMFD	R13!,{R1-R4,R12,R14}	;Restore all the registers
		ORRS	PC,R14,#V_flag		;And return the error

		LTORG

; --- xsave__write ---
;
; On entry:	R0 == pointer to buffer to write
;		R1 == size of buffer to write
;
; On exit:	May return an error
;
; Use:		Writes a block of data to the file or application.  This is
;		one of the few bits of save/send dependent code in the
;		module.

xsave__write	ROUT

		CMP	R1,#0			;Is there anything there?
		BICEQS	PC,R14,#V_flag		;No -- return with no error

		STMFD	R13!,{R14}		;Save some registers
		LDR	R14,xsave__flags	;Find my flags word
		TST	R14,#xsFlag__ramTran	;Am I using RAM transfer?
		BNE	%50xsave__write		;Yes -- do that then

		; --- Use OS_HeebieJeebie to send the data ---

		STMFD	R13!,{R0-R4}		;Save some more registers
		MOV	R2,R0			;Point to the user's buffer
		MOV	R3,R1			;And get the buffer size
		LDR	R1,xsave__file		;Get the file's handle
		MOV	R0,#2			;Write at current position
		SWI	XOS_GBPB		;Write the data out nicely
		STRVS	R0,[R13,#0]		;If error, save error pointer
		LDMFD	R13!,{R0-R4,R14}	;Unstack the registers
		ORRVSS	PC,R14,#V_flag		;Return V set as required
		BICVCS	PC,R14,#V_flag

		; --- Get the main coroutine to send the buffer ---

50xsave__write	STMFD	R13!,{R0}		;Save another register
		ADR	R14,xsave__start	;Point to the buffer info
		STMIA	R14,{R0,R1}		;Save for main coroutine
		MOV	R0,#0			;Get magic coroutine handle
		BL	coRout_switch		;And switch to main code
		LDR	R14,xsave__flags	;Reload the flags
		TST	R14,#xsFlag__error	;Was there a problem?
		LDMFD	R13!,{R0,R14}		;Restore the registers
		BICEQS	PC,R14,#V_flag		;No -- just clear V then
		LDRNE	R0,xsave__error		;Otherwise load error pointer
		ORRNES	PC,R14,#V_flag		;And return with V set

		LTORG

xsave__wSpace	DCD	0

;----- Constants ------------------------------------------------------------

xsave__buffSize	EQU	1024			;1K buffer should be enough

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

		^	0,R12
xsave__wStart	#	0

xsave__flags	#	4			;Various run-time flags
xsave__coRout	#	0			;The send coroutine handle
xsave__file	#	4			;The save file handle
xsave__buffer	#	4			;Pointer to my 1K buffer
xsave__buffUsed	#	4			;Amount of data in buffer
xsave__total	#	4			;How much data sent so far
xsave__error	#	0			;Error pointer from corout
xsave__start	#	4			;Start of buffer to send
xsave__length	#	4			;Size of next buffer to send

xsave__wSize	EQU	{VAR}-xsave__wStart

xsFlag__inited	EQU	(1<<0)			;Are we initialised already?
xsFlag__ramTran	EQU	(1<<1)			;Are we doing RAM transfer?
xsFlag__finish	EQU	(1<<2)			;This is the very last block
xsFlag__error	EQU	(1<<3)			;Should return an error

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	xsave__wSize
		DCD	xsave__wSpace
		DCD	0
		DCD	0

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

		END
