;
; dynamite.s
;
; Memory management in a dynamic area
;
;  1994-1998 Straylight

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Dynamite
;
; Dynamite 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.
;
; Dynamite 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 Dynamite.  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

		GET	libs:stream

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

		GET	sh.dynAnchor
		GET	sh.dynArea
		GET	sh.dynHeap
		GET	sh.dynTask
		GET	sh.wSpace

		GET	sh.messages

		IMPORT	version
		IMPORT	|Dynamite$$Commands$$Base|

;----- Module header --------------------------------------------------------

		AREA	|!!!Module$$Header|,CODE,READONLY

dyn__base	DCD	dt_run			;Application code
		DCD	dyn__init		;Initialisation
		DCD	dyn__quit		;Finalisation
		DCD	dt_service		;Service call handling
		DCD	dyn__name		;Module title string
		DCD	version			;Module help string
		DCD	|Dynamite$$Commands$$Base| ;Command table
		DCD	&4A3C0			;SWI chunk number
		DCD	dyn__swis		;SWI handler code
		DCD	dyn__swiNames		;SWI name table
		DCD	0			;SWI name-number code

dyn__name	DCB	"Dynamite",0

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

		AREA	|Dynamite$$Code|,CODE,READONLY

; --- dyn_base ---
;
; On entry:	--
;
; On exit:	R14 == base address of module
;
; Use:		Finds the base address of the module.

		EXPORT	dyn_base
dyn_base	ROUT

		STMFD	R13!,{R14}
		ADR	R14,dyn__base
		LDMFD	R13!,{PC}^

		LTORG

; --- dyn__init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Claims some memory, and intercepts OS_ChangeDynamicArea.

dyn__init	ROUT

		STMFD	R13!,{R7,R8,R14}	;Save some registers

		; --- Allocate module workspace ---

		MOV	R0,#6			;Claim RMA workspace
		LDR	R3,=dyn_wSize		;Get my workspace size
		SWI	XOS_Module		;Allocate the workspace
		LDMVSFD	R13!,{R7,R8,PC}		;If it failed, return error
		STR	R2,[R12,#0]		;Save workspace address
		MOV	R12,R2			;Point to workspace nicely
		STR	R12,dyn__wsAddr		;Store for my patch code

		MOV	R0,#0			;Initialise some workspace
		STR	R0,dyn_areaSize		;Dynamic area not created yet
		STR	R0,dyn_heapSize		;No data in the heap
		STR	R0,dyn_lockCount	;And clear the lock counter
		STR	R0,dyn_launch		;Make RMRun do clever stuff
		STR	R0,dyn_taskHandle	;And make the task handle 0
		STR	R0,dyn_ancTable		;Clear the free anchor table
		STR	R0,dyn_ancList		;And the anchor block list
		MOV	R0,#hpFlag_tidy		;We are compacted now
		STR	R0,dyn_hpFlags		;So make a note of this

		ADR	R14,dyn_stack		;Point to reloc stack base
		STR	R14,dyn_stackPtr	;Save as the stack pointer

		; --- Claim OS_ChangeDynamicArea ---
		;
		; We trap this in the kernel branch table, rather than at
		; the SWI vector, because it's so much easier.

		SWI	XOS_ReadMemMapInfo	;Read nice things
		STR	R0,dyn_pageSize		;Save the page size
		SUB	R1,R1,#1		;Chop one off for da_findPage
		STR	R1,dyn_pageCount	;And the number of them
		MOV	R14,#0			;Log 2 of page size
00dyn__init	TST	R0,#1			;Is bottom bit set yet?
		MOVEQ	R0,R0,LSR#1		;No -- shift along a bit
		ADDEQ	R14,R14,#1		;Add to the log
		BEQ	%00dyn__init		;And keep on going
		STR	R14,dyn_log2PageSize	;And store this away

		MOV	R0,#129			;The OS_Byte number
		MOV	R1,#0			;Lovely...
		MOV	R2,#255			;...jubbly!
		SWI	XOS_Byte		;Get OS Version number
		STR	R1,dyn_machine		;Store this away
		CMP	R1,#&A5			;Is this a RISC PC?
		BGE	%50dyn__init		;Yes -- jump ahead then
		CMP	R1,#&A3			;Is it RISC OS 2?
		LDRLT	R0,=&ABC		;Yes --- this is sprarea size
		LDRGE	R0,=&ACC		;No -- this is then
		STR	R0,dyn_sprSize		;Save in the workspace

		MOV	R6,PC			;Get the current status
		TST	R6,#IRQ_disable		;Are IRQs enabled?
		TEQEQP	R6,#IRQ_disable		;Yes -- disable them then

		LDR	R0,=&01F033FC		;Find the kernel dispatcher
		LDR	R14,[R0,#OS_ChangeDynamicArea*4]
		STR	R14,dyn_oldChnArea	;Save this in my workspace
		ADR	R14,dyn__patch		;Point to my patch routine
		STR	R14,[R0,#OS_ChangeDynamicArea*4]

		LDR	R14,[R0,#OS_ReadDynamicArea*4]
		STR	R14,dyn_oldReadArea	;Save this in my workspace
		ADR	R14,dyn__readPatch	;Point to my patch routine
		STR	R14,[R0,#OS_ReadDynamicArea*4]

		LDR	R14,[R0,#OS_ValidateAddress*4]
		STR	R14,dyn_oldValidate	;Save this in my workspace
		ADR	R14,dyn__valPatch	;Point to my patch routine
		STR	R14,[R0,#OS_ValidateAddress*4]
		TEQP	R6,#0			;Restore interrupt status
		MOV	R0,R0			;Shut the assembler up

		; --- Find the TaskManager and WindowManager addresses ---

		ADR	R6,dyn_switchBase	;Point to bit of workspace
		MOV	R0,#18			;Look up modules by name
		ADR	R1,dyn__switcher	;Point to the Switcher's name
		SWI	XOS_Module		;Try and find its address
		MOVVS	R3,#0			;If not there, dummy address
		MOVVS	R4,#0			;For that and the end
		LDRVC	R4,[R3,#-4]		;Otherwise load module length
		STMIA	R6!,{R3,R4}		;Save them in workspace

		ADR	R1,dyn__wimp		;Point to the Wimp's name
		SWI	XOS_Module		;Try and find its address
		MOVVS	R3,#0			;If not there, dummy address
		MOVVS	R4,#0			;For that and the end
		LDRVC	R4,[R3,#-4]		;Otherwise load module length
		STMIA	R6!,{R3,R4}		;Save them in workspace
		LDMFD	R13!,{R7,R8,PC}^	;Return to caller

		; --- The machine is a RISC PC -- lucky sod! ---

50dyn__init	MOV	R0,#0			;Create dynamic area
		MOV	R1,#-1			;No particular number
		MOV	R2,#0			;Initial size of area
		MOV	R3,#-1			;No particular base address
		MOV	R4,#&80			;The area flags
		MOV	R5,#-1			;No maximum size please
		MOV	R6,#0			;No handler needed
		MOV	R7,#0			;Workspace to pass to handler
		ADR	R8,dyn__areaName	;Dynamic area name
		SWI	XOS_DynamicArea		;Create the area
		STRVC	R1,dyn_areaHandle	;Store the handle
		STRVC	R3,dyn_areaBase		;And the base address

		LDMFD	R13!,{R7,R8,PC}		;Return to caller

dyn__switcher	DCB	"TaskManager",0
dyn__wimp	DCB	"WindowManager",0
dyn__areaName	DCB	"Dynamite",0

		LTORG

dyn__wsAddr	DCD	0			;Yuk -- address of workspace

; --- dyn__quit ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Tries to close Dynamite down.

dyn__quit	ROUT

		STMFD	R13!,{R12,R14}		;Save a register
		LDR	R12,[R12]		;Find my workspace address

		; --- Make sure we're not needed ---

		BL	dh_compact		;Compact the heap first
		LDR	R14,dyn_areaSize	;How much space are we using?
		CMP	R14,#0			;Is it 0?
		BNE	%45dyn__quit		;No -- people might want them
		LDR	R14,dyn_stackPtr	;Load the stack pointer
		ADR	R0,dyn_stack		;Point to the stack base
		CMP	R14,R0			;Are these equal?
		BNE	%45dyn__quit		;No -- we're still used then

		; --- Close down the compactor ---

		LDR	R14,dyn_taskHandle	;Get the task handle
		CMP	R14,#0			;Is it a sensible one?
		BLGT	dt_quit			;Yes -- kill the task then
		BL	danc_quit		;Free all anchor blocks

		; --- Get rid of all used pages ---

		LDR	R0,dyn_areaSize		;Get the area size
		LDR	R1,dyn_log2PageSize	;And log 2 of page size
		MOV	R0,R0,LSR R1		;Number of pages in area
		BL	da_removePages		;Remove pages

		LDR	R0,dyn_machine		;Get the machine type
		CMP	R0,#&A5			;Is it a RISC PC?
		BGE	%60dyn__quit		;Yes -- jump ahead

		; --- First, try to release OS_ChangeDynamicArea ---

		LDR	R0,=&01F033FC		;Find the kernel dispatcher
		LDR	R1,[R0,#OS_ChangeDynamicArea*4]
		ADR	R14,dyn__patch		;Point to my patch routine
		CMP	R1,R14			;Can I release it safely?
		BNE	%50dyn__quit		;No -- report an error

		LDR	R1,[R0,#OS_ReadDynamicArea*4]
		ADR	R14,dyn__readPatch	;Point to my patch routine
		CMP	R1,R14			;Can I release it safely?
		BNE	%50dyn__quit		;No -- report an error

		LDR	R1,[R0,#OS_ValidateAddress*4]
		ADR	R14,dyn__valPatch	;Point to my patch routine
		CMP	R1,R14			;Can I release it safely?
		BNE	%50dyn__quit		;No -- report an error

		LDR	R14,dyn_oldChnArea	;Load the previous value
		STR	R14,[R0,#OS_ChangeDynamicArea*4]
		LDR	R14,dyn_oldReadArea	;Load the previous value
		STR	R14,[R0,#OS_ReadDynamicArea*4]
		LDR	R14,dyn_oldValidate	;Load the previous value
		STR	R14,[R0,#OS_ValidateAddress*4]

		; --- Free the workspace I claimed ---

40dyn__quit	MOV	R0,#7			;Free RMA workspace
		MOV	R2,R12			;Point to my workspace
		SWI	XOS_Module		;Try to free it nicely
		LDR	R0,[R13],#4		;Load private word address
		MOV	R14,#0			;Clear private word value
		STR	R14,[R0,#0]		;To stop OS doing this too...
		LDMFD	R13!,{PC}^		;Return to caller

		; --- We have handles or relocation entries ---

45dyn__quit	ADRL	R0,msg_errInUse		;Point to error message
		LDMFD	R13!,{R12,R14}		;Unstack some registers
		ORRS	PC,R14,#V_flag		;Return to caller with error

		; --- We couldn't release OS_ChangeDynamicArea ---

50dyn__quit	ADRL	R0,msg_errRelease	;Point to error message
		LDMFD	R13!,{R12,R14}		;Unstack some registers
		ORRS	PC,R14,#V_flag		;Return to caller with error

		; --- We're on a RISC PC ---

60dyn__quit	MOV	R0,#1			;Remove dynamic area
		LDR	R1,dyn_areaHandle	;Get the area handle
		SWI	XOS_DynamicArea		;Remove it then
		B	%40dyn__quit		;Now free workspace etc.

		LTORG

; --- dyn__patch ---
;
; On entry:	As for ChangeDynamicArea
;
; On exit:	Ditto
;
; Use:		Mangles ChangeDynamicArea when being used on the system
;		sprite area.

dyn__patch	ROUT

		LDR	R12,dyn__wsAddr		;Find my workspace address
		CMP	R0,#3			;Is it the sprite area?
		LDRNE	PC,dyn_oldChnArea	;No -- continue as normal

		; --- Mangle the `remembered' size of the sprite area ---

		STMFD	R13!,{R14}		;Save another register
		LDR	R11,dyn_sprSize		;Find the size word
		LDR	R14,[R11,#0]		;Load total sprite area size
		ADD	R10,R14,R1		;How much does he want?
		CMP	R10,#4*1024*1024	;More than 4 megs?
		BGT	%90dyn__patch		;Yes -- that's an error
		LDR	R10,dyn_areaSize	;Load my area's size
		SUB	R14,R14,R10		;Get the size of the sprites
		STR	R14,[R11,#0]		;Save that for the duration

		; --- This is nasty and complicated ---
		;
		; Post processing on kernel SWIs is not something normally
		; to be enjoyed.  The way things will have to be done is as
		; follows:
		;
		; * We save R14 and the value &2002A (XOS_ChangeDynamicArea)
		;   on the stack.  The SWI value is so that we regain control
		;   if something went sadly amiss.
		;
		; * We call the previous OS_ChangeDynamicArea routine, having
		;   set a suitably bogus return address.
		;
		; * We mangle the system sprite area in a manner in keeping
		;   with the aim of the program.
		;
		; * Then we examine the status returned to us, and if V was
		;   set AND the X bit of the R11 saved on the stack before
		;   we were entered at the top was clear we call
		;   OS_GenerateError.

		MOV	R11,#OS_ChangeDynamicArea ;Get the SWI number
		ORR	R11,R11,#&20000		;Set the X bit nicely
		STMFD	R13!,{R10-R12}		;Save other registers here
		STMFD	R13!,{R11}		;And the obviously bogus R11
		MOV	R14,PC			;Set up a return address
		LDR	PC,dyn_oldChnArea	;And let it rip...

		; --- We have now done a ChangeDynamicArea ---
                ;
                ; R14 saved above is on the stack, along with the caller's
                ; R10-R12 from the OS SWI dispatcher.

		LDR	R11,dyn_sprSize		;Find the size word
		LDR	R14,[R11,#0]		;Load the new area size
		ADD	R14,R14,R10		;Add the old difference
		STR	R14,[R11,#0]		;And save it back again

50dyn__patch	LDMFD	R13!,{R14}		;Get his return address
		LDMFD	R13!,{R11}		;Get the SWI number too
		LDMVCFD	R13!,{R10-R12}		;Restore his registers
		BICVCS	PC,R14,#V_flag		;And return control to him

		TST	R11,#&20000		;Was the X bit set?
		SWIEQ	OS_GenerateError	;No -- do error like things

		LDMFD	R13!,{R10-R12}		;Restore his registers
		ORRS	PC,R14,#V_flag		;And return the error

		; --- He wanted too much memory ---

90dyn__patch	ADRL	R0,msg_errTooBig	;Point to the error
		CMP	R0,#&80000000		;Create an overflow
		B	%50dyn__patch		;Make it look like it came
						;from ChangeDynamicArea
		LTORG

; --- dyn__readPatch ---
;
; On entry:	R0 == dynamic area to read, and flags etc.
;
; On exit:	R1 == size of dynamic area, R2 == optional maximum size
;
; Use:		Reads the size of a dynamic area.  If the caller is
;		interested in the sprite area AND they're not either the
;		TaskManager or the WindowManager, we mangle the result so
;		they think that the sprite area doesn't contain our clever
;		heap.

dyn__readPatch	ROUT

		LDR	R12,dyn__wsAddr		;Find my workspace address
		BIC	R10,R0,#&80		;Clear the clever flag bit
		CMP	R10,#3			;Is it the sprite area?
		LDRNE	PC,dyn_oldReadArea	;No -- continue as normal

		; --- Do all the work then ---
		;
		; Seeing as we know all there is to know about the sprite
		; area, we can do all this here without the yukkiness of
		; postprocessing.

		STMFD	R13!,{R2,R14}		;Save the link register
		ADR	R11,dyn_switchBase	;Find module addresses
		BIC	R10,R14,#&FC000003	;Find caller's address
		LDMIA	R11!,{R1,R2}		;Load switcher base/size
		SUB	R14,R10,R1		;Subtract switcher base
		CMP	R14,R2			;Is it within switcher?
		LDMHSIA	R11!,{R1,R2}		;No -- load wimp base/size
		SUBHS	R14,R10,R1		;Subtract wimp base
		CMPHS	R14,R2			;Is it within wimp?
		LDMFD	R13!,{R2,R14}		;Unstack registers again

		LDR	R1,dyn_sprSize		;Find sprite area size word
		LDR	R1,[R1,#0]		;Load current sprite size
		LDRHS	R10,dyn_areaSize	;If caller is pleb, mangle it
		SUBHS	R1,R1,R10		;By subtracting our heap size
		TST	R0,#&80			;Does he want max size?
		MOVNE	R2,#4*1024*1024		;Yes -- that's 4MB
		MOV	R0,#&01400000		;Point to sprite area start

		ADD	R13,R13,#4		;Skip past stacked R11
		LDMFD	R13!,{R10-R12}		;Unstack caller's registers
		BICS	PC,R14,#V_flag		;And return to caller

		LTORG

; --- dyn__valPatch ---
;
; On entry:	As for OS_ValidateAddress
;
; On exit:	As for OS_ValidateAddress
;
; Use:		Mangles OS_ValidateAddress so that it gets our somewhat
;		rearranged sprite area correct.  This is necessary due to
;		braindead implementation of OS_ValidateAddress.  I'd like to
;		be able to do the job properly by hacking the CAM map, but
;		the Mysterious Background Address Validator assumes that the
;		SWI works in the way Acorn wrote it, so that's put the
;		kybosh on that plan.

dyn__valPatch	ROUT

		LDR	R12,dyn__wsAddr		;Find my workspace address

		STMFD	R13!,{R14}		;Save the link register
		MOV	R10,#&01400000		;Get base of the sprite area
		MOV	R11,#&01800000		;Get limit too
		CMP	R0,R10			;Is it in there?
		CMPCS	R11,R0
		CMPCS	R1,R10
		CMPCS	R11,R1
		LDMCCFD	R13!,{R14}		;No -- restore link
		LDRCC	PC,dyn_oldValidate	;And let OS_VA do it then

		; --- Check for weird sprite area thing and Dynamite area ---

		LDR	R11,dyn_sprSize		;Get address of sprite size
		LDR	R11,[R11,#0]		;Load size of sprite area
		LDR	R14,dyn_areaSize	;Load our area size
		ADD	R11,R10,R11		;Find end of sprite area
		SUB	R10,R11,R14		;Taking our area into account
		MOV	R11,#&01800000		;Find top of sprite slot
		SUB	R11,R11,R14		;Find bottom of our area
		CMP	R11,R0			;This is deep -- think about
		CMPHI	R1,R10			;it for a while.
		LDMFD	R13!,{R14}		;If it isn't invalid...
		ADD	R13,R13,#4
		LDMFD	R13!,{R10-R12}
		ORRHIS	PC,R14,#C_flag
		BICLSS	PC,R14,#C_flag

		LTORG

; --- dyn__swis ---
;
; On entry:	R11 == SWI index
;
; On exit:	Depends on the SWI
;
; Use:		Dispatches SWIs to other routines

dyn__swis	ROUT

		LDR	R12,[R12]		;Find my module workspace

 		CMP	R11,#&3F
 		BEQ	dh_dump

		CMP	R11,#(%10-%00)/4	;Is the SWI in range?
		ADDLO	PC,PC,R11,LSL #2	;Yes -- dispatch it then
		B	%10dyn__swis		;Otherwise report the error

00dyn__swis	B	dh_alloc		;Dynamite_Claim
		B	dh_free			;Dynamite_Free
		B	dh_freeWithID		;Dynamite_FreeWithID
		B	dh_blockInfo		;Dynamite_BlockInfo
		B	dh_changeID		;Dynamite_ChangeID
		B	dh_extend		;Dynamite_Resize
		B	dh_midExtend		;Dynamite_MidExtend
		B	dh_save			;Dynamite_Save
		B	dh_load			;Dynamite_Load
		B	dh_reduce		;Dynamite_Reduce
		B	dh_compact		;Dynamite_Compact
		B	dh_lock			;Dynamite_Lock
		B	dh_unlock		;Dynamite_Unlock
		B	danc_alloc		;Dynamite_ClaimAnchor
		B	danc_free		;Dynamite_ReleaseAnchor
		B	da_readSize		;Dynamite_ReadSpriteSize
		B	da_describe		;Dynamite_Describe
		B	dh_checkHeap		;Dynamite_IntegrityCheck
		B	dh_changeAnchor		;Dynamite_ChangeAnchor

10dyn__swis	ADRL	R0,msg_errBadSWI	;Point at the error
		ORRS	PC,R14,#V_flag		;And return it back

dyn__swiNames	DCB	"Dynamite",0
		DCB	"Alloc",0
		DCB	"Free",0
		DCB	"FreeWithID",0
		DCB	"BlockInfo",0
		DCB	"ChangeID",0
		DCB	"Resize",0
		DCB	"MidExtend",0
		DCB	"Save",0
		DCB	"Load",0
		DCB	"Reduce",0
		DCB	"Compact",0
		DCB	"Lock",0
		DCB	"Unlock",0
		DCB	"ClaimAnchor",0
		DCB	"ReleaseAnchor",0
		DCB	"ReadSpriteSize",0
		DCB	"Describe",0
		DCB	"IntegrityCheck",0
		DCB	"ChangeAnchor",0
		DCB	0

		LTORG

; --- *Dynamite_Clear ---

dyn__clear	ROUT

		STMFD	R13!,{R14}		;Save a register
		LDR	R12,[R12]		;Load the workspace address

		; --- Empty the heap ---

		LDR	R0,dyn_areaSize		;Get the area size
		LDR	R1,dyn_log2PageSize	;And log 2 of page size
		MOV	R0,R0,LSR R1		;Number of pages in area
		BL	da_removePages		;Remove pages

		; --- Free all the anchors ---

		BL	danc_quit		;Free all allocated anchors

		; --- Reset the heap variables ---

		MOV	R0,#0			;Initialise some workspace
		STR	R0,dyn_areaSize		;Dynamic area not created yet
		STR	R0,dyn_heapSize		;No data in the heap
		STR	R0,dyn_lockCount	;And clear the lock counter
		STR	R0,dyn_ancTable		;Clear the free anchor table
		STR	R0,dyn_ancList		;And the anchor block list
		MOV	R0,#hpFlag_tidy		;We are compacted now
		STR	R0,dyn_hpFlags		;So make a note of this

		LDMFD	R13!,{PC}^		;And return to caller

		LTORG

		; --- *Commands ---

		AREA	|Dynamite$$Commands|,CODE,READONLY

		DCB	"Dynamite_Clear",0
		DCD	dyn__clear
		DCD	0
		DCD	synt_clear
		DCD	help_clear

		AREA	|Dynamite$$Commands_End|,CODE,READONLY
		DCD	0

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

		END
