aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile7
-rw-r--r--README.md41
-rwxr-xr-xc/build.sh20
-rw-r--r--c/chello.c9
-rw-r--r--c/crt0.s11
-rw-r--r--c/gbos.h9
-rw-r--r--c/libc.s54
-rw-r--r--include/gbos.inc1
-rw-r--r--src/programs.asm13
10 files changed, 165 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 7d20801..bcba5ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
build/
*.gb
*.sav
+c/*.bin
diff --git a/Makefile b/Makefile
index 23551ad..ad14afd 100644
--- a/Makefile
+++ b/Makefile
@@ -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) $@
diff --git a/README.md b/README.md
index a0b8338..49909f9 100644
--- a/README.md
+++ b/README.md
@@ -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