; ============================================================================= ; 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) ; dir_load - read directory wFsDir's block into fs_dirbuf (single-block dirs). dir_load: ld a, [wFsDir] call inode_ptr ld a, l add I_BLOCKS ld l, a ld a, [hl] ; blocks[0] ld [wFsDirBlk], a ld hl, fs_dirbuf jp read_block dir_write: ld a, [wFsDirBlk] ld hl, fs_dirbuf jp write_block ; ----------------------------------------------------------------------------- ; dir_find - dir inode in wFsDir, name in wFsNameBuf -> A = inode (0 if none) ; ----------------------------------------------------------------------------- dir_find:: call dir_load 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 ld a, [hl] ret ; ----------------------------------------------------------------------------- ; dir_add - dir in wFsDir, child in wFsInode, name in wFsNameBuf -> A = 1/0 ; ----------------------------------------------------------------------------- dir_add:: call dir_load 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 ld b, 15 .cp ld a, [de] ld [hl+], a inc de dec b jr nz, .cp call dir_write ld a, 1 ret ; ----------------------------------------------------------------------------- ; dir_remove - dir in wFsDir, name in wFsNameBuf -> A = inode removed (0 none) ; ----------------------------------------------------------------------------- dir_remove:: call dir_load 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 ld a, [hl] ld [wFsInode], a xor a ld [hl], a call dir_write ld a, [wFsInode] ret ; ----------------------------------------------------------------------------- ; cur_cwd -> A = current process's cwd inode ; ----------------------------------------------------------------------------- cur_cwd: push hl ; callers (resolve) keep a path cursor in HL ld a, [wCurProc] add PROC_CWD ld l, a ld a, [wCurProc+1] adc 0 ld h, a ld a, [hl] pop hl ret ; ----------------------------------------------------------------------------- ; resolve - path in wFsPath -> A = inode (0 if any component missing) ; ----------------------------------------------------------------------------- resolve: ld hl, wFsPath ld a, [hl] cp $2F ; '/' jr nz, .rel inc hl ld a, ROOT_INO jr .setcur .rel call cur_cwd .setcur ld [wFsCur], a .loop ld a, [hl] ; skip '/' separators cp $2F jr nz, .comp inc hl jr .loop .comp ld a, [hl] or a jr z, .done ; end of path call extract_comp ; wFsNameBuf = component, HL past it push hl ; dir_find clobbers HL (the path cursor) ld a, [wFsCur] ld [wFsDir], a call dir_find ; A = child inode pop hl or a ret z ; missing component ld [wFsCur], a jr .loop .done ld a, [wFsCur] ret ; extract_comp - copy the path component at [HL] into wFsNameBuf, advancing HL ; to the next '/' or NUL. (<=15 chars) extract_comp: ld de, wFsNameBuf ld b, 15 .l ld a, [hl] or a jr z, .end cp $2F jr z, .end ld [de], a inc de inc hl dec b jr nz, .l .end xor a ld [de], a ret ; ----------------------------------------------------------------------------- ; resolve_parent - path in wFsPath -> A = parent dir inode (0 if a dir in the ; path is missing), wFsNameBuf = the final component. ; ----------------------------------------------------------------------------- resolve_parent: ; find the last '/' ld hl, wFsPath xor a ld [wFsSlashPtr], a ld [wFsSlashPtr+1], a .scan ld a, [hl] or a jr z, .scandone cp $2F jr nz, .noslash ld a, l ld [wFsSlashPtr], a ld a, h ld [wFsSlashPtr+1], a .noslash inc hl jr .scan .scandone ld a, [wFsSlashPtr] or a jr nz, .haveslash ld a, [wFsSlashPtr+1] or a jr nz, .haveslash ; no '/': parent = cwd, name = whole path ld hl, wFsPath call comp_to_namebuf call cur_cwd ret .haveslash ld a, [wFsSlashPtr] ld l, a ld a, [wFsSlashPtr+1] ld h, a ; HL = &lastslash ; if the slash is the very first char, parent = root ld a, l cp LOW(wFsPath) jr nz, .split ld a, h cp HIGH(wFsPath) jr nz, .split inc hl ; name = after the '/' call comp_to_namebuf ld a, ROOT_INO ret .split push hl ; &lastslash xor a ld [hl], a ; terminate the dir part at the slash call resolve ; resolve the dir part -> A = parent inode pop hl ; &lastslash or a ret z ; a directory in the path is missing ld [wFsCur], a inc hl ; name = after the '/' call comp_to_namebuf ; set the final name AFTER resolve (it clobbers it) ld a, [wFsCur] ret ; comp_to_namebuf - copy the NUL-terminated string at HL into wFsNameBuf (<=15) comp_to_namebuf: ld de, wFsNameBuf ld b, 15 .l ld a, [hl] ld [de], a or a ret z inc hl inc de dec b jr nz, .l xor a ld [de], a 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): "." and ".." both -> root (inode 1) ld hl, fs_dirbuf call zero256 ld a, ROOT_INO ld [fs_dirbuf + 0], a ; entry 0 inode ld a, $2E ; '.' ld [fs_dirbuf + 1], a ld a, ROOT_INO ld [fs_dirbuf + 16], a ; entry 1 inode ld a, $2E ld [fs_dirbuf + 17], a ; '.' ld [fs_dirbuf + 18], a ; '.' 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 ld a, ROOT_INO ld [wListDir], 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 path at DE (process memory) into wFsPath (<=39). copy_path: ld hl, wFsPath ld b, 39 .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 = path, B = mode 0=read/1=write) -> A = fd or $FF ; ============================================================================= sys_open:: ld a, b ld [wFsMode], a call copy_path ; path -> wFsPath call resolve_parent ; A = parent dir inode, wFsNameBuf = final name or a jr z, .fail ld [wFsDir], a call dir_find ; A = target inode or 0 ld [wFsInode], a or a jr z, .notdir ; doesn't exist yet: no type to check call inode_ptr ld a, [hl] ; I_TYPE cp IT_DIR jr z, .isdir ; can't open a directory as a file .notdir 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 call dir_add ; wFsDir=parent, wFsInode=child, wFsNameBuf=name 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 .isdir ld a, $FE ; EISDIR: is a directory ret .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 non-hidden entry in the *current* directory. ; ============================================================================= sys_list:: ld a, e ld [wFsOFPtr], a ld a, d ld [wFsOFPtr+1], a ld a, b ld [wFsSlot], a ; wanted index ld a, [wListDir] ld [wFsDir], a call dir_load ld c, 0 ; entry cursor ld b, 0 ; used counter .l call entry_ptr ; HL = &entry ld a, [hl] or a jr z, .next inc hl ld a, [hl] ; name[0] dec hl cp $2E ; '.' -> hidden (skip ".", "..") 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 = path) -> A = 0 ok / $FF not found ; ============================================================================= sys_remove:: call copy_path call resolve_parent ; A = parent inode, wFsNameBuf = name or a jr z, .no ld [wFsDir], a 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 ; ============================================================================= ; sys_mkdir(DE = path) -> A = 0 ok / $FF ; ============================================================================= sys_mkdir:: call copy_path call resolve_parent ; A = parent inode, wFsNameBuf = name or a jr z, .fail ld [wFsDir], a ; parent dir call dir_find ; already exists? or a jr nz, .fail call alloc_inode or a jr z, .fail ld [wFsInode], a ; new directory inode call alloc_block or a jr z, .fail ld [wFsSlot], a ; new directory's data block ; init the inode (type=dir, nlink=1, size=32, blocks[0]=data block) ld a, [wFsInode] call inode_ptr push hl ld b, 16 xor a .zi ld [hl+], a dec b jr nz, .zi pop hl ld a, IT_DIR ld [hl+], a ld a, 1 ld [hl+], a ld a, 32 ld [hl+], a xor a ld [hl+], a ld a, [wFsSlot] ld [hl], a ld a, [wFsInode] call write_inode ; the new dir's data block: "." -> self, ".." -> parent ld hl, fs_dirbuf call zero256 ld a, [wFsInode] ld [fs_dirbuf + 0], a ld a, $2E ld [fs_dirbuf + 1], a ld a, [wFsDir] ld [fs_dirbuf + 16], a ld a, $2E ld [fs_dirbuf + 17], a ld [fs_dirbuf + 18], a ld a, [wFsSlot] ld hl, fs_dirbuf call write_block ; link the new dir into the parent (wFsDir/wFsInode/wFsNameBuf all set) call dir_add or a jr z, .fail xor a ret .fail ld a, $FF ret ; ============================================================================= ; sys_chdir(DE = path) -> A = 0 ok / $FF (not found or not a directory) ; ============================================================================= sys_chdir:: call copy_path call resolve ; A = inode or 0 or a jr z, .fail ld [wFsInode], a call inode_ptr ld a, [hl] ; I_TYPE cp IT_DIR jr nz, .fail ld a, [wCurProc] add PROC_CWD ld l, a ld a, [wCurProc+1] adc 0 ld h, a ld a, [wFsInode] ld [hl], a ; PROC_CWD = inode xor a ret .fail ld a, $FF ret ; ============================================================================= ; sys_opendir(DE = path) -> A = 0 ok / $FF ; sets the directory ls enumerates. ; Empty path or "." means the current directory. ; ============================================================================= sys_opendir:: call copy_path call resolve or a jr z, .fail ld [wFsInode], a call inode_ptr ld a, [hl] cp IT_DIR jr nz, .fail ld a, [wFsInode] ld [wListDir], a xor a ret .fail ld a, $FF ret