From 7b5c532625a864a3fbaf76f44df6aa99b0d6861e Mon Sep 17 00:00:00 2001 From: user Date: Thu, 16 Jul 2026 07:53:19 +0200 Subject: filesystem: a RAM FS (WRAMX) + ls/cat/save/rm; wc/head read files - fs.asm: 8-slot RAM filesystem in WRAMX ($D000-$DFFF), name[8]+len[2]+data[502]; syscalls open/close/getb/putb/list/remove, seeded with a 'readme' at boot. - libc: open/close/fgetc/fputc/flist/fremove wrappers + O_READ/O_WRITE/NOFD. - tools: ls, save (stdin->file, one line), rm; cat/wc/head now take a file arg. - fix: syscall dispatch clobbers A, so putb takes its byte in E (was reading the handler's low address byte, 0xDC); ls NUL-terminates 8-char names. - verified: save/cat/ls/rm cycle; wc readme -> '2 7 38'; head -n 1 readme. - README: document the filesystem + the A-clobber ABI note. --- src/boot.asm | 1 + src/fs.asm | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/kdata.asm | 7 + src/programs.asm | 21 +++ src/syscall.asm | 8 +- 5 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 src/fs.asm (limited to 'src') diff --git a/src/boot.asm b/src/boot.asm index 73ea860..7c175ce 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -49,6 +49,7 @@ KernelInit: call ClearKernelRAM call CopyHramCode ; move context-switch trampoline into HRAM call ProcInit ; wipe process table + call fs_init ; set up the RAM filesystem ; --- spawn the init task (pid 1); it fork()s the rest --- ld hl, TaskInit diff --git a/src/fs.asm b/src/fs.asm new file mode 100644 index 0000000..692fb45 --- /dev/null +++ b/src/fs.asm @@ -0,0 +1,451 @@ +; ============================================================================= +; fs.asm - a tiny RAM filesystem in WRAMX ($D000-$DFFF, 4 KiB). +; +; 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. +; ============================================================================= +INCLUDE "include/gbos.inc" + +SECTION "fs", ROM0 + +; ----------------------------------------------------------------------------- +; FsSlotBase - in: A = slot (0..7) out: HL = slot base. (A clobbered) +; ----------------------------------------------------------------------------- +FsSlotBase:: + add a ; slot*2 + add HIGH(FS_BASE) ; + $D0 + ld h, a + ld l, 0 + ret + +; ----------------------------------------------------------------------------- +; FsNameEq - compare the <=8-byte NUL-terminated names at DE and HL. +; out: Z set if equal. preserves DE, HL. +; ----------------------------------------------------------------------------- +FsNameEq:: + push de + push hl + ld b, 8 +.cmp + ld a, [de] + cp [hl] + jr nz, .ne + or a ; equal byte; if NUL, strings ended equal + jr z, .eq + inc de + inc hl + dec b + jr nz, .cmp +.eq + pop hl + pop de + xor a ; Z set + ret +.ne + pop hl + pop de + ld a, 1 + and a ; Z clear + ret + +; ----------------------------------------------------------------------------- +; FsFind - in: DE = name out: A = slot (0..7) or $FF. +; ----------------------------------------------------------------------------- +FsFind:: + ld c, 0 +.l + ld a, c + call FsSlotBase ; HL = base + ld a, [hl] ; name[0] + or a + jr z, .next ; free slot + call FsNameEq ; DE vs HL + jr z, .found +.next + inc c + ld a, c + cp FS_MAX_FILES + jr c, .l + ld a, $FF + ret +.found + ld a, c + ret + +; ----------------------------------------------------------------------------- +; FsAllocSlot - out: A = free slot or $FF. +; ----------------------------------------------------------------------------- +FsAllocSlot:: + ld c, 0 +.l + ld a, c + call FsSlotBase + ld a, [hl] + or a + jr z, .found + inc c + ld a, c + cp FS_MAX_FILES + jr c, .l + ld a, $FF + ret +.found + ld a, c + ret + +; ----------------------------------------------------------------------------- +; FsSetName - copy the name at DE into slot wFsSlot (<=8 bytes, incl. NUL). +; ----------------------------------------------------------------------------- +FsSetName:: + ld a, [wFsSlot] + call FsSlotBase ; HL = base (= name) + push de + ld b, 8 +.cp + ld a, [de] + ld [hl+], a + or a + jr z, .done + inc de + dec b + jr nz, .cp +.done + pop de + ret + +; ----------------------------------------------------------------------------- +; OFPtr - in: A = fd out: 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 + add hl, de ; 5*fd + ld de, wOFTable + add hl, de + pop de + 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 + ld a, [wFsMode] + or a + jr nz, .write + ; --- read mode: must exist --- + ld a, [wFsSlot] + cp $FF + jr z, .fail + jr .alloc +.write + ; --- write mode: create or truncate --- + ld a, [wFsSlot] + cp $FF + jr nz, .trunc + call FsAllocSlot + cp $FF + 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 + xor a + ld [hl+], a + ld [hl], a ; len = 0 +.alloc + ; --- allocate an open-file entry --- + ld c, 0 +.of + ld a, c + call OFPtr ; HL = &OF[c] + ld a, [hl] + or a + jr z, .gotof + inc c + ld a, c + cp OF_MAX + jr c, .of +.fail + ld a, $FF + ret +.gotof + ld a, 1 + ld [hl+], a ; inuse + ld a, [wFsSlot] + ld [hl+], a ; slot + 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) +; ============================================================================= +sys_close:: + ld a, b + cp OF_MAX + jr nc, .done + 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 ; HL = OF entry + ld a, [hl] + or a + jr z, .eof + ld a, l + ld [wFsOFPtr], a + ld a, h + ld [wFsOFPtr+1], a + inc hl ; OF_SLOT + ld a, [hl] + ld [wFsSlot], a + inc hl ; OF_MODE + 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 + ld a, l + add FS_LEN + ld l, a + ld a, [hl+] + ld c, a + ld a, [hl] + ld b, a ; BC = len + ; pos >= len ? EOF + 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 + ld l, a + ld a, h + adc d + ld h, a ; HL = &data[pos] + ld a, [hl] + ld [wFsByte], a + inc de ; pos++ + ; store pos back + 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 (success) + 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:: + 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 [wFsSlot], a + 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) + 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 + ld l, a + ld a, h + adc d + 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, [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 +.done + xor a + ret + +; ============================================================================= +; sys_list(B = slot, DE = namebuf[8]) -> A = 1 if used (name copied), else 0 +; ============================================================================= +sys_list:: + ld a, b + cp FS_MAX_FILES + jr nc, .no + call FsSlotBase ; HL = base (name) + ld a, [hl] + or a + jr z, .no + ld b, 8 +.cp + ld a, [hl+] + ld [de], a + inc de + dec b + 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 + jr z, .no + call FsSlotBase + xor a + ld [hl], a ; name[0] = 0 -> free + 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 5b91030..9aca466 100644 --- a/src/kdata.asm +++ b/src/kdata.asm @@ -38,3 +38,10 @@ wExitParentPid:: DS 1 wWaitMyPid:: DS 1 wWaitRetPid:: DS 1 wWaitRetCode:: DS 1 + +; filesystem: open-file table + scratch +wOFTable:: DS OF_MAX * OF_SIZE +wFsMode:: DS 1 +wFsSlot:: DS 1 +wFsByte:: DS 1 +wFsOFPtr:: DS 2 diff --git a/src/programs.asm b/src/programs.asm index a3a6db2..161ee2f 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -79,6 +79,15 @@ ProgHead: SECTION "prog_args", ROMX[$4000], BANK[14] ProgArgs: INCBIN "c/args.bin" +SECTION "prog_ls", ROMX[$4000], BANK[15] +ProgLs: + INCBIN "c/ls.bin" +SECTION "prog_save", ROMX[$4000], BANK[16] +ProgSave: + INCBIN "c/save.bin" +SECTION "prog_rm", ROMX[$4000], BANK[17] +ProgRm: + INCBIN "c/rm.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - a tiny shell. @@ -217,6 +226,12 @@ ProgSh: db PROG_HEAD db "args", 0 db PROG_ARGS + db "ls", 0 + db PROG_LS + db "save", 0 + db PROG_SAVE + db "rm", 0 + db PROG_RM db 0 ; terminator ; ----------------------------------------------------------------------------- @@ -277,3 +292,9 @@ ProgramTable:: dw ProgHead db LOW(BANK(ProgArgs)), HIGH(BANK(ProgArgs)) dw ProgArgs + db LOW(BANK(ProgLs)), HIGH(BANK(ProgLs)) + dw ProgLs + db LOW(BANK(ProgSave)), HIGH(BANK(ProgSave)) + dw ProgSave + db LOW(BANK(ProgRm)), HIGH(BANK(ProgRm)) + dw ProgRm diff --git a/src/syscall.asm b/src/syscall.asm index bf75865..5ed3d6f 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -38,14 +38,18 @@ SyscallTable: dw sys_fork ; 1 FORK dw sys_read ; 2 READ dw sys_write ; 3 WRITE - dw sys_nosys ; 4 OPEN (TODO) - dw sys_nosys ; 5 CLOSE (TODO) + dw sys_open ; 4 OPEN + dw sys_close ; 5 CLOSE dw sys_exec ; 6 EXEC dw sys_wait ; 7 WAIT dw sys_getpid ; 8 GETPID dw sys_nosys ; 9 KILL (TODO) dw sys_nosys ; 10 BRK (TODO) dw sys_yield ; 11 YIELD + dw sys_getb ; 12 GETB + dw sys_putb ; 13 PUTB + dw sys_list ; 14 LIST + dw sys_remove ; 15 REMOVE ; ----------------------------------------------------------------------------- sys_nosys: -- cgit v1.3.1-sl0p