Code: Select all
_RTC_init:
;-----------------------------------------------------------------------------
; _RTC_init - Initialize VIA2 Timer 1 for 100Hz NMI system tick
;-----------------------------------------------------------------------------
; Generates NMI interrupt every 10ms (100Hz) at 4MHz CPU clock
; Timer calculation: 4,000,000 / 100 = 40,000 cycles per tick
; Timer value: 40,000 - 2 = 39,998 = $9C3E
; Clobbers: .A
;-----------------------------------------------------------------------------
; Load timer with 39,998 ($9C3E)
LDA #$3E ; Low byte
STA VIA2_T1CL ; Write to counter low (loads latch)
LDA #$9C ; High byte
STA VIA2_T1CH ; Write to counter high (starts timer)
; Configure Timer 1 for continuous interrupts
LDA VIA2_ACR
AND #$7F ; Clear bit 7 (disable T1 output on PB7)
ORA #$40 ; Set bit 6 (T1 continuous mode)
STA VIA2_ACR
; Enable Timer 1 interrupt
LDA #$C0 ; Set enable (bit 7) + T1 interrupt (bit 6)
STA VIA2_IER
RTS ; _RTC_init
Code: Select all
;-----------------------------------------------------------------------------
; Process Control Block (PCB) Structure - 16 bytes
;-----------------------------------------------------------------------------
;
;-----------------------------------------------------------------------------
PID = $00 ; Process ID ($0 - $F)
PSTAT = $01 ; Process Status (BLOCK | READY | RUN)
QUANTUM = $02 ; Time slices remaining before context switch
STACKBASE = $03 ; Pointer to buffer for stack preservation
; $04 ; 16 bits
STACK_PTR = $05 ; Save stack pointer here
; $06 - $0F available for other needed fields
;-----------------------------------------------------------------------------
; Process States
;-----------------------------------------------------------------------------
NULL = %00000000 ; $00 - Only PID 0 should ever be NULL
FREE = %00000001 ; $01 - Indicates currently unused PCB
; = %00000010 ; $02
; = %00000100 ; $04
; = %00001000 ; $08
; = %00010000 ; $10
BLOCK = %00100000 ; $20 - Process waiting on I/O or other
READY = %01000000 ; $40 - Process ready to run
RUN = %10000000 ; $80 - Currently running process
; Zero Page
; $0000 - $007F Reserved for parameter stack
; Kernel data
sys_ticks = $80 ; 4 bytes - 32-bit system tick counter
; $81
; $82
; $83
current_pid = $84
; Pseudoregisters (16 bit)
R1 = $FC
; - $FD
R0 = $FE
; - $FF
; Process Table - statically located, contains 16 Process Control Blocks
PROCTAB = $0200
; - $02FF
Code: Select all
_SYS_mon:
;-----------------------------------------------------------------------------
; _SYS_mon - NMI interrupt handler (system tick from VIA2 Timer 1)
;-----------------------------------------------------------------------------
; Called every 10ms (100Hz) for preemptive multitasking
; Must be fast and non-reentrant safe
; Clobbers: None (all registers saved/restored)
;-----------------------------------------------------------------------------
; Save registers (PC and P already pushed by NMI)
PHA ; Save A
PHX ; Save X (65C02)
PHY ; Save Y (65C02)
; Clear VIA2 Timer 1 interrupt flag
BIT VIA2_T1CL ; Reading T1CL clears IFR bit 6
; Increment 32-bit system tick counter
INC sys_ticks
BNE .scheduler
INC sys_ticks + 1
BNE .scheduler
INC sys_ticks + 2
BNE .scheduler
INC sys_ticks + 3
.scheduler:
; Process Scheduler
LDA current_pid ; Get current PID
ASL A ; x2
ASL A ; x4
ASL A ; x8
ASL A ; x16
ORA #QUANTUM
TAX
LDA PROCTAB,X ; Check Quantum
BEQ .do_switch ; Is it zero?
DEC PROCTAB,X ; No, decrement it
JMP .exit ; And go back to what we were doing
; Otherwise, QUANTUM is 0, perform context switch
.do_switch: ; Step 1: update current process PSTAT and QUANTUM ---
LDA current_pid
ASL
ASL
ASL
ASL
ORA #PSTAT
TAX
LDA PROCTAB,X ; .A now contains PSTAT byte
BPL .not_run ; If Bit 7 is clear, we're NOT RUNNING
; If Bit 7 is set, we *are* RUNNING, so we want to change state to READY
LSR PROCTAB,X ; set PSTAT to READY
LDA #$04
STA PROCTAB+1,X ; reset QUANTUM (It's the next field up from PSTAT)
.not_run:
; Don't touch PSTAT, or QUANTUM, but do save stacks and switch.
; Save Stack Pointer at this point
LDA current_pid
ASL
ASL
ASL
ASL
ORA #STACK_PTR
TAY ; Y = PCB offset
TSX
TXA ; A = Stack Pointer
STA PROCTAB,Y
; Step 2 - Transfer STACKBASE into R0
LDA current_pid
ASL
ASL
ASL
ASL
ORA #STACKBASE
TAX
LDA PROCTAB,X ; Get low byte of STACKBASE
STA R0 ; Save it in pseudoregister
INX ; Get high byte of STACKBASE
LDA PROCTAB,X
STA R0+1 ; Save it in pseudoregister
; Step 3 - Save hardware stack
LDY #$00
.save_hw_stack: ; 256 Bytes
LDA $0100,Y
STA (R0),Y
INY
BNE .save_hw_stack
; Step 4 - Save data stack
INC R0+1 ; Data stack will be saved on next page
LDY #$7F
.save_data_stack: ; 128 Bytes
LDA $00,Y
STA (R0),Y
DEY
BPL .save_data_stack
; Step 4 - Locate next ready process
.next_proc: LDA current_pid
INA ; look at next process
AND #%00001111 ; Only 16 processes
STA current_pid
ASL
ASL
ASL
ASL
ORA #PSTAT
TAX
BIT PROCTAB,X ; Check status
BVC .next_proc ; If bit 6 = 0, process NOT READY
; DANGER - if no process is ready, we will sit here forever
; If the NULL process is always ready to run, this is no problem
; But if we don't check the NULL process last it might sometimes run
; when other processes are ready. Thought: we could always scan the
; process table from top to bottom. This would make the PID work with
; the quantum to provide a kind of priority.
; At this point, current_pid is a runnable process.
; Step 5 - Mark it runnable
LDA #RUN
STA PROCTAB,X ; PCSTAT is still selected
; Restore Stack Pointer here
LDA current_pid
ASL
ASL
ASL
ASL
ORA #STACK_PTR
TAX
LDA PROCTAB,X ; Get new processs's stack pointer
TAX
TXS ; Restore it
; Step 6 - Transfer STACKBASE into R0
LDA current_pid
ASL
ASL
ASL
ASL
ORA #STACKBASE
TAX
LDA PROCTAB,X ; Get low byte of STACKBASE
STA R0 ; Save it in pseudoregister
INX ; Get high byte of STACKBASE
LDA PROCTAB,X
STA R0+1 ; Save it in pseudoregister
; Step 7 - Restore hardware stack
LDY #$00
.restore_hw_stack: ; 256 Bytes
LDA (R0),Y
STA $0100,Y
INY
BNE .restore_hw_stack
; Step 8 - Restore data stack
INC R0+1 ; Data stack will be saved on next page
LDY #$7F
.restore_data_stack: ; 128 Bytes
LDA (R0),Y
STA $00,Y
DEY
BPL .restore_data_stack
; Restore registers
.exit:
PLY ; Restore Y
PLX ; Restore X
PLA ; Restore A
RTI ; _SYS_mon