aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README.md23
-rw-r--r--include/gbos.inc6
-rw-r--r--src/proc.asm53
-rw-r--r--src/programs.asm52
-rw-r--r--src/syscall.asm2
-rw-r--r--src/tasks.asm36
6 files changed, 145 insertions, 27 deletions
diff --git a/README.md b/README.md
index 2d2946c..3f6aab0 100644
--- a/README.md
+++ b/README.md
@@ -48,9 +48,27 @@ User loads `C` = syscall number, args in `DE`/`B`, then `rst $30`. Return in `A`
| 0 | exit | ✅ marks zombie, reschedules |
| 1 | fork | ✅ copies the 8 KiB bank, child returns 0 |
| 3 | write | ✅ `DE`=buf `B`=len → serial console |
+| 6 | exec | ✅ `B`=program id → maps ROM text bank, jumps in |
| 8 | getpid | ✅ |
| 11| yield | ✅ cooperative switch |
-| 2,4,5,6,7,9,10 | read/open/close/exec/wait/kill/brk | ⛔ `ENOSYS` |
+| 2,4,5,7,9,10 | read/open/close/wait/kill/brk | ⛔ `ENOSYS` |
+
+### exec (`sys_exec`, src/proc.asm + programs.asm)
+
+Where the text-in-ROM model pays off. Programs are linked to run from the ROMX
+window (`$4000-$7FFF`) and stored in their own ROM banks (`programs.asm`).
+`exec(B=program id)`:
+
+1. looks up `ProgramTable[B]` = (ROM bank, entry),
+2. sets `PROC_ROMB` and maps the bank live into `$4000-$7FFF`,
+3. resets the stack to the top of the task's (already mapped) cart-RAM bank,
+4. `jp`s to the entry — it never returns.
+
+Proof it's really bank-driven: both demo programs are ORG'd at the **same**
+address `$4000` in **different** banks (02 and 03). The parent `exec`s
+`PROG_PING`, the child `exec`s `PROG_PONG`, and the output `1212...` (2000/2000,
+zero garbage) can only happen if each process executes its own bank via
+`PROC_ROMB`. A fuller exec would also copy `.data` from ROM and zero `.bss`.
### fork (`sys_fork`, src/proc.asm)
@@ -112,7 +130,8 @@ A real gbos would render to VRAM; serial is the zero-VRAM debug channel.
## Roadmap
- [x] `fork`: copy parent's 8 KiB RAM bank → free bank, child returns 0
-- [ ] `exec`: point `PROC_ROMB` at a flashed program, reset RAM-bank layout
+- [x] `exec`: point `PROC_ROMB` at a program in a ROM bank, reset stack, enter
+- [ ] `exec` refinement: copy `.data` from ROM + zero `.bss` for RW globals
- [ ] Preemptive scheduling: real context save in `TimerISR` → `hSwitchTo`
- [ ] Use the `$D000-$DFFF` u-area (SVBK) for per-process kernel state / kstack
- [ ] `brk`/heap allocator inside the task bank (heap up, stack down, collision = ENOMEM)
diff --git a/include/gbos.inc b/include/gbos.inc
index 0727b02..e15f7cb 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -85,4 +85,10 @@ DEF SYS_BRK EQU 10
DEF SYS_YIELD EQU 11
DEF SYS_MAX EQU 12
+; -----------------------------------------------------------------------------
+; Program table indices (see programs.asm). exec() takes one of these in B.
+; -----------------------------------------------------------------------------
+DEF PROG_PING EQU 0
+DEF PROG_PONG EQU 1
+
ENDC
diff --git a/src/proc.asm b/src/proc.asm
index 058881f..114cc9c 100644
--- a/src/proc.asm
+++ b/src/proc.asm
@@ -368,3 +368,56 @@ sys_fork::
ld sp, hl
ld a, $FF
ret
+
+; =============================================================================
+; sys_exec(B = program id) - replace the current process image. Never returns.
+;
+; Looks up ProgramTable[B] = (ROM bank, entry). Points PROC_ROMB at the program
+; text bank, maps it live into $4000-$7FFF, resets the stack to the top of the
+; task's (already mapped) cart-RAM bank, and jumps to the entry. The task keeps
+; its RAM bank; a fuller exec would also copy .data from ROM and zero .bss, but
+; our demo programs keep no pre-initialized RAM.
+; =============================================================================
+sys_exec::
+ ; HL = &ProgramTable[B] = ProgramTable + B*4
+ ld hl, ProgramTable
+ ld a, b
+ add a ; *2
+ add a ; *4 (program id < 64)
+ add l
+ ld l, a
+ jr nc, .nc
+ inc h
+.nc
+ ld a, [hl+] ; bank lo
+ ld b, a
+ ld a, [hl+] ; bank hi
+ ld c, a
+ ld a, [hl+] ; entry lo
+ ld e, a
+ ld a, [hl] ; entry hi
+ ld d, a ; DE = entry, BC = bank(lo,hi)
+
+ ; PROC_ROMB = BC
+ ld a, [wCurProc]
+ add PROC_ROMB
+ ld l, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld h, a
+ ld a, b
+ ld [hl+], a
+ ld a, c
+ ld [hl], a
+
+ ; map the program's ROM bank into $4000-$7FFF
+ ld a, b
+ ld [MBC5_ROMB_LO], a
+ ld a, c
+ ld [MBC5_ROMB_HI], a
+
+ ; fresh empty stack at the top of the task's cart-RAM bank, then enter
+ ld sp, TASK_RAM_TOP ; $C000; first push lands at $BFFF
+ ld h, d
+ ld l, e
+ jp hl ; jump to program entry ($4000)
diff --git a/src/programs.asm b/src/programs.asm
new file mode 100644
index 0000000..a8d8b63
--- /dev/null
+++ b/src/programs.asm
@@ -0,0 +1,52 @@
+; =============================================================================
+; programs.asm - userland program images (text-in-ROM model).
+;
+; Each program is linked to execute from the ROMX window ($4000-$7FFF) and
+; lives in its own ROM bank. exec() maps the bank and jumps to the entry.
+;
+; To prove ROM banking really drives execution, both demo programs are ORG'd at
+; the SAME address ($4000) in DIFFERENT banks - the only thing selecting which
+; code runs is PROC_ROMB. They also read their message string out of the mapped
+; bank, so the correct bank must be live when sys_write runs.
+;
+; Programs only ever talk to the kernel via `rst $30` (the trap + all kernel
+; code live in ROM0, which is always mapped), so they never call kernel labels.
+; =============================================================================
+INCLUDE "include/gbos.inc"
+
+SECTION "prog_ping", ROMX[$4000], BANK[2]
+ProgPing:
+.loop
+ ld de, .msg
+ ld b, 1
+ ld c, SYS_WRITE
+ rst $30
+ ld c, SYS_YIELD
+ rst $30
+ jr .loop
+.msg
+ db "1"
+
+SECTION "prog_pong", ROMX[$4000], BANK[3]
+ProgPong:
+.loop
+ ld de, .msg
+ ld b, 1
+ ld c, SYS_WRITE
+ rst $30
+ ld c, SYS_YIELD
+ rst $30
+ jr .loop
+.msg
+ db "2"
+
+; -----------------------------------------------------------------------------
+; Program table (in ROM0, always mapped). Indexed by exec()'s program id.
+; Entry layout: bank (2 bytes, MBC5 16-bit) then entry address (2 bytes).
+; -----------------------------------------------------------------------------
+SECTION "progtab", ROM0
+ProgramTable::
+ db LOW(BANK(ProgPing)), HIGH(BANK(ProgPing))
+ dw ProgPing
+ db LOW(BANK(ProgPong)), HIGH(BANK(ProgPong))
+ dw ProgPong
diff --git a/src/syscall.asm b/src/syscall.asm
index ca2857c..fd1b213 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -40,7 +40,7 @@ SyscallTable:
dw sys_write ; 3 WRITE
dw sys_nosys ; 4 OPEN (TODO)
dw sys_nosys ; 5 CLOSE (TODO)
- dw sys_nosys ; 6 EXEC (TODO)
+ dw sys_exec ; 6 EXEC
dw sys_nosys ; 7 WAIT (TODO)
dw sys_getpid ; 8 GETPID
dw sys_nosys ; 9 KILL (TODO)
diff --git a/src/tasks.asm b/src/tasks.asm
index 459ccb6..0b5276d 100644
--- a/src/tasks.asm
+++ b/src/tasks.asm
@@ -1,9 +1,10 @@
; =============================================================================
-; tasks.asm - the init task demonstrates fork().
-; init forks once; the parent loops printing 'P', the child loops printing 'C'.
-; Both run the same ROM text, diverging on fork()'s return value (0 in child).
-; Exercises: syscall trap, fork (8 KiB bank copy + child frame), scheduler,
-; and the HRAM context switch swapping cart-RAM banks per task.
+; tasks.asm - the init task demonstrates fork() + exec().
+;
+; init forks; the parent exec()s PROG_PING (ROM bank 2), the child exec()s
+; PROG_PONG (ROM bank 3). Both program images are ORG'd at $4000, so the output
+; "1212..." proves each process runs from its own ROM bank via PROC_ROMB - not
+; from shared ROM0.
; =============================================================================
INCLUDE "include/gbos.inc"
@@ -16,24 +17,11 @@ TaskInit::
jr z, .child
.parent
- ld de, .pmsg
- ld b, 1
- ld c, SYS_WRITE
- rst $30
- ld c, SYS_YIELD
- rst $30
- jr .parent
+ ld b, PROG_PING
+ ld c, SYS_EXEC
+ rst $30 ; never returns
.child
- ld de, .cmsg
- ld b, 1
- ld c, SYS_WRITE
- rst $30
- ld c, SYS_YIELD
- rst $30
- jr .child
-
-.pmsg
- db "P"
-.cmsg
- db "C"
+ ld b, PROG_PONG
+ ld c, SYS_EXEC
+ rst $30 ; never returns