; ; 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