; ============================================================================= ; fs.asm - a small block-based filesystem (inodes + a root directory) on the ; persistent block device (blk.asm). Same syscall interface as before ; (open/close/getb/putb/list/remove) so the tools and libc are unchanged. ; ; Bitmaps (block 1) and the inode table (blocks 2-3) are cached in WRAMX; file ; and directory data stream through fs_datbuf / fs_dirbuf. Metadata bitmap ; writes are write-through; inode writes are flushed on close. ; ============================================================================= INCLUDE "include/gbos.inc" SECTION "fs", ROM0 ; ----------------------------------------------------------------------------- ; small helpers ; ----------------------------------------------------------------------------- zero256: ; HL = buffer; zero 256 bytes ld b, 0 xor a .z ld [hl+], a dec b jr nz, .z ret ; inode_ptr - A = inode -> HL = &inode in fs_inobuf (= fs_inobuf + inode*16) inode_ptr:: ld c, a and $0F swap a ld l, a ; (inode & 15) << 4 ld a, c swap a and $0F ; inode >> 4 add HIGH(fs_inobuf) ld h, a ret ; write_inode - A = inode ; flush the inode's block (2 or 3) to disk write_inode:: cp 16 jr nc, .hi ld hl, fs_inobuf ld a, BLK_INODE0 jp write_block .hi ld hl, fs_inobuf + 256 ld a, BLK_INODE0 + 1 jp write_block ; bit_addr - A = index, HL = bitmap base -> HL = &byte, E = mask(1< A = block# (>=4) or 0 if the disk is full alloc_block:: ld c, BLK_DATA0 .l ld a, c ld hl, fs_bmbuf + BM_BLOCKS call bit_addr ; HL=&byte, E=mask ld a, [hl] and e jr z, .found inc c ld a, c cp FS_NBLOCKS jr c, .l xor a ret .found ld a, [hl] or e ld [hl], a ld a, c ; save block# (write_bitmap clobbers C) push af call write_bitmap pop af ret ; free_block - A = block# free_block:: ld hl, fs_bmbuf + BM_BLOCKS call bit_addr ld a, e cpl ld e, a ld a, [hl] and e ld [hl], a jp write_bitmap ; alloc_inode -> A = inode# (>=2) or 0 alloc_inode:: ld c, 2 .l ld a, c ld hl, fs_bmbuf + BM_INODES call bit_addr ld a, [hl] and e jr z, .found inc c ld a, c cp NINODES jr c, .l xor a ret .found ld a, [hl] or e ld [hl], a ld a, c ; save inode# (write_bitmap clobbers C) push af call write_bitmap pop af ret ; free_inode - A = inode# free_inode:: ld hl, fs_bmbuf + BM_INODES call bit_addr ld a, e cpl ld e, a ld a, [hl] and e ld [hl], a jp write_bitmap ; ----------------------------------------------------------------------------- ; data-block cache (one block in fs_datbuf; wFsCurBlk / wFsDirty) ; ----------------------------------------------------------------------------- db_flush:: ld a, [wFsDirty] or a ret z xor a ld [wFsDirty], a ld a, [wFsCurBlk] cp $FF ret z ld hl, fs_datbuf jp write_block ; db_use - A = block ; ensure that block is loaded in fs_datbuf db_use:: ld c, a ld a, [wFsCurBlk] cp c ret z push bc ; db_flush clobbers C (write_block does ld c,a) call db_flush pop bc ld a, c ld [wFsCurBlk], a ld hl, fs_datbuf jp read_block ; ----------------------------------------------------------------------------- ; name compare: [HL] vs wFsNameBuf, <=15 bytes NUL-terminated -> Z if equal. ; preserves HL. ; ----------------------------------------------------------------------------- name_eq: push hl ld de, wFsNameBuf ld b, 15 .c ld a, [de] cp [hl] jr nz, .ne or a jr z, .eq inc hl inc de dec b jr nz, .c .eq pop hl xor a ret .ne pop hl ld a, 1 and a ret ; entry_ptr - C = entry index -> HL = &fs_dirbuf[C*16] entry_ptr: ld a, c swap a ; C*16 (C < 16) ld hl, fs_dirbuf add l ld l, a ret ; (fs_dirbuf is page-aligned, no carry) ; load_rootdir - read the root directory's data block into fs_dirbuf load_rootdir: ld a, [fs_inobuf + 16 + I_BLOCKS] ; root inode (1) blocks[0] ld hl, fs_dirbuf jp read_block ; write_rootdir - write fs_dirbuf back to the root directory's data block write_rootdir: ld a, [fs_inobuf + 16 + I_BLOCKS] ld hl, fs_dirbuf jp write_block ; ----------------------------------------------------------------------------- ; dir_find - name in wFsNameBuf -> A = inode (0 if not found) ; ----------------------------------------------------------------------------- dir_find:: call load_rootdir ld c, 0 .l call entry_ptr ; HL = &entry ld a, [hl] ; entry.inode or a jr z, .next inc hl ; &name call name_eq jr z, .found .next inc c ld a, c cp DIRENTS jr c, .l xor a ret .found dec hl ; back to entry base ld a, [hl] ret ; ----------------------------------------------------------------------------- ; dir_add - A = inode to link (name in wFsNameBuf) -> A = 1 ok / 0 dir full ; ----------------------------------------------------------------------------- dir_add:: ld [wFsInode], a call load_rootdir ld c, 0 .l call entry_ptr ld a, [hl] or a jr z, .free inc c ld a, c cp DIRENTS jr c, .l xor a ret .free ld a, [wFsInode] ld [hl+], a ; entry.inode ld de, wFsNameBuf ; copy the name ld b, 15 .cp ld a, [de] ld [hl+], a inc de dec b jr nz, .cp call write_rootdir ld a, 1 ret ; ----------------------------------------------------------------------------- ; dir_remove - name in wFsNameBuf -> A = inode removed (0 if not found) ; ----------------------------------------------------------------------------- dir_remove:: call load_rootdir ld c, 0 .l call entry_ptr ld a, [hl] or a jr z, .next inc hl call name_eq jr z, .found .next inc c ld a, c cp DIRENTS jr c, .l xor a ret .found dec hl ; entry base ld a, [hl] ld [wFsInode], a xor a ld [hl], a ; clear entry.inode call write_rootdir ld a, [wFsInode] ret ; ----------------------------------------------------------------------------- ; file_truncate - A = inode ; free all data blocks, size = 0 (cached inode) ; ----------------------------------------------------------------------------- file_truncate:: ld [wFsInode], a call inode_ptr ld a, l add I_BLOCKS ld l, a ; HL = &blocks[0] (fs_inobuf, no page cross) ld b, NDIRECT .fb ld a, [hl] or a jr z, .skip push bc push hl call free_block pop hl pop bc xor a ld [hl], a .skip inc hl dec b jr nz, .fb ; size = 0 ld a, [wFsInode] call inode_ptr ld a, l add I_SIZE ld l, a xor a ld [hl+], a ld [hl], a ld a, [wFsInode] jp write_inode ; ----------------------------------------------------------------------------- ; format + mount ; ----------------------------------------------------------------------------- fs_format:: ; superblock (block 0) ld hl, fs_datbuf call zero256 ld hl, fs_datbuf ld a, SUPER_MAG0 ld [hl+], a ld a, SUPER_MAG1 ld [hl+], a ld a, SUPER_MAG2 ld [hl+], a ld a, SUPER_MAG3 ld [hl+], a ld a, FS_VERSION ld [hl], a ld a, BLK_SUPER ld hl, fs_datbuf call write_block ; bitmaps (block 1): blocks 0-4 used, inodes 0-1 used ld hl, fs_bmbuf call zero256 ld a, $1F ld [fs_bmbuf + BM_BLOCKS], a ld a, $03 ld [fs_bmbuf + BM_INODES], a call write_bitmap ; inode table (blocks 2-3): zero, then inode 1 = root dir ld hl, fs_inobuf call zero256 ld hl, fs_inobuf + 256 call zero256 ld hl, fs_inobuf + 16 ; inode 1 ld a, IT_DIR ld [hl], a ; I_TYPE ld a, 1 ld [fs_inobuf + 16 + I_NLINK], a xor a ld [fs_inobuf + 16 + I_SIZE], a ; size lo ld a, 1 ld [fs_inobuf + 16 + I_SIZE + 1], a ; size hi = 256 (one dir block) ld a, BLK_DATA0 ld [fs_inobuf + 16 + I_BLOCKS], a ; blocks[0] = block 4 ld a, BLK_INODE0 ld hl, fs_inobuf call write_block ld a, BLK_INODE0 + 1 ld hl, fs_inobuf + 256 call write_block ; root dir data block (block 4): empty ld hl, fs_dirbuf call zero256 ld a, BLK_DATA0 ld hl, fs_dirbuf jp write_block ; fs_init - mount: format on first boot (magic/version check), load caches. fs_init:: ld a, $FF ld [wFsCurBlk], a xor a ld [wFsDirty], a ; check superblock ld a, BLK_SUPER ld hl, fs_datbuf call read_block ld hl, fs_datbuf ld a, [hl+] cp SUPER_MAG0 jr nz, .fmt ld a, [hl+] cp SUPER_MAG1 jr nz, .fmt ld a, [hl+] cp SUPER_MAG2 jr nz, .fmt ld a, [hl+] cp SUPER_MAG3 jr nz, .fmt ld a, [hl] cp FS_VERSION jr z, .load .fmt call fs_format .load ld a, BLK_BITMAP ld hl, fs_bmbuf call read_block ld a, BLK_INODE0 ld hl, fs_inobuf call read_block ld a, BLK_INODE0 + 1 ld hl, fs_inobuf + 256 call read_block ld a, $FF ld [wFsCurBlk], a ret ; ----------------------------------------------------------------------------- ; OFPtr - A = fd -> HL = &open-file entry. preserves DE. ; ----------------------------------------------------------------------------- OFPtr:: push de ld h, 0 ld l, a ld d, h ld e, l add hl, hl add hl, hl add hl, de ; 5*fd ld de, wOFTable add hl, de pop de ret ; copy the NUL-terminated name at DE (process memory) into wFsNameBuf. copy_name: ld hl, wFsNameBuf ld b, 15 .c ld a, [de] ld [hl+], a or a ret z inc de dec b jr nz, .c xor a ld [hl], a ret ; ============================================================================= ; sys_open(DE = name, B = mode 0=read/1=write) -> A = fd or $FF ; ============================================================================= sys_open:: ld a, b ld [wFsMode], a call copy_name ; name -> wFsNameBuf (before any block I/O) call dir_find ; A = inode or 0 ld [wFsInode], a ld a, [wFsMode] or a jr nz, .write ; --- read --- ld a, [wFsInode] or a jr z, .fail jr .allocof .write ld a, [wFsInode] or a jr nz, .trunc ; --- create --- call alloc_inode or a jr z, .fail ld [wFsInode], a call inode_ptr ; HL = &inode push hl ld b, 16 xor a .zi ld [hl+], a dec b jr nz, .zi pop hl ld a, IT_FILE ld [hl], a inc hl ld a, 1 ld [hl], a ; nlink ld a, [wFsInode] call write_inode ld a, [wFsInode] call dir_add or a jr z, .fail jr .allocof .trunc ld a, [wFsInode] call file_truncate .allocof ld c, 0 .ofl ld a, c call OFPtr ld a, [hl] or a jr z, .gotof inc c ld a, c cp OF_MAX jr c, .ofl .fail ld a, $FF ret .gotof ld a, 1 ld [hl+], a ; inuse ld a, [wFsInode] ld [hl+], a ; inode ld a, [wFsMode] ld [hl+], a ; mode xor a ld [hl+], a ; pos lo ld [hl], a ; pos hi ld a, c ret ; ============================================================================= ; sys_close(B = fd) - flush data + inode, free the descriptor ; ============================================================================= sys_close:: ld a, b cp OF_MAX jr nc, .done ld [wFsSlot], a ; save fd call OFPtr ld a, [hl] or a jr z, .done inc hl ld a, [hl] ld [wFsInode], a call db_flush ld a, [wFsInode] call write_inode ld a, [wFsSlot] call OFPtr xor a ld [hl], a ; inuse = 0 .done xor a ret ; ============================================================================= ; sys_getb(B = fd) -> A = byte, CF set on EOF ; ============================================================================= sys_getb:: ld a, b cp OF_MAX jr nc, .eof call OFPtr ld a, [hl] or a jr z, .eof ld a, l ld [wFsOFPtr], a ld a, h ld [wFsOFPtr+1], a inc hl ld a, [hl] ld [wFsInode], a inc hl inc hl ; OF_POS lo ld a, [hl+] ld e, a ld a, [hl] ld d, a ; DE = pos ; pos < size ? ld a, [wFsInode] call inode_ptr ld a, l add I_SIZE ld l, a ld a, [hl+] ld c, a ld a, [hl] ld b, a ; BC = size ld a, e sub c ld a, d sbc b jr nc, .eof ; pos >= size ; data block = inode.blocks[pos/256 = D] ld a, [wFsInode] call inode_ptr ld a, l add I_BLOCKS ld l, a ld a, d add l ld l, a ; &blocks[blkidx] ld a, [hl] push de call db_use pop de ; byte = fs_datbuf[E] ld hl, fs_datbuf ld a, e add l ld l, a ld a, h adc 0 ld h, a ld a, [hl] ld [wFsByte], a inc de ; pos++ ld a, [wFsOFPtr] add OF_POS ld l, a ld a, [wFsOFPtr+1] adc 0 ld h, a ld a, e ld [hl+], a ld a, d ld [hl], a ld a, [wFsByte] and a ; CF = 0 ret .eof scf ret ; ============================================================================= ; sys_putb(B = fd, E = byte) ; ============================================================================= sys_putb:: ld a, e ld [wFsByte], a ld a, b cp OF_MAX jr nc, .done call OFPtr ld a, [hl] or a jr z, .done ld a, l ld [wFsOFPtr], a ld a, h ld [wFsOFPtr+1], a inc hl ld a, [hl] ld [wFsInode], a inc hl inc hl ld a, [hl+] ld e, a ld a, [hl] ld d, a ; DE = pos ; ensure inode.blocks[pos/256] is allocated ld a, [wFsInode] call inode_ptr ld a, l add I_BLOCKS ld l, a ld a, d add l ld l, a ; HL = &blocks[blkidx] ld a, [hl] or a jr nz, .haveblk push hl push de call alloc_block pop de pop hl or a jr z, .done ; disk full ld [hl], a .haveblk ld a, [hl] ; data block# push de call db_use pop de ld hl, fs_datbuf ld a, e add l ld l, a ld a, h adc 0 ld h, a ld a, [wFsByte] ld [hl], a ld a, 1 ld [wFsDirty], a inc de ; pos++ ld a, [wFsOFPtr] add OF_POS ld l, a ld a, [wFsOFPtr+1] adc 0 ld h, a ld a, e ld [hl+], a ld a, d ld [hl], a ; size = max(size, pos) (cached inode; flushed on close) ld a, [wFsInode] call inode_ptr ld a, l add I_SIZE ld l, a ld a, [hl+] ld c, a ld a, [hl] ld b, a ; BC = size, HL at size+1 ld a, c sub e ld a, b sbc d jr nc, .done ; size >= pos dec hl ; size lo ld a, e ld [hl+], a ld a, d ld [hl], a .done xor a ret ; ============================================================================= ; sys_list(B = index, DE = namebuf) -> A = 1 (name copied) / 0 (no more) ; index selects the index-th *used* entry in the root directory. ; ============================================================================= sys_list:: ld a, e ld [wFsOFPtr], a ld a, d ld [wFsOFPtr+1], a ld a, b ld [wFsSlot], a ; wanted index call load_rootdir ld c, 0 ; entry cursor ld b, 0 ; used counter .l call entry_ptr ; HL = &entry ld a, [hl] or a jr z, .next ld a, [wFsSlot] cp b jr z, .found inc b .next inc c ld a, c cp DIRENTS jr c, .l xor a ret .found inc hl ; &name (fs_dirbuf) ld a, [wFsOFPtr] ld e, a ld a, [wFsOFPtr+1] ld d, a ; DE = namebuf (process memory) ld b, 15 .cp ld a, [hl+] ld [de], a inc de dec b jr nz, .cp ld a, 1 ret ; ============================================================================= ; sys_remove(DE = name) -> A = 0 ok / $FF not found ; ============================================================================= sys_remove:: call copy_name call dir_remove ; A = inode or 0 or a jr z, .no ld [wFsInode], a call file_truncate ; A = inode (frees data blocks) ld a, [wFsInode] call inode_ptr xor a ld [hl], a ; I_TYPE = free ld a, [wFsInode] call write_inode ld a, [wFsInode] call free_inode xor a ret .no ld a, $FF ret