aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 12:20:57 +0200
committeruser <user@clank>2026-07-16 12:20:57 +0200
commit4f2d40ca35e0e39886dc881d06256fc25ba27a77 (patch)
tree368474d3d24a2480b45c0efd8ffe0b7ee018b879
parentfs rewrite stage 1: persistent block device (bounce buffer, cart SRAM) (diff)
downloadgbos-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.md56
-rw-r--r--c/blk.c2
-rw-r--r--c/gbos.h1
-rw-r--r--c/libc.s10
-rw-r--r--c/ls.c8
-rw-r--r--include/gbos.inc52
-rw-r--r--src/blk.asm65
-rw-r--r--src/boot.asm3
-rw-r--r--src/fs.asm804
-rw-r--r--src/kdata.asm4
10 files changed, 697 insertions, 308 deletions
diff --git a/README.md b/README.md
index 7479e52..74f3608 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/c/blk.c b/c/blk.c
index adf2a58..36839c0 100644
--- a/c/blk.c
+++ b/c/blk.c
@@ -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();
}
diff --git a/c/gbos.h b/c/gbos.h
index 03a91de..6badd16 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -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
diff --git a/c/libc.s b/c/libc.s
index 5b81dca..c245bbd 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -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
+
diff --git a/c/ls.c b/c/ls.c
index f7e5161..5d5ce43 100644
--- a/c/ls.c
+++ b/c/ls.c
@@ -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
diff --git a/src/fs.asm b/src/fs.asm
index 692fb45..7e59f3e 100644
--- a/src/fs.asm
+++ b/src/fs.asm
@@ -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