; ============================================================================= ; proc.asm - process table management, task creation, fork/exec (stubs) ; ============================================================================= INCLUDE "include/gbos.inc" SECTION "proc", ROM0 ; ----------------------------------------------------------------------------- ; ProcInit - table already zeroed by ClearKernelRAM (PS_FREE=0). Seed pids. ; ----------------------------------------------------------------------------- ProcInit:: ld a, 1 ld [wNextPid], a ; bank 0 belongs to the init task; the rest start free ld a, 1 ld [wBankUsed + 0], a ld hl, wBankUsed + 1 ld b, NUM_RAM_BANKS - 1 xor a .bclr ld [hl+], a dec b jr nz, .bclr ret ; ----------------------------------------------------------------------------- ; AllocRamBank - claim a free cart-RAM bank. out: A=bank, CF set if none. ; ----------------------------------------------------------------------------- AllocRamBank:: ld hl, wBankUsed ld c, 0 .scan ld a, [hl] or a jr z, .free inc hl inc c ld a, c cp NUM_RAM_BANKS jr c, .scan scf ret .free ld a, 1 ld [hl], a ; mark used ld a, c and a ; clear carry ret ; ----------------------------------------------------------------------------- ; FreeRamBank - release a cart-RAM bank. in: A=bank. ; ----------------------------------------------------------------------------- FreeRamBank:: ld hl, wBankUsed add l ld l, a ld a, h adc 0 ld h, a xor a ld [hl], a ret ; ----------------------------------------------------------------------------- ; FindPcbByPid - locate a live PCB by pid. ; in: A=pid out: DE=&PCB, CF set if not found. ; ----------------------------------------------------------------------------- FindPcbByPid:: ld b, a ; target pid ld c, 0 .l ld a, c call PcbPtr ; DE=&PCB[c] (preserves BC) ld a, [de] cp PS_FREE jr z, .n ld h, d ld l, e inc hl ; ->PROC_PID ld a, [hl] cp b jr z, .found .n inc c ld a, c cp MAX_PROCS jr c, .l scf ret .found and a ; clear carry (pids are >= 1) ret ; ----------------------------------------------------------------------------- ; ReparentToInit - give every live child of wExitMyPid to pid INIT_PID. ; ----------------------------------------------------------------------------- ReparentToInit:: ld c, 0 .l ld a, c call PcbPtr ; DE=&PCB (preserves BC) ld a, [de] cp PS_FREE jr z, .n ld h, d ld l, e ld a, l add PROC_PARENT ld l, a ld a, h adc 0 ld h, a ; HL=&PROC_PARENT ld a, [wExitMyPid] cp [hl] jr nz, .n ld a, INIT_PID ld [hl], a .n inc c ld a, c cp MAX_PROCS jr c, .l ret ; ----------------------------------------------------------------------------- ; FindFreeSlot - scan the proc table for a PS_FREE slot. ; out: A=slot, DE=&PCB, CF set if the table is full. ; ----------------------------------------------------------------------------- FindFreeSlot:: ld c, 0 .l ld a, c call PcbPtr ; DE = &PCB[c] ld a, [de] cp PS_FREE jr z, .ok inc c ld a, c cp MAX_PROCS jr c, .l scf ret .ok ld a, c and a ; clear carry ret ; ----------------------------------------------------------------------------- ; PcbPtr - slot -> PCB pointer. in: A=slot out: DE=&PCB. preserves BC. ; ----------------------------------------------------------------------------- PcbPtr:: push bc ld hl, wProcTable ld bc, PROC_SIZE and a jr z, .done .mul add hl, bc dec a jr nz, .mul .done ld d, h ld e, l pop bc ret ; ----------------------------------------------------------------------------- ; ProcCreate - allocate a PCB and build an initial (runnable) task. ; in: HL = task entry, B = cart-RAM bank, C = SVBK u-area bank ; out: A = pid, or 0 if the table is full ; ----------------------------------------------------------------------------- ProcCreate:: ; stash params ld a, l ld [wTmpEntry], a ld a, h ld [wTmpEntry+1], a ld a, b ld [wTmpRamB], a ld a, c ld [wTmpWramB], a ; find a free slot ld c, 0 .find ld a, c call PcbPtr ; DE = &PCB[c] ld a, [de] cp PS_FREE jr z, .found inc c ld a, c cp MAX_PROCS jr c, .find xor a ; table full ret .found ld a, c ld [wTmpSlot], a ; fill fields; HL walks the PCB ld h, d ld l, e ld a, PS_READY ld [hl+], a ; PROC_STATE ld a, [wNextPid] ld [hl+], a ; PROC_PID ld [wTmpPid], a inc a ld [wNextPid], a inc hl ; skip PROC_SP (filled below) inc hl ld a, 1 ld [hl+], a ; PROC_ROMB lo (placeholder text bank) xor a ld [hl+], a ; PROC_ROMB hi ld a, [wTmpRamB] ld [hl+], a ; PROC_RAMB ld a, [wTmpWramB] ld [hl+], a ; PROC_WRAMB xor a ld [hl+], a ; PROC_PARENT ld [hl], a ; PROC_EXIT ; build the initial stack frame in the task's RAM bank ld a, [wTmpEntry] ld l, a ld a, [wTmpEntry+1] ld h, a ; HL = entry ld a, [wTmpRamB] ld b, a ; B = RAM bank call ProcSetupStack ; out: HL = initial SP ld a, l ld [wTmpSP], a ld a, h ld [wTmpSP+1], a ; store SP into PCB.PROC_SP ld a, [wTmpSlot] call PcbPtr ; DE = &PCB ld a, e add PROC_SP ld l, a ld a, d adc 0 ld h, a ; HL = &PROC_SP ld a, [wTmpSP] ld [hl+], a ld a, [wTmpSP+1] ld [hl], a ld a, [wTmpPid] ret ; ----------------------------------------------------------------------------- ; ProcSetupStack - lay a fresh switch-in frame at the top of a task RAM bank. ; in: HL = entry, B = cart-RAM bank ; out: HL = initial SP (points at the saved-HL slot) ; The frame matches hSwitchTo's restore sequence: ; [SP+0..1]=hl [+2..3]=de [+4..5]=bc [+6]=f [+7]=a [+8..9]=entry (ret target) ; ----------------------------------------------------------------------------- ProcSetupStack:: ld a, b ld [MBC5_RAMB], a ; map task's RAM bank into $A000-$BFFF push hl ; save entry ld hl, TASK_STACK_TOP - 9 ; frame base = $BFF6 xor a ld [hl+], a ; hl_lo ld [hl+], a ; hl_hi ld [hl+], a ; de_lo ld [hl+], a ; de_hi ld [hl+], a ; bc_lo ld [hl+], a ; bc_hi ld [hl+], a ; f ld [hl+], a ; a pop de ; DE = entry ld a, e ld [hl+], a ; entry lo (ret target) ld a, d ld [hl], a ; entry hi ld hl, TASK_STACK_TOP - 9 ; initial SP ret ; ----------------------------------------------------------------------------- ; sys_fork / sys_exec - TODO. Plan: ; fork: alloc slot, copy parent's 8 KiB RAM bank -> child bank, dup u-area, ; patch child's saved-A to 0 (child return), parent gets child pid. ; exec: point PROC_ROMB at a flashed program image, reset RAM-bank layout ; (copy .data from ROM, zero .bss, fresh heap/stack), longjmp in. ; ----------------------------------------------------------------------------- ; ============================================================================= ; sys_fork() - duplicate the current process. ; out: A = child pid in the parent, 0 in the child, $FF on failure. ; ; Runs on the parent's user stack at entry (SP = Uafter, pointing at the rst ; return address). We switch to a kernel stack in WRAM0 so we're free to swap ; the $A000 cart-RAM window (the parent's user stack lives there), copy the ; parent's 8 KiB bank into a freshly allocated child bank via a WRAM bounce ; buffer, then plant a switch-in frame in the child so it "returns from fork" ; with A=0 to the same user PC. ; ============================================================================= sys_fork:: ; capture parent user SP (= Uafter) ld hl, sp+0 ld a, l ld [wForkUserSP], a ld a, h ld [wForkUserSP+1], a ld sp, KSTACK_TOP2 ; move off the cart-RAM stack call FindFreeSlot ; A=slot, DE=&child PCB jp c, .fail ld [wForkChildSlot], a call AllocRamBank ; A=child cart-RAM bank jp c, .fail ld [wForkChildBank], a ; remember the parent's bank so we can restore the window afterwards ld a, [wCurProc] add PROC_RAMB ld l, a ld a, [wCurProc+1] adc 0 ld h, a ld a, [hl] ld [wForkParentBank], a ; ---- copy parent bank -> child bank, 32 pages of 256 bytes ---- ld c, 0 ; page index 0..31 .page ld a, [wForkParentBank] ld [MBC5_RAMB], a ; map parent bank ld a, $A0 add c ld h, a ld l, 0 ; HL = $A000 + c*256 ld de, wCopyBuf ld b, 0 ; 256 iterations (b wraps 0->255..->0) .rd ld a, [hl+] ld [de], a inc de dec b jr nz, .rd ld a, [wForkChildBank] ld [MBC5_RAMB], a ; map child bank ld a, $A0 add c ld h, a ld l, 0 ld de, wCopyBuf ld b, 0 .wr ld a, [de] ld [hl+], a inc de dec b jr nz, .wr inc c ld a, c cp 32 jr c, .page ; child bank is currently mapped ; ---- plant the child's switch-in frame at Uafter-8 (in child bank) ---- ; hSwitchTo restore pops hl,de,bc,af then rets; we zero those 8 bytes and ; leave the copied return address (retU) at Uafter untouched, so the child ; resumes at the post-fork PC with A=0. ld a, [wForkUserSP] sub 8 ld l, a ld a, [wForkUserSP+1] sbc 0 ld h, a ; HL = Uafter-8 xor a ld b, 8 .zframe ld [hl+], a dec b jr nz, .zframe ; restore the parent's bank into the window (parent stack valid again) ld a, [wForkParentBank] ld [MBC5_RAMB], a ; ---- fill the child PCB ---- ld a, [wForkChildSlot] call PcbPtr ; DE = &child PCB ld h, d ld l, e ; HL walks child PCB ld a, PS_READY ld [hl+], a ; STATE ld a, [wNextPid] ld [hl+], a ; PID ld [wForkChildPid], a inc a ld [wNextPid], a ld a, [wForkUserSP] ; PROC_SP = Uafter-8 sub 8 ld [hl+], a ld a, [wForkUserSP+1] sbc 0 ld [hl+], a ; PROC_ROMB = parent ROMB (shared read-only text) ld a, [wCurProc] add PROC_ROMB ld e, a ld a, [wCurProc+1] adc 0 ld d, a ; DE = &parent.PROC_ROMB ld a, [de] ld [hl+], a inc de ld a, [de] ld [hl+], a ; PROC_RAMB = child bank ld a, [wForkChildBank] ld [hl+], a ; PROC_WRAMB = parent WRAMB (u-area shared for now) ld a, [wCurProc] add PROC_WRAMB ld e, a ld a, [wCurProc+1] adc 0 ld d, a ld a, [de] ld [hl+], a ; PROC_PARENT = parent pid ld a, [wCurProc] add PROC_PID ld e, a ld a, [wCurProc+1] adc 0 ld d, a ld a, [de] ld [hl+], a ; PROC_EXIT = 0 xor a ld [hl], a ; ---- return to the parent with child pid ---- ld a, [wForkUserSP] ld l, a ld a, [wForkUserSP+1] ld h, a ld sp, hl ; back on the parent's user stack ld a, [wForkChildPid] ret .fail ; restore parent user stack and report failure ld a, [wForkUserSP] ld l, a ld a, [wForkUserSP+1] ld h, a 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)