; ============================================================================= ; 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 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 (bank 2) - print a line, exit(0). ; ----------------------------------------------------------------------------- SECTION "prog_worker", ROMX[$4000], BANK[2] ProgWorker: ld de, .msg ld b, .msgend - .msg ld c, SYS_WRITE rst $30 ld b, 0 ld c, SYS_EXIT rst $30 .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 .msg 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). 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