diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 7 | ||||
| -rw-r--r-- | README.md | 41 | ||||
| -rwxr-xr-x | c/build.sh | 20 | ||||
| -rw-r--r-- | c/chello.c | 9 | ||||
| -rw-r--r-- | c/crt0.s | 11 | ||||
| -rw-r--r-- | c/gbos.h | 9 | ||||
| -rw-r--r-- | c/libc.s | 54 | ||||
| -rw-r--r-- | include/gbos.inc | 1 | ||||
| -rw-r--r-- | src/programs.asm | 13 |
10 files changed, 165 insertions, 1 deletions
@@ -1,3 +1,4 @@ build/ *.gb *.sav +c/*.bin @@ -17,6 +17,13 @@ all: $(ROM) $(BUILD)/%.o: src/%.asm | $(BUILD) rgbasm $(ASFLAGS) -o $@ $< +# C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm +CBLOBS := c/chello.bin +$(BUILD)/programs.o: $(CBLOBS) + +c/%.bin: c/%.c c/crt0.s c/libc.s c/build.sh + sh c/build.sh $< $@ + $(ROM): $(OBJS) rgblink -m $(BUILD)/gbos.map -n $(BUILD)/gbos.sym -o $@ $(OBJS) rgbfix $(FIXFLAGS) $@ @@ -169,13 +169,52 @@ A real gbos would render to VRAM; serial is the zero-VRAM debug channel. jump table, clobbering the `write()` buffer pointer passed in `DE`. Dispatch now indexes via `A`/`HL` only, preserving `DE`/`B` for the handler. +## Writing programs in C + +gbos programs can be written in **C**, compiled with **SDCC** (the `sm83` port). +This makes porting tiny Unix CLI tools realistic. The toolchain (`c/`): + +- `c/gbos.h` — the userland API (`writes`, `readc`, `nl`, `getpid`, `sexit`). +- `c/libc.s` — syscall wrappers (asxxxx asm). Each saves + `BC/DE/HL` around `rst $30`, since the trap clobbers them but SDCC expects + them preserved. +- `c/crt0.s` — entry: `call main`; then `exit(0)`. Linked first so `_start` is + the `$4000` entry. +- `c/build.sh` — compiles a `.c` with SDCC's **native** toolchain + (`sdasgb` + `sdldgb`), links code at `$4000`, and extracts the ROM-bank blob. + The Makefile INCBINs it and registers it as a program. + +```c +#include "gbos.h" +void main(void) { + writes("hello from C on gbos!", 21); nl(); + writes("my pid is ", 10); + { char d = getpid() + '0'; writes(&d, 1); } nl(); +} +``` + +``` +$ chello +hello from C on gbos! +my pid is 2 +``` + +**Toolchain gotchas found the hard way:** SDCC's `--asm=rgbds` mode mis-orders +instructions (emits `ld [hl],a` before the `ld hl,sp+0` that sets the pointer) — +so we use SDCC's native asxxxx path and INCBIN the blob instead. String literals +with embedded control chars are also mangled in rgbds mode; C programs pass +explicit lengths and emit newlines via `nl()`. + ## Roadmap - [x] `fork`: copy parent's 8 KiB RAM bank → free bank, child returns 0 - [x] `exec`: point `PROC_ROMB` at a program in a ROM bank, reset stack, enter - [x] `exit` / `wait` / zombie reaping + cart-RAM bank recycling (free list) - [ ] `exec` refinement: copy `.data` from ROM + zero `.bss` for RW globals -- [ ] a real shell program: `fork`+`exec`+`wait` driven from the serial console +- [x] a real shell program: `fork`+`exec`+`wait` driven from the serial console +- [x] C toolchain: SDCC (sm83) + libc shim, C programs run as gbos processes +- [ ] grow libc (strings, a `main(argc,argv)`, more syscalls) + port real tools +- [ ] Makefile-drive multiple C programs (a `CPROGS` list) - [ ] 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/build.sh b/c/build.sh new file mode 100755 index 0000000..6a2deab --- /dev/null +++ b/c/build.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Build a gbos C program into a self-contained 16 KiB ROM-bank image. +# SDCC native toolchain; crt0 linked FIRST so _start is the $4000 entry. +# usage: c/build.sh <prog.c> <out.bin> +set -e +SRC="$1"; OUT="$2"; B=build/c +LIBDIR=/usr/share/sdcc/lib/sm83 +mkdir -p "$B" +sdasgb -o "$B/crt0.rel" c/crt0.s +sdasgb -o "$B/libc.rel" c/libc.s +sdcc -msm83 -c -Ic "$SRC" -o "$B/prog.rel" +# link: crt0 first (=> _start at 0x4000), then libc, program, and sm83 lib +sdldgb -n -m -w -j -i "$B/prog.ihx" \ + -b _CODE=0x4000 -b _DATA=0xa000 \ + -k "$LIBDIR" -l sm83 \ + "$B/crt0.rel" "$B/libc.rel" "$B/prog.rel" +makebin -Z -p "$B/prog.ihx" "$B/prog.bin" +dd if="$B/prog.bin" of="$OUT" bs=1024 skip=16 count=16 2>/dev/null +echo "wrote $OUT ($(wc -c < "$OUT") bytes); entry:" +grep -iE ' _start| _main' "$B/prog.map" | head -2 diff --git a/c/chello.c b/c/chello.c new file mode 100644 index 0000000..f570b61 --- /dev/null +++ b/c/chello.c @@ -0,0 +1,9 @@ +#include "gbos.h" +void main(void) { + writes("hello from C on gbos!", 21); + nl(); + writes("my pid is ", 10); + /* print pid as a digit */ + { unsigned char c = getpid() + '0'; char d = c; writes(&d, 1); } + nl(); +} diff --git a/c/crt0.s b/c/crt0.s new file mode 100644 index 0000000..cde4b67 --- /dev/null +++ b/c/crt0.s @@ -0,0 +1,11 @@ + .module crt0 + .globl _main + ;; gbos C entry. exec() maps our ROM bank and jumps to the start of _CODE + ;; (linked at 0x4000). Call main(), then exit(0). exec() already set SP. + .area _CODE +_start:: + call _main + ld b, #0 + ld c, #0 ; SYS_EXIT + rst #0x30 +1$: jr 1$ diff --git a/c/gbos.h b/c/gbos.h new file mode 100644 index 0000000..9a09a5d --- /dev/null +++ b/c/gbos.h @@ -0,0 +1,9 @@ +/* gbos userland C API (thin wrappers over rst $30 syscalls; see c/libc.asm) */ +#ifndef GBOS_H +#define GBOS_H +void writes(const char *buf, unsigned char len); /* write to console */ +void nl(void); /* newline (CRLF) */ +unsigned char readc(void); /* read one byte */ +void sexit(unsigned char code); /* exit(code) */ +unsigned char getpid(void); +#endif diff --git a/c/libc.s b/c/libc.s new file mode 100644 index 0000000..4fca5e3 --- /dev/null +++ b/c/libc.s @@ -0,0 +1,54 @@ + .module libc + .globl _writes, _nl, _readc, _sexit, _getpid + .area _CODE + ;; void writes(const char *buf /*DE*/, unsigned char len /*A*/) +_writes: + push hl + push de + push bc + ld b, a + ld c, #3 ; SYS_WRITE + rst #0x30 + pop bc + pop de + pop hl + ret + ;; void nl(void) - CRLF straight to serial +_nl: + ld a, #0x0d + ld (0xff01), a + ld a, #0x81 + ld (0xff02), a + ld a, #0x0a + ld (0xff01), a + ld a, #0x81 + ld (0xff02), a + ret + ;; unsigned char readc(void) -> A +_readc: + push hl + push de + push bc + ld c, #2 ; SYS_READ + rst #0x30 + pop bc + pop de + pop hl + ret + ;; void sexit(unsigned char code /*A*/) +_sexit: + ld b, a + ld c, #0 ; SYS_EXIT + rst #0x30 + ret + ;; unsigned char getpid(void) -> A +_getpid: + push hl + push de + push bc + ld c, #8 ; SYS_GETPID + rst #0x30 + pop bc + pop de + pop hl + ret diff --git a/include/gbos.inc b/include/gbos.inc index f398c12..9f6a98a 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -92,5 +92,6 @@ DEF SYS_MAX EQU 12 DEF PROG_WORKER EQU 0 DEF PROG_SH EQU 1 DEF PROG_HELLO EQU 2 +DEF PROG_CHELLO EQU 3 ; a C program (compiled with SDCC), see c/ ENDC diff --git a/src/programs.asm b/src/programs.asm index d4ae89f..eabe56f 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -45,6 +45,15 @@ ProgHello: .msgend ; ----------------------------------------------------------------------------- +; PROG_CHELLO (bank 5) - a program written in C, compiled with SDCC to a +; self-contained ROM-bank image (crt0 + libc + code) and included verbatim. +; Rebuild with: sh c/build.sh c/chello.c c/chello.bin +; ----------------------------------------------------------------------------- +SECTION "prog_chello", ROMX[$4000], BANK[5] +ProgChello: + INCBIN "c/chello.bin" + +; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - a tiny shell. ; prompt -> read a line (with echo) -> match a command -> fork+exec+wait. ; ----------------------------------------------------------------------------- @@ -138,6 +147,8 @@ ProgSh: db PROG_WORKER db "hello", 0 db PROG_HELLO + db "chello", 0 + db PROG_CHELLO db 0 ; terminator ; ----------------------------------------------------------------------------- @@ -178,3 +189,5 @@ ProgramTable:: dw ProgSh db LOW(BANK(ProgHello)), HIGH(BANK(ProgHello)) dw ProgHello + db LOW(BANK(ProgChello)), HIGH(BANK(ProgChello)) + dw ProgChello |
