aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile3
-rw-r--r--README.md40
-rw-r--r--c/cat.c10
-rw-r--r--c/crt0.s6
-rw-r--r--c/echo.c5
-rw-r--r--c/false.c2
-rw-r--r--c/gbos.h16
-rw-r--r--c/libc.s84
-rw-r--r--c/pid.c9
-rw-r--r--c/true.c2
-rw-r--r--c/uname.c2
-rw-r--r--include/gbos.inc8
-rw-r--r--src/programs.asm71
13 files changed, 214 insertions, 44 deletions
diff --git a/Makefile b/Makefile
index ad14afd..4d59135 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 49909f9..c386c08 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/c/cat.c b/c/cat.c
new file mode 100644
index 0000000..4092778
--- /dev/null
+++ b/c/cat.c
@@ -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);
+ }
+}
diff --git a/c/crt0.s b/c/crt0.s
index cde4b67..0d8371f 100644
--- a/c/crt0.s
+++ b/c/crt0.s
@@ -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); }
diff --git a/c/gbos.h b/c/gbos.h
index 9a09a5d..3a10e06 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -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
diff --git a/c/libc.s b/c/libc.s
index 4fca5e3..29031e9 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -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
diff --git a/c/pid.c b/c/pid.c
new file mode 100644
index 0000000..a282a89
--- /dev/null
+++ b/c/pid.c
@@ -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