From d5c06116d3d1257b46c476cf73b634cf089bcb82 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 16 Jul 2026 00:31:27 +0200 Subject: wip: sys_read + serial console shell (read-store bug under investigation) - sys_read (SYS_READ): blocking console input via external-clock serial poll; yields while waiting. Verified working (direct-echo cat: 'hi' -> 'hi'). - shell (PROG_SH): prompt/read/parse/fork+exec+wait; worker+hello programs; StrEqual/SkipName helpers; command table. - init now execs the shell. - KNOWN BUG: storing the typed char to the line buffer at $A000 (cart RAM of an exec'd program) corrupts the following sys_read; sys_write reading $A000 also returns $3E. Kernel syscalls themselves are correct; isolating the cart-RAM / exec RAM-bank interaction next. --- src/programs.asm | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 153 insertions(+), 13 deletions(-) (limited to 'src/programs.asm') diff --git a/src/programs.asm b/src/programs.asm index b5e4d8e..df2b98d 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -1,36 +1,176 @@ ; ============================================================================= -; programs.asm - userland program images (text-in-ROM model). +; programs.asm - userland program images (text-in-ROM model) + the shell. ; ; Programs are linked to run from the ROMX window ($4000-$7FFF) and live in -; their own ROM banks. exec() maps the bank and jumps to the entry. Programs -; only talk to the kernel via `rst $30` (the trap + kernel live in ROM0, always -; mapped), so they never reference kernel labels. +; their own ROM banks. exec() maps the bank and jumps in. Programs talk to the +; kernel only via `rst $30`. ; ============================================================================= INCLUDE "include/gbos.inc" +; the shell keeps its line buffer + scratch at the bottom of its RAM bank +DEF SH_LINEBUF EQU $A000 ; up to 32 bytes +DEF SH_PROGID EQU $A040 ; chosen program id (survives fork via bank copy) +DEF SH_MAXLINE EQU 32 + ; ----------------------------------------------------------------------------- -; PROG_WORKER: announce with 'w', then exit() with a status equal to our pid. -; Exercises exec (runs from a ROM bank) + the exit/wait lifecycle. +; PROG_WORKER (bank 2) - print a line, exit(0). ; ----------------------------------------------------------------------------- SECTION "prog_worker", ROMX[$4000], BANK[2] ProgWorker: ld de, .msg - ld b, 1 + ld b, .msgend - .msg ld c, SYS_WRITE rst $30 - ld c, SYS_GETPID + ld b, 0 + ld c, SYS_EXIT rst $30 - ld b, a ; exit status = pid (so reaped codes are distinct) +.msg + db "worker!", $0D, $0A +.msgend + +; ----------------------------------------------------------------------------- +; PROG_HELLO (bank 4) - print a line, exit(0). +; ----------------------------------------------------------------------------- +SECTION "prog_hello", ROMX[$4000], BANK[4] +ProgHello: + ld de, .msg + ld b, .msgend - .msg + ld c, SYS_WRITE + rst $30 + ld b, 0 ld c, SYS_EXIT - rst $30 ; never returns + rst $30 .msg - db "w" + db "hello world!", $0D, $0A +.msgend + +; ----------------------------------------------------------------------------- +; PROG_SH (bank 3) - a tiny shell. +; prompt -> read a line (with echo) -> match a command -> fork+exec+wait. +; ----------------------------------------------------------------------------- +SECTION "prog_sh", ROMX[$4000], BANK[3] +ProgSh: +.prompt + ld de, .ps1 + ld b, 2 + ld c, SYS_WRITE + rst $30 + + ; read a line into SH_LINEBUF, echoing as we go + ld hl, SH_LINEBUF +.rd + ld c, SYS_READ + rst $30 ; A = input byte + cp $0D + jr z, .eol + cp $0A + jr z, .eol + ld [hl], a ; store in the line buffer (A preserved) + inc hl + ld [$FF01], a ; echo the char straight to serial + ld a, $81 + ld [$FF02], a + jr .rd + +.eol + ld [hl], 0 ; NUL-terminate + ld de, .crlf + ld b, 2 + ld c, SYS_WRITE + rst $30 + ld a, [SH_LINEBUF] + or a + jr z, .prompt ; empty line + + ; look the command up in the table + ld de, .cmdtab +.scan + ld a, [de] + or a + jr z, .unknown ; empty name = end of table + push de + ld hl, SH_LINEBUF + call StrEqual ; Z set if equal + pop de + jr z, .match + call SkipName ; DE -> NUL after this name + inc de ; -> program id + inc de ; -> next entry + jr .scan + +.match + call SkipName + inc de ; DE -> program id + ld a, [de] + ld [SH_PROGID], a + ld c, SYS_FORK + rst $30 + or a + jr z, .exec + ld c, SYS_WAIT ; parent: reap the command + rst $30 + jr .prompt +.exec + ld a, [SH_PROGID] + ld b, a + ld c, SYS_EXEC + rst $30 ; child becomes the command + +.unknown + ld de, .huh + ld b, 3 + ld c, SYS_WRITE + rst $30 + jr .prompt + +.ps1 + db "$ " +.crlf + db $0D, $0A +.huh + db "?", $0D, $0A +.cmdtab + db "worker", 0 + db PROG_WORKER + db "hello", 0 + db PROG_HELLO + db 0 ; terminator + +; ----------------------------------------------------------------------------- +; Shell string helpers (ROM0, always mapped). +; ----------------------------------------------------------------------------- +SECTION "shell_lib", ROM0 +; StrEqual(HL, DE) - compare NUL-terminated strings. Z set if equal. +; Clobbers A, B; advances HL and DE. +StrEqual:: +.l + ld a, [de] + ld b, a + ld a, [hl] + cp b + ret nz + or a + ret z ; both NUL -> equal + inc hl + inc de + jr .l +; SkipName(DE) - advance DE to its NUL terminator. +SkipName:: +.l + ld a, [de] + or a + ret z + inc de + jr .l ; ----------------------------------------------------------------------------- -; Program table (ROM0). Indexed by exec()'s program id. -; Entry: bank (2 bytes, MBC5 16-bit) then entry address (2 bytes). +; Program table (ROM0). Index = program id. Entry: bank (2) + entry addr (2). ; ----------------------------------------------------------------------------- SECTION "progtab", ROM0 ProgramTable:: db LOW(BANK(ProgWorker)), HIGH(BANK(ProgWorker)) dw ProgWorker + db LOW(BANK(ProgSh)), HIGH(BANK(ProgSh)) + dw ProgSh + db LOW(BANK(ProgHello)), HIGH(BANK(ProgHello)) + dw ProgHello -- cgit v1.3.1-sl0p