; ; xfer.xload.s ; ; Simplified loading 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 ----------------------------------------------------- ; ; xload attempts to unify the various loading routines you have to supply, ; by providing a single interface. ; ; There are several main routines, xload_file, xload_initBuf, ; xload_killBuf, xload_extend and xload_doneBuf, which are called from ; the main load branch table. ; ; Loading of data is done with the call xload_block. This just reads ; a block of data somehow. All the other routines are special interfaces to ; xload_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 load's 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 --> xload__startCo --> xload_failed ; ; Error created by remote receiver ; ; xload_failed --> xload__read --> user code --> xload__startCo ;----- Main code ------------------------------------------------------------ AREA |Sapphire$$Code|,CODE,READONLY ; --- xload_file --- ; ; On entry: R0 == pointer to loader routine ; R1 == R10 value to pass to loader ; R2 == R12 value to pass to loader ; R3 == pointer to leafname of file (passed to loader in R0) ; R4 == pointer to filename to load from ; R5 == whether the file is safe (passed to loader in R1) ; ; On exit: May return an error ; ; Use: Calls a generalised loader routine to read data from a file. EXPORT xload_file xload_file ROUT STMFD R13!,{R0-R5,R10,R12,R14} ;Save lots of registers WSPACE xload__wSpace ;Find my workspace address ; --- First, try to allocate a buffer --- BL xload__setBuff ;Allocate the buffer nicely BVS %99xload_file ;If we couldn't, tidy up ; --- Set up the file for writing --- MOV R1,R4 ;Point at the file name MOV R0,#&4F ;Lots of errors, no path SWI XOS_Find ;Try to open the file BVS %98xload_file ;Tidy up if it wouldn't open STR R0,xload__file ;Save the file handle away ; --- Set up flags and let rip --- LDR R14,xload__flags ;Load my current flags word AND R14,R14,#xlFlag__inited ;Only leave inited flag STR R14,xload__flags ;Save the flags back again MOV R14,#0 ;Nothing read yet STR R14,xload__buffUsed ;So clear buffer size LDMIA R13,{R2,R10,R12,R14} ;Load the arguments out MOV R0,R14 ;Get the leafname in R0 ADDS R1,R1,#0 ;Clear overflow and carry LDR R1,[R13,#16] ;Load the safeness flag MOV R14,PC ;Set up the return address MOV PC,R2 ;And call the saver routine WSPACE xload__wSpace ;Restore my workspace ptr BVS %97xload_file ;Tidy up if it failed ; --- Close the file --- MOV R0,#0 ;Close an open file LDR R1,xload__file ;Get the file handle ready SWI OS_Find ;Close it ; --- Get rid of the buffer and return --- BL xload__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 --- 97xload_file MOV R10,R0 ;Keep the error pointer MOV R0,#0 ;Close an open file LDR R1,xload__file ;Get the file handle ready SWI OS_Find ;Close it MOV R0,R10 ;Restore the error pointer 98xload_file BL xload__loseBuff ;Free up the buffer space 99xload_file 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 ; --- xload_initBuf --- ; ; On entry: R0 == pointer to loader routine ; R1 == R10 value to pass to loader ; R2 == R12 value to pass to loader ; R3 == pointer to leafname of file (passed to loader in R0) ; ; On exit: R0 == pointer to load buffer ; R1 == size of load buffer ; May return an error ; ; Use: Starts a RAM transfer and starts up a generalised load ; routine. EXPORT xload_initBuf xload_initBuf ROUT STMFD R13!,{R0-R3,R12,R14} ;Save some registers WSPACE xload__wSpace ;Load my workspace address ; --- Firstly, allocate the buffer --- BL xload__setBuff ;Create the load buffer ADDVS R13,R13,#8 ;If failed, unstack R0,R1 BVS %99xload_initBuf ;And then return error ; --- Now create the coroutine --- ADR R0,xload__startCo ;Point to the coroutine MOV R1,R13 ;Point to saved corout info MOV R2,R12 ;Pass my workspace in R12 MOV R3,#0 ;Default stack size please BL coRout_create ;Try to create the coroutine ADDVS R13,R13,#8 ;Don't return R0,R1 BVS %98xload_initBuf ;If it failed, tidy up STR R0,xload__coRout ;Save the couroutine handle ; --- Let xload__startCo save coroutine info --- BL coRout_switch ;Switch to it quickly ADD R13,R13,#8 ;Don't return R0,R1 ; --- Set up flags and bits of workspace --- LDR R14,xload__flags ;Load my flags word AND R14,R14,#xlFlag__inited ;Clear all but initialised ORR R14,R14,#xlFlag__ramTran ;We're doing RAM transfer STR R14,xload__flags ;Save the flags back MOV R14,#0 ;No data RAMmed in yet STR R14,xload__rammed ;So save this information ; --- Return the buffer information --- ADR R14,xload__buffer ;Find my buffer info LDMIA R14,{R0,R1} ;Load address and size LDMFD R13!,{R2,R3,R12,R14} ;Unstack my registers BICS PC,R14,#V_flag ;And return no error ; --- Things went wrong --- 98xload_initBuf BL xload__loseBuff ;Get rid of transfer buffer 99xload_initBuf LDMFD R13!,{R2,R3,R12,R14} ;Unstack my registers ORRS PC,R14,#V_flag ;And return the error LTORG ; --- xload__startCo --- ; ; On entry: R0 == my coroutine handle ; R10 == pointer to block containing (routine,R10,R12,leafname) ; 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. ; ; The information about the real client is stored on the ; main routine's stack. Before it returns, therefore, we ; need to load it from there and save it somewhere so that ; it will be safe when we start the coroutine properly from ; xload_extend. Therefore we actually start from xload_initBuf ; and save the information on the coroutine's stack, and ; switch back quickly. xload__startCo ROUT LDMIA R10,{R0-R3} ;Load the caller's registers STMFD R13!,{R0-R3} ;Save them on our stack MOV R0,#0 ;Return to main routine BL coRout_switch ;Switch back please LDR R14,xload__flags ;Load my flags word ORR R14,R14,#xlFlag__started ;Coroutine has started now STR R14,xload__flags ;Save flags back again LDMFD R13!,{R2,R10,R12,R14} ;Load client's workspace MOV R0,R14 ;Pass leafname in R0 MOV R1,#0 ;File is not safe MOV R14,PC ;Set up a return address MOV PC,R2 ;And call the saver routine BVS %90xload__startCo ;If it failed, report error MOV R14,#0 ;A nice zero value WSPACE xload__wSpace ;Load my workspace pointer STR R14,xload__coRout ;Write over the coroutine LDR R0,xload__buffer ;Load the buffer address MOV R1,#xload__buffSize ;Get the current buffer size ADR R14,xload__start ;Point to load area STMIA R14,{R0,R1} ;Save the data in there B coRout_end ;Terminate coroutine ; --- Handle its return values --- 90 WSPACE xload__wSpace ;Locate my workspace address LDR R14,xload__flags ;Load the flags word ORR R14,R14,#xlFlag__error ;Set the main error flag STR R14,xload__flags ;Save the flags back again STR R0,xload__error ;And save the error pointer B coRout_end ;And finish the coroutine LTORG ; --- xload_killBuf --- ; ; On entry: -- ; ; On exit: -- ; ; Use: Does a buffer destroy for a failed load operation. EXPORT xload_killBuf xload_killBuf ROUT STMFD R13!,{R0,R12,R14} ;Save some registers WSPACE xload__wSpace ;Find my workspace address LDR R14,xload__flags ;Load my flags word TST R14,#xlFlag__started ;Has the coroutine started? LDMNEFD R13!,{R0,R12,PC}^ ;Yes -- this is an error then ; --- Destroy the buffer and the coroutine --- BL xload__loseBuff ;Get rid of the load buffer LDR R0,xload__coRout ;Load the coroutine handle BL coRout_destroy ;Destroy the coroutine LDMFD R13!,{R0,R12,PC}^ ;Return to caller LTORG ; --- xload_extend --- ; ; On entry: R1 == size of last buffer used for receiving ; ; On exit: R0 == pointer to new buffer ; R1 == size of new buffer ; May return an error ; ; Use: Performs a buffer extent operation during an xload RAM ; transfer. EXPORT xload_extend xload_extend ROUT STMFD R13!,{R12,R14} ;Save some registers WSPACE xload__wSpace ;Find my workspace address ; --- Bump the amount of data received --- LDR R14,xload__rammed ;Find how much was sent ADD R14,R14,R1 ;Add size of last buffer STR R14,xload__rammed ;And save the size back ; --- Let the coroutine do some loading --- BL xload__resume ;Let the coroutine run a bit LDR R14,xload__flags ;Load the coroutine's flags TST R14,#xlFlag__error ;Has it returned an error? BNE %90xload_extend ;Yes -- then return it ; --- Get the next buffer to send from xload__read --- ADR R14,xload__start ;Point to buffer data LDMIA R14,{R0,R1} ;Load the buffer data LDMFD R13!,{R12,R14} ;Unstack the registers BICS PC,R14,#V_flag ;And don't return an error ; --- Client had a wee problem --- 90xload_extend LDR R0,xload__error ;Load the error pointer LDMFD R13!,{R12,R14} ;Unstack the registers ORRS PC,R14,#V_flag ;And return the error LTORG ; --- xload_doneBuf --- ; ; On entry: R1 == total size of data received ; ; On exit: R0 corrupted ; May return an error ; ; Use: Handles the last bufferful of a RAM load. EXPORT xload_doneBuf xload_doneBuf ROUT STMFD R13!,{R12,R14} ;Save some registers WSPACE xload__wSpace ;Find my workspace address ; --- Work out how much of the last buffer was sent --- LDR R14,xload__rammed ;Find how much was sent SUB R0,R1,R14 ;How much of last buffer? STR R0,xload__length ;And save the size back LDR R14,xload__flags ;Load the flags word ORR R14,R14,#xlFlag__finish ;Set the finished flag STR R14,xload__flags ;Save the flags back again TST R14,#xlFlag__started ;Has the coroutine started? STREQ R0,xload__buffUsed ;No -- set buffer size ; --- Now let the coroutine finish off --- BL xload__resume ;Let the coroutine run a bit LDR R14,xload__flags ;Load the coroutine's flags TST R14,#xlFlag__error ;Has it returned an error? BNE %90xload_doneBuf ;Yes -- then return it ; --- Return to caller --- BL xload__loseBuff ;Don't want this any more LDMFD R13!,{R12,R14} ;Unstack the registers BICS PC,R14,#V_flag ;Don't return an error ; --- Coroutine hit a wee problem --- 90xload_doneBuf LDR R0,xload__error ;Load the error pointer LDMFD R13!,{R12,R14} ;Unstack the registers ORRS PC,R14,#V_flag ;And return the error LTORG ; --- xload_done --- ; ; On entry: -- ; ; On exit: -- ; ; Use: Tidies up after a successful load job. EXPORT xload_done xload_done ROUT MOVS PC,R14 ;Nothing to do here ; --- xload_failed --- ; ; On entry: R0 == pointer to error block ; ; On exit: -- ; ; Use: Tidies up a RAM transfer after an error. EXPORT xload_failed xload_failed ROUT STMFD R13!,{R12,R14} ;Save some registers WSPACE xload__wSpace ;Find my workspace LDR R14,xload__flags ;Load my flags word TST R14,#xlFlag__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 --- STMFD R13!,{R0} ;Save some more registers TST R14,#xlFlag__error ;Has he seen the error? BNE %30xload_failed ;Yes -- then don't switch ; --- Get xload__write to return an error --- ORR R14,R14,#xlFlag__error ;Set the error flag STR R0,xload__error ;And save the error pointer BL xload__resume ;Let the coroutine run a bit ; --- Now tidy everything up --- ; ; The coroutine should have killed itself by now 30xload_failed BL xload__loseBuff ;Kill off the data buffer LDMFD R13!,{R0,R12,PC}^ ;Return to caller LTORG ; --- xload__resume --- ; ; On entry: -- ; ; On exit: -- ; ; Use: Lets the coroutine run for a bit. xload__resume ROUT STMFD R13!,{R0,R1,R14} ;Save some registers LDR R0,xload__coRout ;Load the coroutine handle CMP R0,#0 ;Has it finished yet? BLNE coRout_switch ;No -- run it a bit more then LDMNEFD R13!,{R0,R1,PC}^ ;And return to caller LDR R0,xload__buffer ;Load the buffer address MOV R1,#xload__buffSize ;Get the current buffer size ADR R14,xload__start ;Point to load area STMIA R14,{R0,R1} ;Save the data in there LDMFD R13!,{R0,R1,PC}^ ;Return to caller LTORG ; --- xload__setBuff --- ; ; On entry: -- ; ; On exit: May return an error ; ; Use: Sets up the buffer for a data transfer. xload__setBuff ROUT STMFD R13!,{R0-R3,R14} ;Save some registers ; --- Allocate the buffer memory --- MOV R0,#xload__buffSize ;Get the buffer size BL alloc ;Try to allocate the buffer BLCS alloc_error ;Get an error if it failed BCS %90xload__setBuff ;And tidy things up ; --- Set up los of variables --- ADR R14,xload__buffer ;Point at the buffer info MOV R1,#xload__buffSize ;No buffer used yet MOV R2,#0 ;Start buffer from beginning MOV R3,#0 ;No data sent yet either STMIA R14,{R0-R3} ;Save these values away LDMFD R13!,{R0-R3,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-R3,R14} ;Restore all the registers ORRS PC,R14,#V_flag ;And return with V set LTORG ; --- xload__loseBuff --- ; ; On entry: -- ; ; On exit: -- ; ; Use: Kills off the buffer. xload__loseBuff ROUT STMFD R13!,{R0,R14} ;Save some registers LDR R0,xload__buffer ;Load the buffer address BL free ;Free the buffer LDR R14,xload__flags ;Load my current flags word AND R14,R14,#xlFlag__inited ;Only leave inited flag STR R14,xload__flags ;Save the flags back again LDMFD R13!,{R0,PC}^ ;And return to caller LTORG ; --- xload_byte --- ; ; On entry: -- ; ; On exit: CC if data read OK, and ; R0 == byte read ; else CC if end-of-file, and ; R0 corrupted ; May return an error ; ; Use: Reads a byte from the current input. EXPORT xload_byte xload_byte ROUT STMFD R13!,{R0-R2,R14} ;Save some registers MOV R0,R13 ;Overwrite saved R0 MOV R1,#1 ;Just read one byte BL xload_block ;Read it onto the stack STRVS R0,[R13,#0] ;If error, store pointer LDR R0,[R13],#4 ;Load the byte/error pointer ANDVC R0,R0,#&FF ;If byte/EOF, kill other bits LDMFD R13!,{R1,R2,PC} ;And return to caller LTORG ; --- xload_word --- ; ; On entry: -- ; ; On exit: CC if data read OK, and ; R0 == word read ; else CS if end-of-file and ; R0 corrupted ; May return an error ; ; Use: Reads a word from the current input. EXPORT xload_word xload_word ROUT STMFD R13!,{R0-R2,R14} ;Save some registers MOV R0,R13 ;Overwrite saved R0 MOV R1,#4 ;Just read one word BL xload_block ;Read it onto the stack STRVS R0,[R13,#0] ;If error, store pointer LDR R0,[R13],#4 ;Load the byte/error pointer LDMFD R13!,{R1,R2,PC} ;And return to caller LTORG ; --- xload_block --- ; ; On entry: R0 == pointer to buffer to read ; R1 == size of buffer to read ; ; On exit: R0, R1 preserved ; R2 == number of bytes read ; CC if more data available, CS for end-of-file ; May return an error ; ; Use: Reads in a block of data. Data is buffered, so this is ; fairly quick for reading small objects. Large data blocks ; are read directly to avoid buffering overhead. EXPORT xload_block xload_block ROUT CMP R1,#0 ;Is there anything there? MOVEQ R2,#0 ;Nothing read yet BICEQS PC,R14,#V_flag ;No -- return with no error STMFD R13!,{R0,R1,R3-R8,R12,R14} ;Save a load of registers WSPACE xload__wSpace ;Load my workspace address MOV R6,R0 ;Keep his buffer address MOV R7,R1 ;Look after his buffer size MOV R8,#0 ;No data copied yet ; --- First, try to read from the buffer --- ADR R14,xload__buffer ;Point to the buff info LDMIA R14,{R3-R5} ;Load the buffer stuff 00xload_block SUBS R14,R4,R5 ;Find out how much there is BEQ %20xload_block ;No -- skip ahead then ; --- Read as much as we need from buffer --- CMP R7,R14 ;Is there enough there? MOVLT R2,R7 ;Yes -- get as much as we can MOVGE R2,R14 ;Otherwise empty the buffer ADD R1,R3,R5 ;Get the correct address MOV R0,R6 ;Point at his buffer BL fastMove ;Copy the data across nicely ; --- Reset the buffer arguments --- ADD R5,R5,R2 ;Add on the offset now ADD R6,R6,R2 ;Bump up his buffer address SUBS R7,R7,R2 ;And decrease the size ADD R8,R8,R2 ;Bump amount of data read BEQ %90xload_block ;If no more to do, return ; --- Buffer was empty --- ; ; If he wants more than a buffer full, we should just read ; directly into his buffer. Otherwise we fill the buffer ; and loop back again to copy data. 20xload_block CMP R7,#xload__buffSize ;Does he want more? BGE %50xload_block ;Yes -- skip ahead then MOV R0,R3 ;Point to the buffer MOV R1,#xload__buffSize ;Get the buffer size BL xload__read ;Read data into the buffer BVS %95xload_block ;If it failed, return error MOVS R4,R2 ;Get the new buffer size MOV R5,#0 ;Haven't started on it yet BNE %00xload_block ;If anything read, go round B %90xload_block ;Otherwise we'd better stop ; --- Read data into his big buffer --- 50xload_block MOV R0,R6 ;Point to his buffer MOV R1,R7 ;And get his buffer size BL xload__read ;Read data into the buffer BVS %95xload_block ;If it failed, return error ADD R8,R8,R2 ;Bump by amount read ; --- Everything went OK --- 90xload_block ADR R14,xload__buffUsed ;Point to the buff info STMIA R14,{R4,R5} ;Store new offsets MOV R2,R8 ;Get amount of data read LDR R14,xload__total ;Load the total data read ADD R14,R14,R8 ;Add new amount read STR R14,xload__total ;And save back the new total LDR R14,xload__flags ;Load the flags word LDR R1,[R13,#4] ;Load amount he wanted CMP R1,R2 ;Did we read it all? ORRGT R14,R14,#xlFlag__finish ;No -- set eof flag then STRGT R14,xload__flags ;Save the flags back LDMFD R13!,{R0,R1,R3-R8,R12,R14} ;Unstack loads of regs BIC R14,R14,#V_flag ;Clear error indicator ORRGTS PC,R14,#C_flag ;Set Carry on exit if EOF BICLES PC,R14,#C_flag ;Otherwise clear it ; --- We had an error --- 95xload_block ADD R13,R13,#4 ;Don't restore R0 on exit LDMFD R13!,{R1,R3-R8,R12,R14} ;Unstack loads of regs ORRS PC,R14,#V_flag ;Return the error condition LTORG ; --- xload__read --- ; ; On entry: R0 == pointer to buffer to read ; R1 == size of buffer to read ; ; On exit: May return an error ; R2 == actual amount of data read ; ; Use: Reads a block of data from the input stream. xload__read ROUT BIC R14,R14,#V_flag ;Clear error indicator MOV R2,#0 ;Nothing read yet CMP R1,#0 ;Is there anything there? MOVEQS PC,R14 ;No -- return with no error STMFD R13!,{R14} ;Save some registers LDR R14,xload__flags ;Find my flags word TST R14,#xlFlag__finish ;Is the transfer over? LDMNEFD R13!,{PC}^ ;Yes -- return right now TST R14,#xlFlag__ramTran ;Am I using RAM transfer? BNE %50xload__read ;Yes -- do that then ; --- Use OS_HeebieJeebie to read the data --- STMFD R13!,{R0,R1,R3,R4} ;Save some more registers MOV R2,R0 ;Point to the user's buffer MOV R3,R1 ;And get the buffer size LDR R1,xload__file ;Get the file's handle MOV R0,#4 ;Read at current position SWI XOS_GBPB ;Read the data in nicely STRVS R0,[R13,#0] ;If error, save error ptr LDRVC R1,[R13,#4] ;Load number of bytes wanted SUBVC R2,R1,R3 ;Get number actually read LDMFD R13!,{R0,R1,R3,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 read the buffer --- 50xload__read STMFD R13!,{R0} ;Save another register ADR R14,xload__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 R2,xload__length ;Read data size read LDR R14,xload__flags ;Reload the flags TST R14,#xlFlag__error ;Was there a problem? LDMFD R13!,{R0,R14} ;Restore the registers BICEQS PC,R14,#V_flag ;No -- just clear V then LDRNE R0,xload__error ;Otherwise load error pointer ORRNES PC,R14,#V_flag ;And return with V set LTORG xload__wSpace DCD 0 ;----- Constants ------------------------------------------------------------ xload__buffSize EQU 1024 ;1K buffer should be enough ;----- Workspace ------------------------------------------------------------ ^ 0,R12 xload__wStart # 0 xload__flags # 4 ;Various run-time flags xload__coRout # 0 ;The load coroutine handle xload__file # 4 ;The load file handle xload__buffer # 4 ;Pointer to my 1K buffer xload__buffUsed # 4 ;Amount of data in buffer xload__buffOff # 4 ;Offset reading from buffer xload__total # 4 ;How much data read so far xload__error # 0 ;Error pointer from corout xload__start # 4 ;Start of buffer to send xload__length # 4 ;Size of next buffer to send xload__rammed # 4 ;Data RAMFetched so far xload__wSize EQU {VAR}-xload__wStart xlFlag__inited EQU (1<<0) ;Are we initialised already? xlFlag__ramTran EQU (1<<1) ;Are we doing RAM transfer? xlFlag__finish EQU (1<<2) ;This is the very last block xlFlag__error EQU (1<<3) ;Should return an error xlFlag__started EQU (1<<4) ;Coroutine has started AREA |Sapphire$$LibData|,CODE,READONLY DCD xload__wSize DCD xload__wSpace DCD 0 DCD 0 ;----- That's all, folks ---------------------------------------------------- END