aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--README.md15
-rw-r--r--c/blk.c13
-rw-r--r--c/gbos.h2
-rw-r--r--c/libc.s14
-rw-r--r--include/gbos.inc22
-rw-r--r--src/blk.asm161
-rw-r--r--src/boot.asm9
-rw-r--r--src/programs.asm7
-rw-r--r--src/syscall.asm2
10 files changed, 243 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index a3a4b2b..87a7d9d 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index fe8a930..7479e52 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/c/blk.c b/c/blk.c
new file mode 100644
index 0000000..adf2a58
--- /dev/null
+++ b/c/blk.c
@@ -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();
+ }
+}
diff --git a/c/gbos.h b/c/gbos.h
index f76d1e0..03a91de 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -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
diff --git a/c/libc.s b/c/libc.s
index 2e2516b..5b81dca 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -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: