; ============================================================================= ; 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_CHELLO (bank 5) - a program written in C, compiled with SDCC to a ; self-contained ROM-bank image (crt0 + libc + code) and included verbatim. ; Rebuild with: sh c/build.sh c/chello.c c/chello.bin ; ----------------------------------------------------------------------------- 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" SECTION "prog_wc", ROMX[$4000], BANK[12] ProgWc: INCBIN "c/wc.bin" SECTION "prog_head", ROMX[$4000], BANK[13] ProgHead: INCBIN "c/head.bin" SECTION "prog_args", ROMX[$4000], BANK[14] ProgArgs: INCBIN "c/args.bin" ; ----------------------------------------------------------------------------- ; 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 push hl ; syscalls clobber HL (trap uses it to dispatch) 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 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 ld a, l 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 ld b, 2 ld c, SYS_WRITE rst $30 ld a, [SH_LINEBUF] or a 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 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 jp .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 jp .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 "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 "wc", 0 db PROG_WC db "head", 0 db PROG_HEAD db "args", 0 db PROG_ARGS 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 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 db LOW(BANK(ProgWc)), HIGH(BANK(ProgWc)) dw ProgWc db LOW(BANK(ProgHead)), HIGH(BANK(ProgHead)) dw ProgHead db LOW(BANK(ProgArgs)), HIGH(BANK(ProgArgs)) dw ProgArgs