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
|
; =============================================================================
; sched.asm - round-robin scheduler + yield + timer stub
; =============================================================================
INCLUDE "include/gbos.inc"
SECTION "sched", ROM0
; -----------------------------------------------------------------------------
; SchedInit - prime the round-robin cursor so the first pick lands on slot 0.
; -----------------------------------------------------------------------------
SchedInit::
ld a, MAX_PROCS - 1
ld [wSchedNext], a
ret
; -----------------------------------------------------------------------------
; SchedRunFirst - hand control to the first runnable task. Never returns; the
; kernel-init context is saved into wKernelPCB and simply abandoned.
; -----------------------------------------------------------------------------
SchedRunFirst::
ld hl, wKernelPCB
ld a, l
ld [wCurProc], a
ld a, h
ld [wCurProc+1], a
call FindNextReady ; DE = &first ready PCB
jp hSwitchTo
; -----------------------------------------------------------------------------
; SchedYield - cooperative switch to the next runnable task. Returns to the
; caller when this task is eventually rescheduled.
;
; PRESERVES ALL REGISTERS, on both paths. The switch path always did, because
; hSwitchTo saves and restores the full context - but the "nobody else is
; runnable" path used to `ret c` straight out of FindNextReady, which leaves
; HL pointing into wProcTable (via PcbPtr). A caller holding a pointer in HL
; across the call then walked the process table instead of its own variable.
;
; That is what warm-rebooted a link-less gbos every ~11s: with no DHCP server,
; the boot-time `dhcp` spun in net_op_recv's timeout loop and, with the shell
; blocked on tty input, every yield took the no-switch path - so `inc hl /
; inc [hl]` incremented PCB bytes instead of wNetTO until a saved return
; address rolled to $0100 and the cart restarted. It only ever bit with the
; network absent, which is why it hid for so long.
; -----------------------------------------------------------------------------
SchedYield::
push af
push bc
push de
push hl
call FindNextReady ; DE = &next PCB, CF if none runnable
jr c, .none ; nobody else ready: keep running current
call hSwitchTo ; (returns here when we are rescheduled; the
; pushes above sit on our own stack)
.none
pop hl
pop de
pop bc
pop af
ret
; -----------------------------------------------------------------------------
; FindNextReady - round-robin from wSchedNext.
; out: DE = &PCB and CF clear on success; CF set if nothing is PS_READY.
; -----------------------------------------------------------------------------
FindNextReady::
ld a, [wSchedNext]
ld b, MAX_PROCS
.scan
inc a
cp MAX_PROCS
jr c, .nowrap
xor a
.nowrap
push af ; save candidate slot
call PcbPtr ; DE = &PCB[slot] (preserves BC)
ld a, [de] ; state
cp PS_READY
jr z, .found
pop af ; restore candidate slot
dec b
jr nz, .scan
scf ; nothing runnable
ret
.found
pop af ; A = chosen slot
ld [wSchedNext], a
call PcbPtr ; DE = &PCB
and a ; clear carry
ret
; -----------------------------------------------------------------------------
; TimerISR - the system tick. TAC runs the timer at 16384 Hz with TMA=0 -
; but the CPU is in DOUBLE-SPEED mode, so the input clock is really 32768 Hz
; and this fires at 128 Hz. A toggle halves that back down: wTicks stays a
; monotonic 32-bit 1/64-second counter (SYS_UPTIME reads it; wraps ~2.1 yr).
; Scheduling stays cooperative - this ISR is deliberately transparent (all
; registers preserved, fixed-WRAM only), so it can interrupt anything,
; including kernel code mid-syscall. Preemption would hook in here later.
; -----------------------------------------------------------------------------
TimerISR::
push af
push hl
ld hl, wTickTog
ld a, [hl]
xor 1
ld [hl], a
jr nz, .done ; first IRQ of the 128 Hz pair: count nothing
ld hl, wTicks
inc [hl]
jr nz, .done ; no carry out of byte 0
inc hl
inc [hl]
jr nz, .done
inc hl
inc [hl]
jr nz, .done
inc hl
inc [hl]
.done
pop hl
pop af
reti
|