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



.Cache_CreateCache
FNdt(">>> Cache_CreateCache")
FNdt("  r0 (number of blocks per cache entry) = %0i4")
;------------------------
; Generates a cache tree of the given size.
;------------------------
; On entry:
;	r0  =	number of blocks per cache entry
; On exit:
;	r0  =	pointer to cache control block
;------------------------
	FNfunction("r1-r6")
	mov	r6,r0		; For cachectrl_numentries%
;------------------------
; Claim memory for the cache control block.
	FNmovc(3, len_cachectrl% << config_cachehashbits%)
	bl	Memory_Claim
	bvs	gerror
	mov	r0,r2
;------------------------
; Claim memory for the cache entries.
	mov	r4,r6,lsl #shift_cacheent%
	mov	r3,r4,lsl #config_cachehashbits%
	bl	Memory_ClaimZero
	bvs	gerror
;------------------------
; Set up and execute loop to fill up cache control blocks.
	mov	r5,#1 << config_cachehashbits%
	mov	r1,r0		; Moving pointer within cache control block.
._lp
	stmia	r1!,{r2,r6}
	add	r2,r2,r4	; Increment pointer to the cache entries.
	subs	r5,r5,#1	; Control blocks counter.
	bgt	_lp
FNdt("<<< Cache_CreateCache")
FNdt("  r0 (pointer to cache control block) = %0x8")
	FNreturn

.gerror
	FNgerror



.Cache_Initialise
FNdt(">>> Cache_Initialise")
;------------------------
; Initialises all the cache information.
;------------------------
; On entry:
;	Nothing required
; On exit:
;	ARP
;------------------------
	FNfunction("r0-r4")
;------------------------
; Set up the A and B cache control blocks.
	mov	r0,#config_blocksA%
	bl	Cache_CreateCache
	bvs	_gerror
	str	r0,[r12,#blocktableA]
	mov	r0,#config_blocksB%
	bl	Cache_CreateCache
	bvs	_gerror
	str	r0,[r12,#blocktableB]
FNdt("<<< Cache_Initialise")
	FNreturn

._gerror
	FNgerror



.Cache_FindBlockInCache
FNdt(">>> Cache_FindBlockInCache")
FNdt("  r0 (block number) = %0i4")
FNdt("  r1 (hash value) = %1x8")
FNdt("  r2 (cache control blocks ptr) = %2x8")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; Tries to find the given block in the cache specified.
;------------------------
; Get a numbered block from disc
; On entry:
;	r0  =	block number
;	r1  =	hash value
;	r2  =	pointer to start of cache control blocks to search
;	r6  =	pointer to MDB
; On exit:
;	r2  =	pointer to cache entry, or zero if block wasn't found
;------------------------
	FNfunction("r0,r1,r3-r11")
	add	r2,r2,r1,lsl #shift_cachectrl%
	ldr	r3,[r2,#cachectrl_numentries%]
	ldr	r2,[r2,#cachectrl_pointer%]
; FNdt("iii Checking in cache control block %2x8")
._lp
	ldr	r1,[r2,#cacheent_blocknum%]
	ldr	r4,[r2,#cacheent_mount%]
; FNdt("iii Checking entry %1x8, %4x8 (%0x8, %6x8), r3 = %3i4")
	teq	r0,r1		; Same block number?
	teqeq	r6,r4		; Same mount descriptor block?
FNdtc(eq, "<<< Cache_FindBlockInCache (succeeded)")
FNdtc(eq, "  r2 (cache entry) = %2x8")
	FNcreturn("EQ")
	add	r2,r2,#len_cacheent%
	subs	r3,r3,#1
	bgt	_lp
	mov	r2,#0		; No matching block was found.
FNdt("<<< Cache_FindBlockInCache (failed)")
FNdt("  r2 (cache entry) = %2x8")
	FNreturn



.Cache_CleanBlock
FNdt(">>> Cache_CleanBlock")
FNdt("  r0 (block number) = %0i4")
FNdt("  r4 (block table entry) = %4x8")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; Writes the block's data back into the filesystem from whence it came.
;------------------------
; On entry:
;	r0  =	block number
;	r4  =	pointer to block table entry
;	r6  =	pointer to MDB
; On exit:
;	All registers preserved.
;------------------------
	FNfunction("r2-r11")
FNdt("<<< Cache_CleanBlock")
	FNreturn



.Cache_GetBlock
FNdt(">>> Cache_GetBlock")
FNdt("  r0 (block number) = %0i4")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; 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")
;------------------------
; Generate the hash value for the given block.
	eor	r1,r0,r0,lsr #13
	eor	r1,r1,r6,lsr #2
	and	r1,r1,#cache_hashmask%
;------------------------
; Search for the block in each cache.
	ldr	r2,[r12,#blocktableB]
	bl	Cache_FindBlockInCache
	teq	r2,#0
	bne	_found_cache_entry
	ldr	r2,[r12,#blocktableA]
	bl	Cache_FindBlockInCache
	teq	r2,#0
	beq	_cache_miss
;------------------------
; Find a block in cache B which we can promote our cache A block into.
	ldr	r3,[r12,#blocktableB]
	add	r3,r3,r1,lsl #shift_cachectrl%
	ldr	r3,[r3,#cachectrl_pointer%]
	FNrnd(4)
	and	r4,r4,#config_blocksB%-1
	add	r3,r3,r4,lsl #shift_cacheent%
;------------------------
; Switch the two, demoting the block from B, and promoting that from A.
	ldmia	r2,{r4,r5,r7,r8}
	ldmia	r3,{r9,r10,r11,r14}
	stmia	r3,{r4,r5,r7,r8}
	stmia	r2,{r9,r10,r11,r14}
	mov	r2,r3		; The required block has moved to here.
	b	_found_cache_entry

._found_cache_entry
;------------------------
; We've found the entry - its cacheent is in r2.
	mov	r1,r2
	ldr	r0,[r2,#cacheent_pointer%]
FNdt("<<< Cache_GetBlock")
FNdt("  r0 (block pointer) = %0x8")
FNdt("  r1 (cache entry) = %1x8")
	FNreturn

._cache_miss
;------------------------
; Find a block in cache A we can load the data into.
	ldr	r3,[r12,#blocktableB]
	add	r3,r3,r1,lsl #shift_cachectrl%
	ldr	r3,[r3,#cachectrl_pointer%]
	FNrnd(4)
	and	r4,r4,#config_blocksB%-1
	add	r4,r3,r4,lsl #shift_cacheent%
;------------------------
; If there's no block currently there, claim one.
	ldr	r2,[r4,#cacheent_pointer%]
	ldr	r8,[r6,#s_log_block_size%]
	mov	r3,#1024	; Get block size in bytes.
	mov	r3,r3,lsl r8
	teq	r2,#0
	beq	_claim_target_block
;------------------------
; If there's an existing block which is the right size, use that.
	ldr	r1,[r4,#cacheent_flags%]
	tst	r1,#fcacheent_dirty%
	blne	Cache_CleanBlock
	and	r1,r1,#fcacheent_blksize%
	cmp	r1,r8		; Compare against filesystem's block size.
	beq	_existing_block_ok
	bllt	Memory_Free	; Free old block (wrong size).
	movlt	r2,#0
._claim_target_block
	bl	Memory_Resize	; Claim new block (right size!).
	bvs	_gerror		; !!! Handle this more gracefully.
	str	r2,[r4,#cacheent_pointer%]
._existing_block_ok
;------------------------
; Store all pertinent information about this block.
	movs	r7,r0		; Put block number into a stmable register.
				; set flags here - EQ implies hole
	stmib	r4,{r6-r8}	; Mount, block num, flags (block size).
;------------------------
; Store all pertinent information about this block.
	mov	r1,r4		; Return value - pointer to cache entry.
	mov	r0,r2		; Return value - pointer to block data.
	beq	_read_from_hole
	add	r8,r8,#10	; Shift for block num -> file offset.
FNdt("  r7 () = %7x8")
	mov	r4,r7,lsl r8	; Get file offset (low) from block number.
	rsb	r14,r8,#32
	mov	r5,r7,lsr r14	; Get file offset (high) from block number.
FNdt("  r5 (high) = %5x8")
FNdt("  r4 (low)  = %4x8")
	mov	r14,pc
	ldr	pc,[r6,#mnt_readbytes%]
FNdt("<<< Cache_GetBlock")
FNdt("  r0 (block pointer) = %0x8")
FNdt("  r1 (cache entry) = %1x8")
	FNreturn

._gerror
	FNgerror

._read_from_hole
;------------------------
; Read bytes from a black hole.
;------------------------
; On entry:
;	r2  =	pointer to buffer
;	r3  =	number of bytes to read
;	r4  =	partition offset
;	r6  =	pointer to mount descriptor block
; On exit:
;	r3  =	number of bytes not read
;------------------------
	mov	r1,r2
	mov	r2,r3
	bl	Memory_ZeroBlock
FNdt("<<< Cache_GetBlock (hole)")
FNdt("  r0 (block pointer) = %0x8")
FNdt("  r1 (cache entry) = %1x8")
	FNreturn



.Cache_FindInode
FNdt(">>> Cache_FindInode")
FNdt("  r0 (pathname) = %0z")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; 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
FNdtc(eq, "<<< Cache_FindInode (root)")
FNdtc(eq, "  r0 (inode number) = %0i4")
FNdtc(eq, "  r1 (filetype) = %1x8")
	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
FNdtc(vs, "<<! Cache_FindInode (Directory_Scan failed)")
	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 0-terminator
	TEQ	r10,#0			; more subentries?
	BNE	_dir_loop		; yes
	MOV	r0,r2			; nope
FNdt("<<< Cache_FindInode")
FNdt("  r0 (block pointer) = %0x8")
FNdt("  r1 (cache entry) = %1x8")
	FNreturn

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

._match_leaf
FNdt("_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%]
	ADD	r6,r0,#dirent_name%
	MOV	r7,r7,LSR #16
	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
FNdt(">>> Cache_GetInodePointer")
FNdt("  r0 (inode number) = %0i4")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; 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%]
	MOV	r14, #1			; StrongARM order
	ADD	r1, r1, #3
	ADD	r0, r0, r2, LSR r1
	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
FNdt("<<< Cache_GetInodePointer")
FNdt("  r0 (inode structure) = %0x8")
	FNcreturn("VC")
	FNpreturn



.Cache_BlockFromInode
FNdt(">>> Cache_BlockFromInode")
FNdt("  r0 (block number) = %0i4")
FNdt("  r1 (inode definition) = %1x8")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; 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
FNdtc(vs, "<<! Cache_BlockFromInode (Cache_BlockNumberFromInode failed)")
	FNpcreturn("VS")
	CMP	r0,#0
FNdtc(lt, "<<? Cache_BlockFromInode (negative block pointer)")
	FNcreturn("LT")
	BL	Cache_GetBlock
	MOV	r2,r1
	ADD	r1,r0,r3
FNdt("<<< Cache_BlockFromInode")
FNdt("  r0 (pointer to block) = %0x8")
FNdt("  r1 (pointer to end of block) = %1x8")
FNdt("  r2 (block table entry) = %2x8")
	FNreturn


.Cache_BlockNumberFromInode
FNdt(">>> Cache_BlockNumberFromInode")
FNdt("  r0 (block number) = %0i4")
FNdt("  r1 (inode definition ptr) = %1x8")
FNdt("  r6 (MDB) = %6x8")
;------------------------
; 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 odd reason it's 2x too big
	bge	_no_such_block
	ldr	r2,[r6,#s_log_block_size%]
	add	r1,r1,#i_block%
	add	r8, r2, #8	; 8=10-2 (10 add to s_log, 2 for 1word/entry)
	cmp	r0,#12		; main section?
	bge	_sub_block
	ldr	r0,[r1,r0,LSL #2]; get block number
FNdt("<<< Cache_BlockNumberFromInode (primary block)")
FNdt("  r0 (block number) = %0i4")
	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]
FNdt("<<< Cache_BlockNumberFromInode (indirected block)")
FNdt("  r0 (block number) = %0i4")
	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]
FNdt("<<< Cache_BlockNumberFromInode (doubly indirected block)")
FNdt("  r0 (block number) = %0i4")
	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,r8,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]
FNdt("<<< Cache_BlockNumberFromInode (triply indirected block)")
FNdt("  r0 (block number) = %0i4")
	FNcreturn("VC")
	FNpreturn

._no_such_block
FNdt("<<? Cache_BlockNumberFromInode (unknown block)")
FNdt("  r0 (block number) = %0i4 (returning -1)")
	MVN	r0,#0			; return -1 (no more blocks)
	FNreturn
