diff options
| author | user <user@clank> | 2026-07-16 12:20:57 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-16 12:20:57 +0200 |
| commit | 4f2d40ca35e0e39886dc881d06256fc25ba27a77 (patch) | |
| tree | 368474d3d24a2480b45c0efd8ffe0b7ee018b879 | |
| parent | fs rewrite stage 1: persistent block device (bounce buffer, cart SRAM) (diff) | |
| download | gbos-4f2d40ca35e0e39886dc881d06256fc25ba27a77.tar.gz gbos-4f2d40ca35e0e39886dc881d06256fc25ba27a77.tar.xz gbos-4f2d40ca35e0e39886dc881d06256fc25ba27a77.zip | |
fs rewrite stage 2: inodes + root directory (block-based, persistent)
- fs.asm: a real block filesystem on the persistent block device - superblock,
block/inode bitmaps, a 32-entry inode table (type, size, 8 direct block
pointers -> files up to 2 KiB), and a root directory of 16-byte entries.
Metadata cached in WRAMX; one-block data cache streams file/dir blocks.
- same syscall interface (open/getb/putb/list/remove) -> tools unchanged;
ls now enumerates until flist() runs out (16 files, was hardcoded 8).
- old 8-slot WRAM FS removed; cart RAM partitioned: process banks 0-7, disk 8-11.
- two register-clobber bugs fixed: alloc_block/alloc_inode returned the
bitmap-block number (write_bitmap clobbers C); db_use loaded the wrong block
on a transition (db_flush->write_block clobbers C) -> multi-block files broke.
- verified: 10+ files, multi-block (300-byte) files, delete, and persistence
across reboots. Debug 'blk' disk tool retained (fill/peek).
- README: document the block filesystem.
| -rw-r--r-- | README.md | 56 | ||||
| -rw-r--r-- | c/blk.c | 2 | ||||
| -rw-r--r-- | c/gbos.h | 1 | ||||
| -rw-r--r-- | c/libc.s | 10 | ||||
| -rw-r--r-- | c/ls.c | 8 | ||||
| -rw-r--r-- | include/gbos.inc | 52 | ||||
| -rw-r--r-- | src/blk.asm | 65 | ||||
| -rw-r--r-- | src/boot.asm | 3 | ||||
| -rw-r--r-- | src/fs.asm | 804 | ||||
| -rw-r--r-- | src/kdata.asm | 4 |
10 files changed, 697 insertions, 308 deletions
@@ -294,42 +294,38 @@ $ ps ## Filesystem -> **Being rewritten** into a persistent block-based FS. Stage 1 landed: a block -> device (`src/blk.asm`) over battery-backed cart RAM (banks 8–11, 256-byte -> blocks) read/written through a WRAM bounce buffer — so only two routines touch -> cart-RAM banking, and (crucially) they never touch the stack while the disk -> bank is mapped over the process's `$A000` window. Data survives power-off via -> the emulator's `.sav`. The 8-slot WRAM filesystem below still backs the tools -> until stages 2–3 (inodes + directories) replace it. +A small **block-based, persistent filesystem** on battery-backed cart RAM +(`src/blk.asm` + `src/fs.asm`). Layout on the 32 KiB "disk" (256-byte blocks): +``` +block 0 superblock (magic + version) +block 1 bitmaps (free blocks + free inodes) +blocks 2-3 inode table (32 inodes x 16 B: type, size, 8 direct block ptrs) +blocks 4.. data blocks +``` -A tiny **RAM filesystem** lives in **WRAMX (`$D000-$DFFF`, 4 KiB)** — always -mapped in DMG mode and simultaneously accessible with a process's `$A000` data -bank, so the kernel copies between file and process memory with no bounce -buffer. 8 fixed slots of 512 bytes: `name[8]` + `len[2]` + `data[502]`. Seeded -with a `readme` at boot. - -Syscalls: `open` (4), `close` (5), `getb` (12), `putb` (13, byte in `E`), -`list` (14), `remove` (15). Libc: `open`/`close`/`fgetc`/`fputc`/`flist`/ -`fremove` (`c/libc.s`). +- **Inodes + a root directory.** A directory is a file of 16-byte entries + (inode# + name). Files are up to **2 KiB** (8 direct blocks); up to **16** + entries in the root, **32** inodes. +- **Bounce buffer.** Only `read_block`/`write_block` touch cart-RAM banking; + everything else works on WRAM buffers. The bitmap + inode table are cached in + WRAMX; data/dir blocks stream through a one-block cache. +- **Persistent.** Formats on first boot (magic/version check), then survives + power-off via the emulator's `.sav`. +- Same syscalls as before (`open`/`getb`/`putb`/`list`/`remove`) so the tools + (`cat`/`ls`/`save`/`rm`/`wc`/`head`) are unchanged. ``` $ save notes -gbos has a filesystem now +gbos has a real filesystem now $ cat notes -gbos has a filesystem now -$ wc notes -1 5 26 +gbos has a real filesystem now $ ls -readme notes -$ rm notes +$ wc notes # survives a reboot +1 5 31 ``` -> **ABI note:** the syscall trap indexes its jump table with `A`, so syscall -> args go in `B`/`DE`/`E` (never `A`) and results come back in `A`. That's why -> `putb` takes its byte in `E`. - ## Roadmap - [x] `fork`: copy parent's 8 KiB RAM bank → free bank, child returns 0 @@ -348,10 +344,10 @@ $ rm notes - [x] per-process stdin/stdout routing (`KGetc`/`KPutc`, `setin`/`setout`) - [ ] more tools (`rev`, `grep`, `tail`), true concurrent pipes - [~] persistent filesystem rewrite (block-based, cart SRAM), directories: - - [x] **stage 1**: block device (`blk.asm`) — read_block/write_block via a WRAM - bounce buffer, format-on-first-boot, **persists across reboots** (`.sav`) - - [ ] stage 2: inodes + flat root dir (variable-size files); port cat/ls/save - - [ ] stage 3: directories, path resolution, per-process cwd, `cd`/`mkdir` + - [x] **stage 1**: block device (`blk.asm`) via a WRAM bounce buffer + - [x] **stage 2**: inodes + root directory; variable-size files (<=2 KiB), + bitmap allocators; 16 files / 32 inodes; persists across reboots + - [ ] stage 3: nested directories, path resolution, per-process cwd, `cd`/`mkdir` - [ ] Preemptive scheduling: real context save in `TimerISR` → `hSwitchTo` - [ ] Use the `$D000-$DFFF` u-area (SVBK) for per-process kernel state / kstack - [ ] `brk`/heap allocator inside the task bank (heap up, stack down, collision = ENOMEM) @@ -6,7 +6,7 @@ void main(void) { if (argc >= 3 && argv[0][0] == 'f') { bfill(atou(argv[1]), atou(argv[2])); } else if (argc >= 2 && argv[0][0] == 'p') { - putu(bpeek(atou(argv[1]))); nl(); + putu(bpeek2(atou(argv[1]), argc>=3?atou(argv[2]):0)); nl(); } else { puts("usage: blk fill <n> <v> | blk peek <n>"); nl(); } @@ -45,4 +45,5 @@ void yield(void); unsigned char reap(void); /* reap a finished bg job, or 0 */ void bfill(unsigned char block, unsigned char byte); /* [debug] disk */ unsigned char bpeek(unsigned char block); /* [debug] disk */ +unsigned char bpeek2(unsigned char block, unsigned char off); #endif @@ -200,7 +200,7 @@ _reap: rst #0x30 ret - .globl _bfill, _bpeek + .globl _bfill, _bpeek, _bpeek2 ;; void bfill(unsigned char block /*A*/, unsigned char byte /*E*/) _bfill: ld b, a @@ -210,6 +210,14 @@ _bfill: ;; unsigned char bpeek(unsigned char block /*A*/) -> A _bpeek: ld b, a + ld e, #0 ld c, #24 ; SYS_BPEEK rst #0x30 ret + ;; unsigned char bpeek2(unsigned char block /*A*/, unsigned char off /*E*/) -> A +_bpeek2: + ld b, a + ld c, #24 ; SYS_BPEEK (off already in E) + rst #0x30 + ret + @@ -1,9 +1,7 @@ #include "gbos.h" -/* ls: list files in the RAM filesystem. */ +/* ls: list files in the filesystem. */ void main(void) { - char name[9]; + char name[16]; unsigned char i; - for (i = 0; i < 8; i++) { - if (flist(i, name)) { name[8] = 0; puts(name); nl(); } - } + for (i = 0; flist(i, name); i++) { name[15] = 0; puts(name); nl(); } } diff --git a/include/gbos.inc b/include/gbos.inc index 3b96f56..770bd55 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -124,21 +124,51 @@ DEF SYS_MAX EQU 25 ; (SYS_OPEN=4 / SYS_CLOSE=5 are now implemented by the filesystem) ; ----------------------------------------------------------------------------- -; RAM filesystem (lives in WRAMX $D000-$DFFF, 4 KiB; fixed in DMG mode). -; 8 files x 512 bytes: name[8] (first byte 0 = free) + len[2] + data[502]. +; Block-based filesystem (on the persistent block device; see fs.asm). +; block 0 superblock (magic + version) +; block 1 bitmaps: block bitmap @0 (16 B), inode bitmap @16 (4 B) +; blocks 2-3 inode table (32 inodes x 16 B) +; blocks 4.. data blocks +; Metadata (bitmaps + inode table) is cached in WRAMX; data/dir blocks stream +; through fs_datbuf / fs_dirbuf. ; ----------------------------------------------------------------------------- -DEF FS_BASE EQU $D000 -DEF FS_MAX_FILES EQU 8 -DEF FS_FILE_SIZE EQU 512 -DEF FS_NAME EQU 0 -DEF FS_LEN EQU 8 -DEF FS_DATA EQU 10 -DEF FS_MAX_DATA EQU 502 +DEF BLK_SUPER EQU 0 +DEF BLK_BITMAP EQU 1 +DEF BLK_INODE0 EQU 2 ; inode table = blocks 2,3 +DEF BLK_DATA0 EQU 4 ; first data block +DEF FS_VERSION EQU 2 +DEF NINODES EQU 32 +DEF ROOT_INO EQU 1 +DEF NDIRECT EQU 8 ; direct block pointers per inode -> files <= 2 KiB +DEF DIRENTS EQU 16 ; directory entries per block (256/16) -; open-file table (kernel WRAM0): inuse, slot, mode, pos(2) +; inode fields (16 bytes) +DEF I_TYPE EQU 0 ; 0=free 1=file 2=dir +DEF I_NLINK EQU 1 +DEF I_SIZE EQU 2 ; 16-bit +DEF I_BLOCKS EQU 4 ; 8 direct block pointers (1 byte each; 0=none) +DEF IT_FREE EQU 0 +DEF IT_FILE EQU 1 +DEF IT_DIR EQU 2 + +; directory entry (16 bytes): inode(1, 0=free) + name(15, NUL-term) +DEF DE_INO EQU 0 +DEF DE_NAME EQU 1 + +; bitmap offsets within block 1 +DEF BM_BLOCKS EQU 0 +DEF BM_INODES EQU 16 + +; WRAMX working buffers (freed by removing the old 8-slot FS) +DEF fs_bmbuf EQU $D000 ; 256 B: bitmap block cache (block 1) +DEF fs_inobuf EQU $D100 ; 512 B: inode table cache (blocks 2-3) +DEF fs_dirbuf EQU $D300 ; 256 B: directory block scratch +DEF fs_datbuf EQU $D400 ; 256 B: data block cache + +; open-file table (kernel WRAM0): inuse, inode, mode, pos(2) DEF OF_MAX EQU 4 DEF OF_INUSE EQU 0 -DEF OF_SLOT EQU 1 +DEF OF_INODE EQU 1 DEF OF_MODE EQU 2 DEF OF_POS EQU 3 DEF OF_SIZE EQU 5 diff --git a/src/blk.asm b/src/blk.asm index ea04e95..2be961e 100644 --- a/src/blk.asm +++ b/src/blk.asm @@ -83,57 +83,6 @@ write_block:: restore_bank ret -; ----------------------------------------------------------------------------- -; blk_format - write a fresh superblock (magic) to block 0. (mkfs, stage 1) -; ----------------------------------------------------------------------------- -blk_format:: - ld hl, wCopyBuf ; zero the buffer - ld b, 0 - xor a -.z - ld [hl+], a - dec b - jr nz, .z - ld hl, wCopyBuf - 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, SUPER_BLOCK - ld hl, wCopyBuf - call write_block - ret - -; ----------------------------------------------------------------------------- -; blk_init - format the disk only if it isn't already formatted (magic check). -; Called at boot; the disk otherwise persists across power cycles (.sav). -; ----------------------------------------------------------------------------- -blk_init:: - ld a, SUPER_BLOCK - ld hl, wCopyBuf - call read_block - ld hl, wCopyBuf - 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 - ret ; already formatted -.fmt - call blk_format - ret - ; ============================================================================= ; [debug] block poke/peek syscalls, to validate the layer + persistence. ; ============================================================================= @@ -152,10 +101,20 @@ sys_bfill:: xor a ret -; sys_bpeek(B = block) -> A = block's first byte +; sys_bpeek(B = block, E = offset) -> A = block[offset] sys_bpeek:: + ld a, e + ld [wFsByte], a ; stash offset ld a, b ld hl, wCopyBuf call read_block - ld a, [wCopyBuf] + ld hl, wCopyBuf + ld a, [wFsByte] + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] ret + diff --git a/src/boot.asm b/src/boot.asm index 1f4a048..9d9571e 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -55,8 +55,7 @@ KernelInit: ld [wCurProc], a ld a, HIGH(wKernelPCB) ld [wCurProc+1], a - call blk_init ; format the disk if needed (persists otherwise) - call fs_init ; (legacy WRAMX filesystem, still used by tools) + call fs_init ; mount the disk (format on first boot; persists) ; --- spawn the init task (pid 1); it fork()s the rest --- ld hl, TaskInit @@ -1,193 +1,568 @@ ; ============================================================================= -; fs.asm - a tiny RAM filesystem in WRAMX ($D000-$DFFF, 4 KiB). +; 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. ; -; 8 fixed slots of 512 bytes: name[8] (first byte 0 = free) + len[2] + data[502]. -; Slot i base = $D000 + i*512 -> high = $D0 + i*2, low = $00. -; Because WRAMX is always mapped (DMG) and the caller's buffers live in the -; mapped $A000 window, the kernel can copy between them directly. +; 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 ; ----------------------------------------------------------------------------- -; FsSlotBase - in: A = slot (0..7) out: HL = slot base. (A clobbered) +; small helpers ; ----------------------------------------------------------------------------- -FsSlotBase:: - add a ; slot*2 - add HIGH(FS_BASE) ; + $D0 +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 - ld l, 0 + 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<<bit) +bit_addr: + ld c, a + srl a + srl a + srl a ; index/8 + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, c + and 7 + ld e, 1 +.sh + or a + jr z, .done + sla e + dec a + jr .sh +.done ret ; ----------------------------------------------------------------------------- -; FsNameEq - compare the <=8-byte NUL-terminated names at DE and HL. -; out: Z set if equal. preserves DE, HL. +; block / inode allocators (bitmaps live in fs_bmbuf, write-through to block 1) ; ----------------------------------------------------------------------------- -FsNameEq:: - push de +write_bitmap: + ld hl, fs_bmbuf + ld a, BLK_BITMAP + jp write_block + +; alloc_block -> 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 b, 8 -.cmp + ld de, wFsNameBuf + ld b, 15 +.c ld a, [de] cp [hl] jr nz, .ne - or a ; equal byte; if NUL, strings ended equal + or a jr z, .eq - inc de inc hl + inc de dec b - jr nz, .cmp + jr nz, .c .eq pop hl - pop de - xor a ; Z set + xor a ret .ne pop hl - pop de ld a, 1 - and a ; Z clear + 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 + ; ----------------------------------------------------------------------------- -; FsFind - in: DE = name out: A = slot (0..7) or $FF. +; dir_find - name in wFsNameBuf -> A = inode (0 if not found) ; ----------------------------------------------------------------------------- -FsFind:: +dir_find:: + call load_rootdir ld c, 0 .l - ld a, c - call FsSlotBase ; HL = base - ld a, [hl] ; name[0] + call entry_ptr ; HL = &entry + ld a, [hl] ; entry.inode or a - jr z, .next ; free slot - call FsNameEq ; DE vs HL + jr z, .next + inc hl ; &name + call name_eq jr z, .found .next inc c ld a, c - cp FS_MAX_FILES + cp DIRENTS jr c, .l - ld a, $FF + xor a ret .found - ld a, c + dec hl ; back to entry base + ld a, [hl] ret ; ----------------------------------------------------------------------------- -; FsAllocSlot - out: A = free slot or $FF. +; dir_add - A = inode to link (name in wFsNameBuf) -> A = 1 ok / 0 dir full ; ----------------------------------------------------------------------------- -FsAllocSlot:: +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 - call FsSlotBase + 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 FS_MAX_FILES + cp DIRENTS jr c, .l - ld a, $FF + xor a ret .found - ld a, c + 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 ; ----------------------------------------------------------------------------- -; FsSetName - copy the name at DE into slot wFsSlot (<=8 bytes, incl. NUL). +; file_truncate - A = inode ; free all data blocks, size = 0 (cached inode) ; ----------------------------------------------------------------------------- -FsSetName:: - ld a, [wFsSlot] - call FsSlotBase ; HL = base (= name) - push de - ld b, 8 -.cp - ld a, [de] - ld [hl+], a +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, .done - inc de + 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, .cp -.done - pop de + 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 - in: A = fd out: HL = &open-file entry. preserves DE. +; OFPtr - A = fd -> HL = &open-file entry. preserves DE. ; ----------------------------------------------------------------------------- OFPtr:: push de ld h, 0 ld l, a ld d, h - ld e, l ; DE = fd - add hl, hl ; 2 - add hl, hl ; 4 + 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 FsFind ; A = slot or $FF (DE = name) - ld [wFsSlot], 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 mode: must exist --- - ld a, [wFsSlot] - cp $FF + ; --- read --- + ld a, [wFsInode] + or a jr z, .fail - jr .alloc + jr .allocof .write - ; --- write mode: create or truncate --- - ld a, [wFsSlot] - cp $FF + ld a, [wFsInode] + or a jr nz, .trunc - call FsAllocSlot - cp $FF + ; --- create --- + call alloc_inode + or a jr z, .fail - ld [wFsSlot], a - call FsSetName ; DE still = name -.trunc - ld a, [wFsSlot] - call FsSlotBase - ld a, l - add FS_LEN - ld l, a ; HL = &len + ld [wFsInode], a + call inode_ptr ; HL = &inode + push hl + ld b, 16 xor a +.zi ld [hl+], a - ld [hl], a ; len = 0 -.alloc - ; --- allocate an open-file entry --- + 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 -.of +.ofl ld a, c - call OFPtr ; HL = &OF[c] + call OFPtr ld a, [hl] or a jr z, .gotof inc c ld a, c cp OF_MAX - jr c, .of + jr c, .ofl .fail ld a, $FF ret .gotof ld a, 1 ld [hl+], a ; inuse - ld a, [wFsSlot] - ld [hl+], a ; slot + ld a, [wFsInode] + ld [hl+], a ; inode ld a, [wFsMode] ld [hl+], a ; mode xor a @@ -197,12 +572,24 @@ sys_open:: ret ; ============================================================================= -; sys_close(B = fd) +; 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 @@ -217,7 +604,7 @@ sys_getb:: ld a, b cp OF_MAX jr nc, .eof - call OFPtr ; HL = OF entry + call OFPtr ld a, [hl] or a jr z, .eof @@ -225,44 +612,54 @@ sys_getb:: ld [wFsOFPtr], a ld a, h ld [wFsOFPtr+1], a - inc hl ; OF_SLOT + inc hl ld a, [hl] - ld [wFsSlot], a - inc hl ; OF_MODE + ld [wFsInode], a + inc hl inc hl ; OF_POS lo ld a, [hl+] ld e, a ld a, [hl] ld d, a ; DE = pos - ; file length -> BC - ld a, [wFsSlot] - call FsSlotBase ; HL = base + ; pos < size ? + ld a, [wFsInode] + call inode_ptr ld a, l - add FS_LEN + add I_SIZE ld l, a ld a, [hl+] ld c, a ld a, [hl] - ld b, a ; BC = len - ; pos >= len ? EOF + ld b, a ; BC = size ld a, e sub c ld a, d sbc b - jr nc, .eof - ; addr = base + FS_DATA + pos - ld a, [wFsSlot] - call FsSlotBase ; HL = base (low = 0) - ld a, FS_DATA - add e + 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 d - ld h, a ; HL = &data[pos] + adc 0 + ld h, a ld a, [hl] ld [wFsByte], a inc de ; pos++ - ; store pos back ld a, [wFsOFPtr] add OF_POS ld l, a @@ -274,15 +671,14 @@ sys_getb:: ld a, d ld [hl], a ld a, [wFsByte] - and a ; CF = 0 (success) + and a ; CF = 0 ret .eof scf ret ; ============================================================================= -; sys_putb(B = fd, E = byte) -- byte is in E, not A: the syscall dispatch -; clobbers A (it indexes the jump table with it). +; sys_putb(B = fd, E = byte) ; ============================================================================= sys_putb:: ld a, e @@ -300,42 +696,50 @@ sys_putb:: ld [wFsOFPtr+1], a inc hl ld a, [hl] - ld [wFsSlot], a + ld [wFsInode], a + inc hl inc hl - inc hl ; OF_POS lo ld a, [hl+] ld e, a ld a, [hl] ld d, a ; DE = pos - ; pos >= FS_MAX_DATA ? full - ld a, e - sub LOW(FS_MAX_DATA) + ; 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 - sbc HIGH(FS_MAX_DATA) - jr nc, .done - ; addr = base + FS_DATA + pos - ld a, [wFsSlot] - call FsSlotBase - ld a, FS_DATA - add e + 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 d + adc 0 ld h, a ld a, [wFsByte] - ld [hl], a ; write byte - inc de ; pos++ - ; len = pos (sequential append) - ld a, [wFsSlot] - call FsSlotBase - ld a, l - add FS_LEN - ld l, a - ld a, e - ld [hl+], a - ld a, d ld [hl], a - ; store pos back + ld a, 1 + ld [wFsDirty], a + inc de ; pos++ ld a, [wFsOFPtr] add OF_POS ld l, a @@ -346,22 +750,67 @@ sys_putb:: 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 = slot, DE = namebuf[8]) -> A = 1 if used (name copied), else 0 +; 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 - cp FS_MAX_FILES - jr nc, .no - call FsSlotBase ; HL = base (name) + 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, .no - ld b, 8 + 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 @@ -370,82 +819,27 @@ sys_list:: jr nz, .cp ld a, 1 ret -.no - xor a - ret ; ============================================================================= ; sys_remove(DE = name) -> A = 0 ok / $FF not found ; ============================================================================= sys_remove:: - call FsFind - cp $FF + call copy_name + call dir_remove ; A = inode or 0 + or a jr z, .no - call FsSlotBase + ld [wFsInode], a + call file_truncate ; A = inode (frees data blocks) + ld a, [wFsInode] + call inode_ptr xor a - ld [hl], a ; name[0] = 0 -> free + 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 - -; ============================================================================= -; fs_init - mark all slots free, clear the OF table, seed a "readme" file. -; ============================================================================= -fs_init:: - ld c, 0 -.clr - ld a, c - call FsSlotBase - xor a - ld [hl], a ; name[0] = 0 - inc c - ld a, c - cp FS_MAX_FILES - jr c, .clr - ; zero the open-file table - ld hl, wOFTable - ld b, OF_MAX * OF_SIZE - xor a -.zof - ld [hl+], a - dec b - jr nz, .zof - ; seed slot 0 = "readme" - xor a - ld [wFsSlot], a - ld de, .rname - call FsSetName - ; copy content -> data - xor a - call FsSlotBase ; HL = base - ld a, FS_DATA - ld l, a ; HL = &data (base low = 0) - ld de, .rtext - ld bc, .rtext_end - .rtext -.ccpy - ld a, [de] - ld [hl+], a - inc de - dec bc - ld a, b - or c - jr nz, .ccpy - ; set len - xor a - call FsSlotBase - ld a, l - add FS_LEN - ld l, a - ld a, LOW(.rtext_end - .rtext) - ld [hl+], a - ld a, HIGH(.rtext_end - .rtext) - ld [hl], a - ret -.rname - db "readme", 0 -.rtext - db "welcome to gbos!", $0D, $0A - db "files live in RAM.", $0D, $0A -.rtext_end diff --git a/src/kdata.asm b/src/kdata.asm index b299021..b239caa 100644 --- a/src/kdata.asm +++ b/src/kdata.asm @@ -45,6 +45,10 @@ wFsMode:: DS 1 wFsSlot:: DS 1 wFsByte:: DS 1 wFsOFPtr:: DS 2 +wFsCurBlk:: DS 1 ; data block currently in fs_datbuf ($FF = none) +wFsDirty:: DS 1 ; fs_datbuf needs write-back +wFsInode:: DS 1 ; working inode number +wFsNameBuf:: DS 16 ; kernel-side copy of a name argument wKChar:: DS 1 ; scratch for KPutc wKillParent::DS 1 ; scratch for sys_kill wPsBuf:: DS 2 ; scratch for sys_ps |
