diff options
Diffstat (limited to '')
| -rw-r--r-- | README.md | 23 | ||||
| -rw-r--r-- | include/gbos.inc | 6 | ||||
| -rw-r--r-- | src/proc.asm | 53 | ||||
| -rw-r--r-- | src/programs.asm | 52 | ||||
| -rw-r--r-- | src/syscall.asm | 2 | ||||
| -rw-r--r-- | src/tasks.asm | 36 |
6 files changed, 145 insertions, 27 deletions
@@ -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 |
