aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/gbos.inc2
-rw-r--r--src/programs.asm166
-rw-r--r--src/syscall.asm21
-rw-r--r--src/tasks.asm62
4 files changed, 179 insertions, 72 deletions
diff --git a/include/gbos.inc b/include/gbos.inc
index 7919bd6..f398c12 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -90,5 +90,7 @@ DEF SYS_MAX EQU 12
; Program table indices (see programs.asm). exec() takes one of these in B.
; -----------------------------------------------------------------------------
DEF PROG_WORKER EQU 0
+DEF PROG_SH EQU 1
+DEF PROG_HELLO EQU 2
ENDC
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)
@@ -71,6 +71,25 @@ sys_write:
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
; -----------------------------------------------------------------------------
sys_getpid:
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