aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile2
-rw-r--r--c/chat.c57
-rw-r--r--c/gbos.h2
-rw-r--r--c/libc.s19
-rw-r--r--include/gbos.inc5
-rw-r--r--src/net.asm22
-rw-r--r--src/programs.asm7
-rw-r--r--src/syscall.asm2
-rw-r--r--tools/gateway.py16
9 files changed, 127 insertions, 5 deletions
diff --git a/Makefile b/Makefile
index 2dc3db7..cf04419 100644
--- a/Makefile
+++ b/Makefile
@@ -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/blk.bin c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin c/wget.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 c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin c/wget.bin c/chat.bin
$(BUILD)/programs.o: $(CBLOBS)
# C programs also depend on the shared libc sources
diff --git a/c/chat.c b/c/chat.c
new file mode 100644
index 0000000..987b336
--- /dev/null
+++ b/c/chat.c
@@ -0,0 +1,57 @@
+#include "netlib.h"
+/* chat: a link-port chat client. Incoming SLIP frames from the host gateway
+ print as messages; SELECT brings up the on-screen keyboard, type a line and
+ press START to send it. A poll loop interleaves receiving and typing so
+ messages arrive while you're not mid-line. */
+
+static char rxbuf[96];
+static unsigned char rxlen, rxin, rxesc;
+
+/* feed one non-blocking byte to the SLIP receiver; returns a frame length when
+ a whole frame has arrived (copied into out), else 0. */
+static unsigned char slip_poll(char *out) {
+ int b = srecv_nb();
+ unsigned char c, i, n;
+ if (b < 0) return 0;
+ c = (unsigned char)b;
+ if (c == SLIP_END) {
+ if (rxin && rxlen) {
+ n = rxlen;
+ for (i = 0; i < n; i++) out[i] = rxbuf[i];
+ rxlen = 0; rxin = 0; rxesc = 0;
+ return n;
+ }
+ rxin = 1; rxlen = 0; rxesc = 0;
+ return 0;
+ }
+ if (!rxin) return 0;
+ if (rxesc) {
+ if (c == SLIP_ESC_END) c = SLIP_END;
+ else if (c == SLIP_ESC_ESC) c = SLIP_ESC;
+ rxesc = 0;
+ } else if (c == SLIP_ESC) { rxesc = 1; return 0; }
+ if (rxlen < 96) rxbuf[rxlen++] = (char)c;
+ return 0;
+}
+
+void main(void) {
+ char line[64], frame[96];
+ unsigned char linelen = 0, n, i, c;
+ puts("chat: SELECT=keyboard START=send"); nl();
+ for (;;) {
+ n = slip_poll(frame); /* incoming message? */
+ if (n) {
+ for (i = 0; i < n; i++) putc(frame[i]);
+ nl();
+ }
+ c = pollin(); /* typed a key? */
+ if (c == '\n') {
+ if (linelen) { slip_send(line, linelen); linelen = 0; }
+ } else if (c == 8) {
+ if (linelen) { linelen--; putc(8); }
+ } else if (c) {
+ if (linelen < 63) { line[linelen++] = (char)c; putc((char)c); }
+ }
+ yield();
+ }
+}
diff --git a/c/gbos.h b/c/gbos.h
index d207a75..df52fbe 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -15,6 +15,8 @@ unsigned char getpid(void);
unsigned char pipe(void); /* -> read fd; write end is read+1 */
void ssend(unsigned char b); /* link-port: transmit one byte */
unsigned char srecv(void); /* link-port: receive one byte (blocks) */
+int srecv_nb(void); /* link-port: receive, or -1 if none */
+unsigned char pollin(void); /* poll OSK for a typed char, or 0 */
/* helpers (c/libc.c) */
void putu(unsigned int n); /* print decimal */
diff --git a/c/libc.s b/c/libc.s
index 50d7860..7897a79 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -1,6 +1,6 @@
.module libc
.globl _writes, _putc, _puts, _nl, _strlen, _readc, _sexit, _getpid, _getargs
- .globl _open, _close, _fgetc, _fputc, _flist, _fremove, _pipe, _ssend, _srecv
+ .globl _open, _close, _fgetc, _fputc, _flist, _fremove, _pipe, _ssend, _srecv, _srecv_nb, _pollin
;; 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.
@@ -105,6 +105,23 @@ _srecv:
rst #0x30
ret
+ ;; int srecv_nb(void) -> byte, or -1 if none (non-blocking)
+_srecv_nb:
+ ld c, #31 ; SYS_SRECVNB
+ rst #0x30
+ jr c, 1$
+ ld c, a
+ ld b, #0
+ ret
+1$: ld bc, #0xffff
+ ret
+
+ ;; unsigned char pollin(void) -> A (typed OSK char, or 0)
+_pollin:
+ ld c, #32 ; SYS_POLLIN
+ rst #0x30
+ ret
+
;; unsigned char open(const char *name /*DE*/, unsigned char mode /*A*/) -> A(fd)
_open:
ld b, a ; B = mode
diff --git a/include/gbos.inc b/include/gbos.inc
index 7103f48..a6467bf 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -128,7 +128,9 @@ DEF SYS_OPENDIR EQU 27 ; point ls at a directory (DE=path -> A=0/$FF)
DEF SYS_PIPE EQU 28 ; make a pipe (-> A=read fd; write=A+1)
DEF SYS_SSEND EQU 29 ; link-port: transmit one byte (E=byte)
DEF SYS_SRECV EQU 30 ; link-port: receive one byte (-> A=byte, blocks)
-DEF SYS_MAX EQU 31
+DEF SYS_SRECVNB EQU 31 ; link-port: receive, non-blocking (-> A=byte, CF=none)
+DEF SYS_POLLIN EQU 32 ; poll OSK for a typed char (-> A=char or 0)
+DEF SYS_MAX EQU 33
; 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)
@@ -215,5 +217,6 @@ DEF PROG_COUNT EQU 21
DEF PROG_PTEST EQU 22
DEF PROG_NECHO EQU 23
DEF PROG_WGET EQU 24
+DEF PROG_CHAT EQU 25
ENDC
diff --git a/src/net.asm b/src/net.asm
index 939b975..f2c4279 100644
--- a/src/net.asm
+++ b/src/net.asm
@@ -39,3 +39,25 @@ sys_srecv::
.got
ld a, [rSB]
ret
+
+; sys_srecv_nb() -> A = byte, CF set if none available (non-blocking).
+sys_srecv_nb::
+ ld a, $80
+ ld [rSC], a
+ ld a, [rSC]
+ bit 7, a
+ jr z, .got
+ scf
+ ret
+.got
+ ld a, [rSB]
+ and a ; CF = 0 (got a byte)
+ ret
+
+; sys_pollin() -> A = char typed on the OSK this poll, or 0 if none.
+sys_pollin::
+ call pad_poll
+ call osk_handle ; CF set + A = char if a key was pressed
+ ret c
+ xor a
+ ret
diff --git a/src/programs.asm b/src/programs.asm
index fbe6067..180f551 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -115,6 +115,9 @@ ProgNecho:
SECTION "prog_wget", ROMX[$4000], BANK[26]
ProgWget:
INCBIN "c/wget.bin"
+SECTION "prog_chat", ROMX[$4000], BANK[27]
+ProgChat:
+ INCBIN "c/chat.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and
@@ -207,6 +210,8 @@ ProgramTable::
dw ProgNecho
db LOW(BANK(ProgWget)), HIGH(BANK(ProgWget))
dw ProgWget
+ db LOW(BANK(ProgChat)), HIGH(BANK(ProgChat))
+ dw ProgChat
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> program id, searched by sys_lookup.
@@ -263,4 +268,6 @@ NameTable::
db PROG_NECHO
db "wget", 0
db PROG_WGET
+ db "chat", 0
+ db PROG_CHAT
db 0
diff --git a/src/syscall.asm b/src/syscall.asm
index cb16bbe..f926d89 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -65,6 +65,8 @@ SyscallTable:
dw sys_pipe ; 28 PIPE
dw sys_ssend ; 29 SSEND
dw sys_srecv ; 30 SRECV
+ dw sys_srecv_nb ; 31 SRECVNB
+ dw sys_pollin ; 32 POLLIN
; -----------------------------------------------------------------------------
sys_nosys:
diff --git a/tools/gateway.py b/tools/gateway.py
index 3129773..3b18024 100644
--- a/tools/gateway.py
+++ b/tools/gateway.py
@@ -46,6 +46,10 @@ def http_fetch(payload):
def handle(payload, mode):
if mode == "http":
return http_fetch(payload)
+ if mode == "chat":
+ msg = bytes(payload).decode("latin1", "replace")
+ sys.stderr.write("\n[gw] GB says: %s\n" % msg)
+ return [("<bot> " + msg).encode()] # a simple echo-bot peer
return [bytes(payload)] # echo
def reader(out, inp, mode):
@@ -75,15 +79,23 @@ def reader(out, inp, mode):
def main():
ap = argparse.ArgumentParser()
- ap.add_argument("--mode", default="echo", choices=["echo", "http"])
+ ap.add_argument("--mode", default="echo", choices=["echo", "http", "chat"])
ap.add_argument("--cmd", default="necho")
ap.add_argument("--rom", default=os.path.join(os.path.dirname(__file__), "..", "gbos.gb"))
+ ap.add_argument("--keys", default=None)
a = ap.parse_args()
- p = subprocess.Popen([EMU, "--headless", "--uncapped", a.rom],
+ cmd = [EMU, "--headless", "--uncapped"]
+ if a.keys: cmd += ["--keys", a.keys]
+ cmd.append(a.rom)
+ p = subprocess.Popen(cmd,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0)
threading.Thread(target=reader, args=(p.stdout, p.stdin, a.mode), daemon=True).start()
time.sleep(1.0)
p.stdin.write((a.cmd + "\n").encode()); p.stdin.flush()
+ if a.mode == "chat":
+ time.sleep(1.0)
+ for m in [b"welcome to sl0pchat", b"<alice> hey gameboy!", b"<bob> nice link cable"]:
+ p.stdin.write(slip_encode(m)); p.stdin.flush(); time.sleep(0.6)
time.sleep(4.0)
p.stdin.write(b"exit\n"); p.stdin.flush()
try: p.wait(timeout=3)