diff options
| author | user <user@clank> | 2026-07-16 11:42:38 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-16 11:42:38 +0200 |
| commit | 057e5377af2d086917b9cd0ffb336231d7d098cb (patch) | |
| tree | fd814d32d88a0eede32a69f45b729310b182e13a | |
| parent | jobs: background execution (&), non-blocking reap, spin demo (diff) | |
| download | gbos-057e5377af2d086917b9cd0ffb336231d7d098cb.tar.gz gbos-057e5377af2d086917b9cd0ffb336231d7d098cb.tar.xz gbos-057e5377af2d086917b9cd0ffb336231d7d098cb.zip | |
fs rewrite stage 1: persistent block device (bounce buffer, cart SRAM)
- blk.asm: read_block/write_block move 256-byte blocks between battery-backed
cart-RAM banks (8-11, the 'disk') and a WRAM bounce buffer. Banking is hidden
in those two routines; they inline the 'restore my bank' step so they never
touch the stack while the disk bank is mapped over the process's $A000 window
(the stack lives there too -- a call/ret would rug-pull it).
- format-on-first-boot: superblock magic in block 0; otherwise the disk persists.
- cart RAM bumped to 128 KiB (-r 4); process banks 0-7, disk banks 8-11.
- debug tool 'blk fill/peek' + syscalls to validate round-trip and persistence.
- verified: round-trip incl. cross-bank (block 100 -> bank 11); block survives a
reboot via .sav; magic intact (no reformat on 2nd boot).
- old WRAM 8-slot FS still backs the tools until stages 2-3.
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | c/blk.c | 13 | ||||
| -rw-r--r-- | c/gbos.h | 2 | ||||
| -rw-r--r-- | c/libc.s | 14 | ||||
| -rw-r--r-- | include/gbos.inc | 22 | ||||
| -rw-r--r-- | src/blk.asm | 161 | ||||
| -rw-r--r-- | src/boot.asm | 9 | ||||
| -rw-r--r-- | src/programs.asm | 7 | ||||
| -rw-r--r-- | src/syscall.asm | 2 |
10 files changed, 243 insertions, 6 deletions
@@ -8,7 +8,7 @@ OBJS := $(patsubst src/%.asm,$(BUILD)/%.o,$(SRCS)) ASFLAGS := -I include -Wall # MBC5 + RAM + battery; -r 3 = 32 KiB cart RAM (bump for more task banks) -FIXFLAGS := -v -m MBC5+RAM+BATTERY -r 3 -p 0xFF -t GBOS +FIXFLAGS := -v -m MBC5+RAM+BATTERY -r 4 -p 0xFF -t GBOS .PHONY: all clean run @@ -20,7 +20,7 @@ $(BUILD)/%.o: src/%.asm | $(BUILD) # C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \ c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin \ - c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin + c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin $(BUILD)/programs.o: $(CBLOBS) # C programs also depend on the shared libc sources @@ -294,6 +294,15 @@ $ 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 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 @@ -338,7 +347,11 @@ $ rm notes - [x] shell in C with redirection (`>`/`<`) and pipes (`|`, via a temp file) - [x] per-process stdin/stdout routing (`KGetc`/`KPutc`, `setin`/`setout`) - [ ] more tools (`rev`, `grep`, `tail`), true concurrent pipes -- [ ] battery-backed files (move the FS to cart SRAM), directories +- [~] 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` - [ ] 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) @@ -0,0 +1,13 @@ +#include "gbos.h" +/* blk: exercise the block device. blk fill <n> <v> | blk peek <n> */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + 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(); + } else { + puts("usage: blk fill <n> <v> | blk peek <n>"); nl(); + } +} @@ -43,4 +43,6 @@ unsigned char psget(unsigned char slot, unsigned char *buf); /* {pid,state,prog} void progname(unsigned char id, char *buf); /* id -> name */ 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 */ #endif @@ -199,3 +199,17 @@ _reap: ld c, #22 ; SYS_REAP rst #0x30 ret + + .globl _bfill, _bpeek + ;; void bfill(unsigned char block /*A*/, unsigned char byte /*E*/) +_bfill: + ld b, a + ld c, #23 ; SYS_BFILL + rst #0x30 + ret + ;; unsigned char bpeek(unsigned char block /*A*/) -> A +_bpeek: + ld b, a + ld c, #24 ; SYS_BPEEK + rst #0x30 + ret diff --git a/include/gbos.inc b/include/gbos.inc index 329430c..3b96f56 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -46,7 +46,22 @@ DEF KSTACK_TOP EQU $D000 ; kernel stack top (grows down into WRAM0) ; Process table ; ----------------------------------------------------------------------------- DEF MAX_PROCS EQU 8 -DEF NUM_RAM_BANKS EQU 4 ; MBC5 cart RAM banks available (-r 3 = 32 KiB) +DEF NUM_RAM_BANKS EQU 8 ; process data/stack banks 0..7 (of 16; -r 4 = 128 KiB) + +; ----------------------------------------------------------------------------- +; Block device (persistent, battery-backed cart RAM). The "disk" is cart-RAM +; banks FS_BANK0.. accessed 256 bytes at a time through a WRAM bounce buffer +; (read_block / write_block), so only those two routines ever touch banking. +; ----------------------------------------------------------------------------- +DEF FS_BANK0 EQU 8 ; disk starts at cart-RAM bank 8 +DEF FS_NBLOCKS EQU 128 ; 128 blocks x 256 B = 32 KiB (banks 8..11) +DEF BLK_SIZE EQU 256 +DEF BLK_PERBANK EQU 32 ; 8 KiB / 256 +DEF SUPER_BLOCK EQU 0 ; block 0 = superblock +DEF SUPER_MAG0 EQU $47 ; 'G' +DEF SUPER_MAG1 EQU $42 ; 'B' +DEF SUPER_MAG2 EQU $46 ; 'F' +DEF SUPER_MAG3 EQU $53 ; 'S' DEF KSTACK_TOP2 EQU $D000 ; kernel stack top reused for syscalls (fork) DEF INIT_PID EQU 1 ; pid of the init task (orphan reaper) @@ -102,7 +117,9 @@ DEF SYS_LOOKUP EQU 19 ; program id for a command name (DE=name -> A=id/$FF) DEF SYS_PS EQU 20 ; process table entry (B=slot, DE=buf3 -> A=1/0) DEF SYS_PROGNAME EQU 21 ; program id -> name (B=id, DE=namebuf) DEF SYS_REAP EQU 22 ; reap a zombie child, nohang (-> A=pid or 0) -DEF SYS_MAX EQU 23 +DEF SYS_BFILL EQU 23 ; [debug] fill a disk block (B=block, E=byte) +DEF SYS_BPEEK EQU 24 ; [debug] read a disk block[0] (B=block -> A) +DEF SYS_MAX EQU 25 ; SYS_KILL (9) is now implemented (B=pid -> A=0/$FF; pid 1 is immortal) ; (SYS_OPEN=4 / SYS_CLOSE=5 are now implemented by the filesystem) @@ -148,5 +165,6 @@ DEF PROG_RM EQU 15 DEF PROG_PS EQU 16 DEF PROG_KILL EQU 17 DEF PROG_SPIN EQU 18 +DEF PROG_BLK EQU 19 ENDC diff --git a/src/blk.asm b/src/blk.asm new file mode 100644 index 0000000..ea04e95 --- /dev/null +++ b/src/blk.asm @@ -0,0 +1,161 @@ +; ============================================================================= +; blk.asm - persistent block device on battery-backed cart RAM. +; +; The "disk" is cart-RAM banks FS_BANK0.. (256-byte blocks). Blocks are read +; and written through a WRAM bounce buffer, so only read_block/write_block ever +; touch cart-RAM banking; the rest of the filesystem works on WRAM buffers. +; +; block B lives in cart-RAM bank FS_BANK0 + B/32, at $A000 + (B & 31)*256. +; +; After each transfer we remap the current process's own RAM bank, so a syscall +; that does disk I/O leaves the caller's $A000 window exactly as it found it. +; ============================================================================= +INCLUDE "include/gbos.inc" + +SECTION "blk", ROM0 + +; A process's stack lives in the $A000 window too, so while the disk bank is +; mapped there NOTHING may touch the stack (no call/ret/push/pop). The block +; routines therefore inline the "restore my bank" step instead of calling it. +; (MACRO because it must not be a `call`.) +MACRO restore_bank + ld a, [wCurProc] + add PROC_RAMB + ld l, a + ld a, [wCurProc+1] + adc 0 + ld h, a + ld a, [hl] + ld [MBC5_RAMB], a +ENDM + +; ----------------------------------------------------------------------------- +; read_block - in: A = block, HL = dest (WRAM buffer, 256 bytes) +; ----------------------------------------------------------------------------- +read_block:: + ld c, a + srl a + srl a + srl a + srl a + srl a ; A = block / 32 + add FS_BANK0 + ld [MBC5_RAMB], a ; map the disk bank (stack now hidden!) + ld a, c + and 31 + add $A0 + ld d, a + ld e, 0 ; DE = $A000 + (block&31)*256 + ld b, 0 ; 256 bytes +.cp + ld a, [de] + ld [hl+], a + inc de + dec b + jr nz, .cp + restore_bank ; inline - no stack while the disk bank is mapped + ret + +; ----------------------------------------------------------------------------- +; write_block - in: A = block, HL = src (WRAM buffer, 256 bytes) +; ----------------------------------------------------------------------------- +write_block:: + ld c, a + srl a + srl a + srl a + srl a + srl a + add FS_BANK0 + ld [MBC5_RAMB], a + ld a, c + and 31 + add $A0 + ld d, a + ld e, 0 ; DE = dest in the disk bank + ld b, 0 +.cp + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .cp + 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. +; ============================================================================= +; sys_bfill(B = block, E = byte) - fill the whole block with a byte +sys_bfill:: + ld a, e + ld hl, wCopyBuf + ld c, 0 +.f + ld [hl+], a + dec c + jr nz, .f + ld a, b + ld hl, wCopyBuf + call write_block + xor a + ret + +; sys_bpeek(B = block) -> A = block's first byte +sys_bpeek:: + ld a, b + ld hl, wCopyBuf + call read_block + ld a, [wCopyBuf] + ret diff --git a/src/boot.asm b/src/boot.asm index 7c175ce..1f4a048 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -49,7 +49,14 @@ KernelInit: call ClearKernelRAM call CopyHramCode ; move context-switch trampoline into HRAM call ProcInit ; wipe process table - call fs_init ; set up the RAM filesystem + ; point wCurProc at the (zeroed) kernel PCB so disk I/O during boot restores + ; RAM bank 0 after each block transfer (wKernelPCB.PROC_RAMB = 0). + ld a, LOW(wKernelPCB) + 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) ; --- spawn the init task (pid 1); it fork()s the rest --- ld hl, TaskInit diff --git a/src/programs.asm b/src/programs.asm index e83d29e..53142ec 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -97,6 +97,9 @@ ProgKill: SECTION "prog_spin", ROMX[$4000], BANK[20] ProgSpin: INCBIN "c/spin.bin" +SECTION "prog_blk", ROMX[$4000], BANK[21] +ProgBlk: + INCBIN "c/blk.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and @@ -177,6 +180,8 @@ ProgramTable:: dw ProgKill db LOW(BANK(ProgSpin)), HIGH(BANK(ProgSpin)) dw ProgSpin + db LOW(BANK(ProgBlk)), HIGH(BANK(ProgBlk)) + dw ProgBlk ; ----------------------------------------------------------------------------- ; NameTable (ROM0) - command name -> program id, searched by sys_lookup. @@ -221,4 +226,6 @@ NameTable:: db PROG_KILL db "spin", 0 db PROG_SPIN + db "blk", 0 + db PROG_BLK db 0 diff --git a/src/syscall.asm b/src/syscall.asm index 2fc66b6..29dc2b0 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -57,6 +57,8 @@ SyscallTable: dw sys_ps ; 20 PS dw sys_progname ; 21 PROGNAME dw sys_reap ; 22 REAP + dw sys_bfill ; 23 BFILL (debug) + dw sys_bpeek ; 24 BPEEK (debug) ; ----------------------------------------------------------------------------- sys_nosys: |
