aboutsummaryrefslogtreecommitdiffstats
path: root/src/programs.asm
blob: d4ae89f3e0dbcd939525487ddbd1a9c377c5e829 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
; =============================================================================
; 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
    push hl                     ; syscalls clobber HL (trap uses it to dispatch)
    ld c, SYS_READ
    rst $30                     ; A = input byte
    pop hl
    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

.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