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 ++++++++++++++++++++++++++++++++++++++++++++++++++----- src/syscall.asm | 21 ++++++- src/tasks.asm | 62 ++------------------- 3 files changed, 177 insertions(+), 72 deletions(-) (limited to 'src') 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 diff --git a/src/syscall.asm b/src/syscall.asm index 4b8771c..bf75865 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -36,7 +36,7 @@ SyscallTrap:: SyscallTable: dw sys_exit ; 0 dw sys_fork ; 1 FORK - dw sys_nosys ; 2 READ (TODO) + dw sys_read ; 2 READ dw sys_write ; 3 WRITE dw sys_nosys ; 4 OPEN (TODO) dw sys_nosys ; 5 CLOSE (TODO) @@ -70,6 +70,25 @@ sys_write: ld a, b ; return len ret +; ----------------------------------------------------------------------------- +; sys_read() -> A = one input byte from the console (blocking). +; Uses an external-clock serial transfer as a clean RX: it completes only when +; the host has a byte (no stdout echo). While waiting we yield so other procs +; run. Preserves BC/DE/HL (the context switch saves/restores them). +; ----------------------------------------------------------------------------- +sys_read: +.poll + ld a, $80 + ld [rSC], a ; request a byte (external clock) + ld a, [rSC] + bit 7, a + jr z, .got ; bit7 clear => transfer completed + call SchedYield ; nothing yet: let others run, then retry + jr .poll +.got + ld a, [rSB] + ret + ; ----------------------------------------------------------------------------- ; sys_getpid() -> A = current pid ; ----------------------------------------------------------------------------- diff --git a/src/tasks.asm b/src/tasks.asm index 028d074..687bddf 100644 --- a/src/tasks.asm +++ b/src/tasks.asm @@ -1,67 +1,13 @@ ; ============================================================================= -; tasks.asm - init exercises the full process lifecycle. -; -; init fork()s three children; each child exec()s PROG_WORKER, which prints 'w' -; and exit()s with its pid as the status. init then wait()s in a loop, printing -; 'R' + the reaped pid digit for each child, until wait() returns $FF (no more -; children), then prints '!' and idles. This drives fork + exec + exit + wait + -; zombie reaping + cart-RAM bank recycling. -; -; The fork counter lives at $A000 (bottom of init's own RAM bank) because the -; register file does not survive a fork() call. +; tasks.asm - init just becomes the shell. +; init (pid 1) exec()s PROG_SH; the shell then drives fork+exec+wait to run +; commands typed on the serial console. As pid 1 it is also the orphan reaper. ; ============================================================================= INCLUDE "include/gbos.inc" -DEF ASCII0 EQU $30 ; '0', added to a pid to print it as a digit -DEF CH_R EQU $52 ; 'R' -DEF CH_BANG EQU $21 ; '!' - SECTION "tasks", ROM0 TaskInit:: - ld a, 3 - ld [$A000], a ; children left to spawn - -.forkloop - ld c, SYS_FORK - rst $30 - or a - jr z, .child ; A==0 -> we are the child - ; parent: one more child spawned - ld a, [$A000] - dec a - ld [$A000], a - jr nz, .forkloop - -.waitloop - ld c, SYS_WAIT - rst $30 ; A = reaped pid (B = code), or $FF if none left - cp $FF - jr z, .done - ; print 'R' then the reaped pid as a digit - push af - ld a, CH_R - ld [$FF01], a - ld a, $81 - ld [$FF02], a - pop af - add ASCII0 - ld [$FF01], a - ld a, $81 - ld [$FF02], a - jr .waitloop - -.done - ld a, CH_BANG - ld [$FF01], a - ld a, $81 - ld [$FF02], a -.idle - ld c, SYS_YIELD - rst $30 - jr .idle - -.child - ld b, PROG_WORKER + ld b, PROG_SH ld c, SYS_EXEC rst $30 ; never returns -- cgit v1.3.1-sl0p