diff options
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | README.md | 40 | ||||
| -rw-r--r-- | c/cat.c | 10 | ||||
| -rw-r--r-- | c/crt0.s | 6 | ||||
| -rw-r--r-- | c/echo.c | 5 | ||||
| -rw-r--r-- | c/false.c | 2 | ||||
| -rw-r--r-- | c/gbos.h | 16 | ||||
| -rw-r--r-- | c/libc.s | 84 | ||||
| -rw-r--r-- | c/pid.c | 9 | ||||
| -rw-r--r-- | c/true.c | 2 | ||||
| -rw-r--r-- | c/uname.c | 2 | ||||
| -rw-r--r-- | include/gbos.inc | 8 | ||||
| -rw-r--r-- | src/programs.asm | 71 |
13 files changed, 214 insertions, 44 deletions
@@ -18,7 +18,8 @@ $(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 +CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \ + c/pid.bin c/cat.bin $(BUILD)/programs.o: $(CBLOBS) c/%.bin: c/%.c c/crt0.s c/libc.s c/build.sh @@ -193,12 +193,40 @@ void main(void) { } ``` +### libc + +`writes`, `putc`, `puts`, `nl`, `strlen`, `readc` (returns `EOF`=4 at end of +input), `getargs` (this program's argument string), `getpid`, `sexit`. +Arguments: the shell splits the command line at the first space and leaves +`"cmd\0args\0"` at `$A000`; the child inherits it through `fork`, and +`getargs()` returns the args. + +### CLI tools (in `c/`) + +| tool | what it does | +|------|--------------| +| `echo` | print its arguments (`echo hello world`) | +| `cat` | copy stdin to stdout until EOF (Ctrl-D / pipe end) | +| `uname`| print the system name | +| `pid` | print the process's pid (decimal) | +| `true` / `false` | exit 0 / 1 | +| `chello` | the C "hello" demo | + ``` -$ chello -hello from C on gbos! -my pid is 2 +$ uname +gbos sm83 (Game Boy Color) +$ echo C tools on a Game Boy +C tools on a Game Boy +$ pid +5 +$ cat +hi from cat +hi from cat ``` +Each tool is a separate `c/<name>.c`, built to a ROM-bank blob, INCBIN'd, and +registered in the program table + shell command table (`src/programs.asm`). + **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 @@ -213,8 +241,10 @@ explicit lengths and emit newlines via `nl()`. - [ ] `exec` refinement: copy `.data` from ROM + zero `.bss` for RW globals - [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) +- [x] grow libc (`puts`/`putc`/`strlen`/`getargs`/EOF) + args via the shell +- [x] CLI tools in C: `echo`, `cat`, `uname`, `pid`, `true`, `false` +- [x] Makefile builds all C programs (a `CBLOBS` list) +- [ ] more tools (`wc`, `head`, `rev`), a real `argc/argv`, a RAM filesystem - [ ] 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,10 @@ +#include "gbos.h" +/* cat: copy stdin to stdout until EOF (Ctrl-D / pipe end). */ +void main(void) { + char c; + for (;;) { + c = readc(); + if (c == EOF) break; + putc(c); + } +} @@ -1,11 +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. + ;; gbos C entry. exec() maps our ROM bank and jumps here ($4000). + ;; Call main(), then exit(0). (Args are fetched on demand via getargs().) .area _CODE _start:: call _main ld b, #0 - ld c, #0 ; SYS_EXIT + ld c, #0 ; SYS_EXIT rst #0x30 1$: jr 1$ diff --git a/c/echo.c b/c/echo.c new file mode 100644 index 0000000..f8d5125 --- /dev/null +++ b/c/echo.c @@ -0,0 +1,5 @@ +#include "gbos.h" +void main(void) { + puts(getargs()); + nl(); +} diff --git a/c/false.c b/c/false.c new file mode 100644 index 0000000..a124951 --- /dev/null +++ b/c/false.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { sexit(1); } @@ -1,9 +1,15 @@ -/* gbos userland C API (thin wrappers over rst $30 syscalls; see c/libc.asm) */ +/* gbos userland C API (thin wrappers over rst $30 syscalls; see c/libc.s) */ #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) */ +#define EOF 4 /* readc() returns this at end of input */ + +void writes(const char *buf, unsigned char len); +void putc(char c); +void puts(const char *s); /* write a NUL-terminated string */ +void nl(void); /* newline (CRLF) */ +unsigned char strlen(const char *s); +char *getargs(void); /* this program's argument string */ +char readc(void); /* one input byte, EOF at end */ +void sexit(unsigned char code); unsigned char getpid(void); #endif @@ -1,19 +1,48 @@ .module libc - .globl _writes, _nl, _readc, _sexit, _getpid + .globl _writes, _putc, _puts, _nl, _strlen, _readc, _sexit, _getpid, _getargs + ;; SDCC sm83 sdcccall(1): 1st arg -> A (byte) or DE (pointer); ret A / BC. + ;; rst $30 (the trap) clobbers BC/DE/HL; SDCC treats them caller-saved, so + ;; wrappers need not preserve them - but we compute cleanly regardless. .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 + ld c, #3 ; SYS_WRITE rst #0x30 - pop bc - pop de - pop hl ret - ;; void nl(void) - CRLF straight to serial + + ;; char *getargs(void) -> BC : the arg string (command line past word 1). + ;; The shell leaves "cmd\0args\0" at 0xA000, inherited by the child via fork. +_getargs: + ld hl, #0xa000 +1$: ld a, (hl) + inc hl + or a + jr nz, 1$ + ld b, h + ld c, l + ret + + ;; void putc(char c /*A*/) +_putc: + ld (0xff01), a + ld a, #0x81 + ld (0xff02), a + ret + + ;; void puts(const char *s /*DE*/) - write until NUL (no newline) +_puts: +1$: ld a, (de) + or a + ret z + ld (0xff01), a + ld a, #0x81 + ld (0xff02), a + inc de + jr 1$ + + ;; void nl(void) - CRLF _nl: ld a, #0x0d ld (0xff01), a @@ -24,31 +53,34 @@ _nl: ld a, #0x81 ld (0xff02), a ret - ;; unsigned char readc(void) -> A + + ;; unsigned char strlen(const char *s /*DE*/) -> A +_strlen: + ld c, #0 +1$: ld a, (de) + or a + jr z, 2$ + inc c + inc de + jr 1$ +2$: ld a, c + ret + + ;; char readc(void) -> A (4 = EOF) _readc: - push hl - push de - push bc - ld c, #2 ; SYS_READ + ld c, #2 ; SYS_READ rst #0x30 - pop bc - pop de - pop hl ret - ;; void sexit(unsigned char code /*A*/) + + ;; void sexit(unsigned char code /*A*/) - never returns _sexit: ld b, a - ld c, #0 ; SYS_EXIT + 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 + ld c, #8 ; SYS_GETPID rst #0x30 - pop bc - pop de - pop hl ret @@ -0,0 +1,9 @@ +#include "gbos.h" +/* print an unsigned byte as decimal (no runtime division: subtract-based) */ +static void putu(unsigned char n) { + char buf[3]; unsigned char i = 0; + if (n == 0) { putc('0'); return; } + while (n) { buf[i++] = '0' + (n % 10); n = n / 10; } + while (i) putc(buf[--i]); +} +void main(void) { putu(getpid()); nl(); } diff --git a/c/true.c b/c/true.c new file mode 100644 index 0000000..4cf437e --- /dev/null +++ b/c/true.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { sexit(0); } diff --git a/c/uname.c b/c/uname.c new file mode 100644 index 0000000..511501d --- /dev/null +++ b/c/uname.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { puts("gbos sm83 (Game Boy Color)"); nl(); } diff --git a/include/gbos.inc b/include/gbos.inc index 9f6a98a..01d7f1a 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -92,6 +92,12 @@ 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/ +DEF PROG_CHELLO EQU 3 ; C programs (compiled with SDCC), see c/ +DEF PROG_ECHO EQU 4 +DEF PROG_TRUE EQU 5 +DEF PROG_FALSE EQU 6 +DEF PROG_UNAME EQU 7 +DEF PROG_PID EQU 8 +DEF PROG_CAT EQU 9 ENDC diff --git a/src/programs.asm b/src/programs.asm index eabe56f..322c7fc 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -52,6 +52,24 @@ ProgHello: SECTION "prog_chello", ROMX[$4000], BANK[5] ProgChello: INCBIN "c/chello.bin" +SECTION "prog_echo", ROMX[$4000], BANK[6] +ProgEcho: + INCBIN "c/echo.bin" +SECTION "prog_true", ROMX[$4000], BANK[7] +ProgTrue: + INCBIN "c/true.bin" +SECTION "prog_false", ROMX[$4000], BANK[8] +ProgFalse: + INCBIN "c/false.bin" +SECTION "prog_uname", ROMX[$4000], BANK[9] +ProgUname: + INCBIN "c/uname.bin" +SECTION "prog_pid", ROMX[$4000], BANK[10] +ProgPid: + INCBIN "c/pid.bin" +SECTION "prog_cat", ROMX[$4000], BANK[11] +ProgCat: + INCBIN "c/cat.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - a tiny shell. @@ -72,6 +90,8 @@ ProgSh: ld c, SYS_READ rst $30 ; A = input byte pop hl + cp $04 + jr z, .eof ; Ctrl-D / EOF: exit the shell cp $0D jr z, .eol cp $0A @@ -85,6 +105,11 @@ ProgSh: cp LOW(SH_LINEBUF) + SH_MAXLINE - 1 jr c, .rd ; keep reading unless the buffer is full +.eof + ld b, 0 + ld c, SYS_EXIT ; exit init/shell -> system idle + rst $30 + .eol ld [hl], 0 ; NUL-terminate ld de, .crlf @@ -93,8 +118,24 @@ ProgSh: rst $30 ld a, [SH_LINEBUF] or a - jr z, .prompt ; empty line + jp z, .prompt ; empty line + + ; split off the first word: replace the first space with a NUL so command + ; matching sees just the command, and the args survive at SH_LINEBUF+n + ; (inherited by the child through fork, read back via main(char *args)). + ld hl, SH_LINEBUF +.split + ld a, [hl] + or a + jr z, .lookup ; no space -> no args + cp $20 + jr z, .cut + inc hl + jr .split +.cut + ld [hl], 0 ; isolate the command word +.lookup ; look the command up in the table ld de, .cmdtab .scan @@ -122,7 +163,7 @@ ProgSh: jr z, .exec ld c, SYS_WAIT ; parent: reap the command rst $30 - jr .prompt + jp .prompt .exec ld a, [SH_PROGID] ld b, a @@ -134,7 +175,7 @@ ProgSh: ld b, 3 ld c, SYS_WRITE rst $30 - jr .prompt + jp .prompt .ps1 db "$ " @@ -149,6 +190,18 @@ ProgSh: db PROG_HELLO db "chello", 0 db PROG_CHELLO + db "echo", 0 + db PROG_ECHO + db "true", 0 + db PROG_TRUE + db "false", 0 + db PROG_FALSE + db "uname", 0 + db PROG_UNAME + db "pid", 0 + db PROG_PID + db "cat", 0 + db PROG_CAT db 0 ; terminator ; ----------------------------------------------------------------------------- @@ -191,3 +244,15 @@ ProgramTable:: dw ProgHello db LOW(BANK(ProgChello)), HIGH(BANK(ProgChello)) dw ProgChello + db LOW(BANK(ProgEcho)), HIGH(BANK(ProgEcho)) + dw ProgEcho + db LOW(BANK(ProgTrue)), HIGH(BANK(ProgTrue)) + dw ProgTrue + db LOW(BANK(ProgFalse)), HIGH(BANK(ProgFalse)) + dw ProgFalse + db LOW(BANK(ProgUname)), HIGH(BANK(ProgUname)) + dw ProgUname + db LOW(BANK(ProgPid)), HIGH(BANK(ProgPid)) + dw ProgPid + db LOW(BANK(ProgCat)), HIGH(BANK(ProgCat)) + dw ProgCat |
