#multipass on
; Cache (Crache?)
; ~~~~~
; Cache control code and general high-level filesystem interface for read
; operations.


.Cache_Initialise
FNdbg_str("Cache_Initialise")
;------------------------
; Initialises all the cache information.
;------------------------
; On entry:
;	Nothing required
; On exit:
;	ARP
;------------------------
	FNfunction("r0-r4")
;------------------------
; claim memory for block caches and tables
;P Cache-line optimisation for cache1 and cache2, which can use the 1024-byte
;P optimised memory copy.  Wastes 32 bytes, but saves about the same by
;P allocating all memory in one chunk (esp nice for RMA-users).
	FNmovc(3,cache_totallen%+32)
	BL	Memory_Claim
	AND	r3,r2,#31
	RSB	r3,r3,#32
	ADD	r2,r2,r3
	STR	r2,[r12,#blockcache1]
	ADD	r2,r2,#config_cache1size%
	STR	r2,[r12,#blockcache2]
	ADD	r2,r2,#config_cache2size%
	STR	r2,[r12,#blocktable1]
	ADD	r2,r2,#cache1_blocks%*len_blocktable%
	STR	r2,[r12,#blocktable2]
;------------------------
; initialise the cache tables
	MVN	r1,#0			; block -1 will never be used
	MOV	r2,#0			; not dirty
	MOV	r3,#0			; null drive number
	LDR	r0,[r12,#blocktable1]
	MOV	r4,#cache1_blocks%	; counter
._loop
	STMIA	r0!,{r1-r3}
	SUBS	r4,r4,#1
	BGT	_loop
	LDR	r0,[r12,#blocktable2]
	MOV	r4,#cache2_blocks%	; counter
._loop
	STMIA	r0!,{r1-r3}
	SUBS	r4,r4,#1
	BGT	_loop
;------------------------
; initialise the records of the next blocks and nametab places to use
	STR	r2,[r12,#blocktouse1]	; already zero (from above)
	STR	r2,[r12,#blocktouse2]
	FNreturn


.Cache_GetBlock
FNdbg_func("Cache_GetBlock","r0,r6","r0,r1")
;------------------------
; Tries to find a block in the cache; if it fails it reads the requested block
; into the next available slot.
;------------------------
; Get a numbered block from disc
; On entry:
;	r0  =	block number
;	r6  =	pointer to MDB
; On exit:
;	r0  =	pointer to block
;	r1  =	pointer to block table entry
;------------------------
	FNfunction("r2-r11")
;------------------------
; try to find the block in the second level cache
	LDR	r4,[r12,#blocktable2]
	MOV	r7,#cache2_blocks%
._loop
	SUBS	r7,r7,#1
	BLT	_fail_cache2
	LDMIA	r4!,{r1-r3}
	TEQ	r3,r6			; same mount?
	TEQEQ	r1,r0			; same block base number?
	BNE	_loop
;------------------------
; match found - return that one
	RSB	r7,r7,#cache2_blocks%
	LDR	r5,[r12,#blockcache2]
	SUB	r7,r7,#1
	ADD	r0,r5,r7,LSL #10	; = blockcache + 1024*blocknumber
	SUB	r1,r4,#len_blocktable%
	FNreturn

._fail_cache2
FNdbg_str("_fail_cache2")
;------------------------
; try to find the block in the first level cache
	LDR	r4,[r12,#blocktable1]
	MOV	r7,#cache1_blocks%
._loop
	SUBS	r7,r7,#1
	BLT	_fail_cache1
	LDMIA	r4!,{r1-r3}
	TEQ	r3,r6			; same mount?
	TEQEQ	r1,r0			; same block base number?
	BNE	_loop
;------------------------
; match found - return that one
	RSB	r7,r7,#cache1_blocks%
	LDR	r5,[r12,#blockcache1]
	SUB	r7,r7,#1
	ADD	r0,r5,r7,LSL #10	; = blockcache + 1024*blocknumber
;------------------------
; we have a cache hit!  move this block into the second-level cache
; Useful regs are:
;	r0  =	address of block in first-level cache
;	r1  =	current blocktable entry - block base number
;	r2  =	current blocktable entry - flags
;	r4  =	address of blocktable entry after current one
	LDR	r5,[r12,#blocktable2]
	LDR	r7,[r12,#blockcache2]
	LDR	r10,[r6,#s_log_block_size%]
	LDR	r8,[r12,#blocktouse2]
	MOVS	r10,r10,LSL #1		; number of blocks to copy
	MOVEQ	r10,#1
	ADD	r9,r8,r10		; check if the blocks will fit
	CMP	r9,#cache2_blocks%
	MOVGT	r8,#0			; no - force to cache start
	STRGT	r10,[r12,#blocktouse2]
	STRLE	r9,[r12,#blocktouse2]
	ADD	r5,r5,r8,LSL #shift1_blocktable%
	ADD	r5,r5,r8,LSL #shift2_blocktable%
#if config_write%
;	BL	Cache_FlushBlock
#endif
	STMIA	r5!,{r1,r2,r6}		; store info for to-be-copied block
	ADD	r1,r7,r8,LSL #10	; position in 2nd-lev cache for data
	MOV	r2,r10,LSL #10		; nbytes to copy
	BL	Memory_QuickCopy	; copy block from 1st to 2nd lev cache
	MOV	r0,r1			; return block in 2nd-lev cache
	MVN	r1,#0			; illegal number for trailing blocks
	MOV	r3,#0			; illegal mount for trailing blocks
._lp
	SUBS	r10,r10,#1
	STMGTIA	r5!,{r1-r3}
	BGT	_lp
	SUB	r1,r4,#len_blocktable%
	FNreturn

._fail_cache1
FNdbg_str("_fail_cache1")
;------------------------
; prepare address to be returned and place in a safe register
;	r0  =	block number
	STMFD	r13!,{r4}
	LDR	r5,[r12,#blocktable1]
	LDR	r7,[r12,#blockcache1]
	LDR	r14,[r6,#s_log_block_size%]
	LDR	r8,[r12,#blocktouse1]
	MOV	r4,r0,LSL r14		; offset in file in 1024-byte blocks
	MOVS	r14,r14,LSL #1		; number of blocks to copy
	MOV	r4,r4,LSL #10		; image-offset to load from
	MOVEQ	r14,#1
	ADD	r9,r8,r14
	CMP	r9,#cache1_blocks%	; check if the blocks will fit
	MOVGT	r8,#0			; no - force to cache start
	MOVGT	r9,r14
	ADD	r5,r5,r8,LSL #shift1_blocktable%
	STR	r9,[r12,#blocktouse1]
	ADD	r5,r5,r8,LSL #shift2_blocktable%
	MOV	r3,#0			; block_flags
	MVN	r1,#0			; illegal number for trailing blocks
	MOV	r10,#0			; illegal mount for trailing blocks
	ADD	r2,r7,r8,LSL #10	; position to load data into
#if config_write%
;	BL	Cache_FlushBlock
#endif
	STMIA	r5!,{r0,r3,r6}		; blocknum, blockflags, mount
	MOV	r11,r14
	ADD	r0,r0,#1
._lp
	SUBS	r11,r11,#1
#if config_write%
;	BLGT	Cache_FlushBlock
#endif
	STMGTIA	r5!,{r1,r3,r10}
	BGT	_lp
	MOV	r3,r14,LSL #10		; size of a FS block
	MOV	r14,pc
	LDR	pc,[r6,#mnt_readbytes%]
	MOVVC	r0,r2
	LDMFD	r13!,{r1}
	SUBVC	r1,r1,#len_blocktable%
	FNcreturn("VC")
	FNpreturn


.Cache_FindInode
FNdbg_func("Cache_FindInode","r0,r6","r0,r1")
FNdbg_indstr(0)
;------------------------
; Returns the inode number of the item described by the given pathname.
;------------------------
; On entry:
;	r0  =	pointer to full pathname
;	r6  =	pointer to MDB
; On exit:
;	r0  =	inode number
;	r1  =	filetype
;------------------------
	FNfunction("r2-r11")
	MOVS	r5,r0
	MOV	r0,#2
	LDRNEB	r1,[r5]
	TEQNE	r1,#0
	FNcreturn("EQ")
	MOV	r2,#2			; inode number of root
._dir_loop
	MOV	r4,#0			; length of string
	MOV	r11,r5
._lp
	LDRB	r14,[r11],#1		; get length of current leafname
	TEQ	r14,#0
	TEQNE	r14,#ASC"."
	ADDNE	r4,r4,#1
	BNE	_lp
	MOV	r0,r2
	MOV	r1,#0
	MOV	r2,#0			; inode number of found inode
	ADR	r11,_match_leaf
	BL	Directory_Scan
	FNpcreturn("VS")
	MOV	r1,r3			; filetype
	TEQ	r2,#0
	BEQ	_not_found
	ADD	r5,r5,r4		; move to next leafname
	LDRB	r10,[r5],#1		; directory separator or zero-terminator
	TEQ	r10,#0			; more subentries?
	BNE	_dir_loop		; yes
	MOV	r0,r2			; nope
	FNreturn

._not_found
	FNrerror
	EQUD	0
	EQUS	"Object not found."+CHR$0:ALIGN

._match_leaf
FNdbg_str("_match_leaf")
;------------------------
; Code segment for use with Directory_Scan which checks if the name of the item
; given matches the requested name.  Exits immediately if match is found.
;------------------------
; On entry:
;	r0  =	pointer to block containing dirent
;	r4  =	length of leafname to find
;	r5  =	pointer to leafname
; On exit:
;	r0  =	-1 if matched, unchanged if didn't
;	r2  =	inode number if matched, unchanged if didn't
;	r3  =	filetype of file (if r0=-1)
;------------------------
	FNfunction("r4-r7")
	LDR	r7,[r0,#dirent_entry_length%]
	MOV	r7,r7,LSR #16
	ADD	r6,r0,#dirent_name%
	SUB	r7,r7,r4
._lp
	LDRB	r3,[r6],#1
	LDRB	r14,[r5],#1
	TEQ	r3,#ASC"."
	MOVEQ	r3,#ASC"/"
	TEQ	r3,r14
	FNcreturn("NE")
	SUBS	r4,r4,#1
	BGT	_lp
	CMP	r7,#0			; possibility of a comma?
	BLE	_no_comma
	LDRB	r3,[r6],#1
	TEQ	r3,#ASC","
	FNcreturn("NE")			; wrong length and no comma
;------------------------
; get the filetype from the ,b21 etc
	MOV	r7,r0
	LDRB	r3,[r6]
	LDRB	r4,[r6,#1]
	LDRB	r5,[r6,#2]
	ORR	r3,r3,r4,LSL #8
	ORR	r3,r3,r5,LSL #16
	STR	r3,_buff
	ADR	r1,_buff
	MOV	r0,#16
	ORR	r0,r0,#1<<29		; must be in range 0->r2
	MOV	r2,#&f00
	ORR	r2,r2,#&ff
	SWI	"XOS_ReadUnsigned"
	MOV	r3,r2			; value
	LDR	r2,[r7,#dirent_inode%]
	MVN	r0,#0
	FNreturn

;------------------------
; matched - return instantly with inode
._no_comma
	LDR	r2,[r0,#dirent_inode%]
	MVN	r0,#0
	FNlmov(3,filetype_default%)
	FNreturn
._buff
	EQUD	0


.Cache_GetInodePointer
FNdbg_func("Cache_GetInodePointer","r0,r6","r0")
;------------------------
; Given the inode number, returns a pointer to the inode desciptor.  Block
; locking has been removed, and instead the inode contents will be copied into
; the global temporary workspace in case it is flushed from the buffers.
;------------------------
; On entry:
;	r0  =	inode number
;	r6  =	pointer to MDB
; On exit:
;	r0  =	pointer to inode structure
;------------------------
	FNfunction("r1-r4")
	SUB	r0,r0,#1		; inodes are numbered from 1 - b'zarre
	LDR	r3,[r6,#s_inodes_per_group%]
	MOV	r2,r0
	BL	Divide			; r0 = r2/r3 ; r2 = r2%r3
	LDR	r1,[r6,#s_blocks_per_group%]
	LDR	r14,[r6,#mnt_groupdescs%]
	MUL	r4,r1,r0		; first block of group containing inode
	ADD	r0,r14,r0,LSL #shift_groupdesc%
	LDR	r1,[r0,#bg_inode_table%]
	ADD	r0,r1,r4		; block number of inode table start
	LDR	r1,[r6,#s_log_block_size%]
	ADD	r1, r1, #3
	ADD	r0, r0, r2, LSR r1
	MOV	r14, #1
	MOV	r14, r14, LSL r1
	SUB	r14, r14, #1
	AND	r2, r2, r14
	BLVC	Cache_GetBlock
	STRVC	r0,[r6,#mnt_inodeblockaddr%]
	STRVC	r1,[r6,#mnt_inodetabaddr%]
	ADDVC	r0,r0,r2,LSL #7		; inode offset in block
	FNcreturn("VC")
	FNpreturn


.Cache_BlockFromInode
FNdbg_func("Cache_BlockFromInode","r0,r1,r6","r0-r2")
;------------------------
; Returns a pointer to the nth block of an inode.
;------------------------
; On entry:
;	r0  =	block number
;	r1  =	pointer to inode definition
;	r6  =	pointer to MDB
; On exit:
;	r0  =	pointer to block
;	r1  =	pointer to end of block
;	r2  =	pointer to block table entry
;------------------------
	FNfunction("r3")
	LDR	r2,[r6,#s_log_block_size%]
	MOV	r3,#1024
	MOV	r3,r3,LSL r2
	BL	Cache_BlockNumberFromInode
	FNpcreturn("VS")
	CMP	r0,#0
	FNcreturn("LT")
	BL	Cache_GetBlock
	MOV	r2,r1
	ADD	r1,r0,r3
	FNreturn


.Cache_BlockNumberFromInode
FNdbg_func("Cache_BlockNumberFromInode","r0,r1,r6","r0")
;------------------------
; Gets the block number of an inode's nth block.
;------------------------
; On entry:
;	r0  =	block number
;	r1  =	pointer to inode definition
;	r6  =	pointer to MDB
; On exit:
;	r0  =	block number
;------------------------
	FNfunction("r1-r5,r7,r8")
	LDR	r2,[r1,#i_blocks%]
	CMP	r0,r2,LSR #1		; for some b'zarre reason tis 2x too big
	BGE	_no_such_block
	LDR	r2,[r6,#s_log_block_size%]
	ADD	r8, r2, #8
	ADD	r1,r1,#i_block%
	CMP	r0,#12			; main section?
	BGE	_sub_block
	LDR	r0,[r1,r0,LSL #2]	; get block number
	FNreturn

._sub_block
	SUB	r4,r0,#12
	MOV	r3,#1
	MOV	r3,r3,LSL r8		; number of words in one block
	CMP	r4,r3			; this level ok?
	BGE	_sub_sub_block
	LDR	r0,[r1,#12*4]		; get 13th block number
	BL	Cache_GetBlock
	LDRVC	r0,[r0,r4,LSL #2]
	FNcreturn("VC")
	FNpreturn

._sub_sub_block
	SUB	r4,r4,r3
	MOV	r3,r3,LSL r8
	CMP	r4,r3			; this level ok?
	BGE	_sub_sub_sub_block
	LDR	r0,[r1,#13*4]		; get 14th block number
	BL	Cache_GetBlock
	MOVVC	r7,r4,LSR r8		; get next parent block
	LDRVC	r0,[r0,r7,LSL #2]
	BLVC	Cache_GetBlock
	SUBVC	r4,r4,r7,LSL r8
	LDRVC	r0,[r0,r4,LSL #2]
	FNcreturn("VC")
	FNpreturn

._sub_sub_sub_block
	SUB	r4,r4,r3
	LDR	r0,[r1,#14*4]		; get 15th block number
	BL	Cache_GetBlock
	MOVVC	r3,r2,LSL #1
	MOVVC	r7,r4,LSR r3
	LDRVC	r0,[r0,r7,LSL #2]
	SUBVC	r4,r4,r7,LSL r3
	BLVC	Cache_GetBlock
	MOVVC	r7,r4,LSR r8		; get next parent block
	LDRVC	r0,[r0,r7,LSL #2]
	BLVC	Cache_GetBlock
	SUBVC	r4,r4,r7,LSL r8
	LDRVC	r0,[r0,r4,LSL #2]
	FNcreturn("VC")
	FNpreturn

._no_such_block
	MVN	r0,#0			; return -1 (no more blocks)
	FNreturn
