1 ; A task switcher for the Acorn Electron. 2 3 ; Copyright (C) 2015 Paul Boddie <paul@boddie.org.uk> 4 5 ; This program is free software; you can redistribute it and/or modify it under 6 ; the terms of the GNU General Public License as published by the Free Software 7 ; Foundation; either version 3 of the License, or (at your option) any later 8 ; version. 9 10 ; This program is distributed in the hope that it will be useful, but WITHOUT 11 ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 ; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 ; details. 14 15 ; You should have received a copy of the GNU General Public License along with 16 ; this program. If not, see <http://www.gnu.org/licenses/>. 17 18 .include "macros.oph" 19 20 .alias IRQ1V $204 21 22 .alias SP $70 23 .alias SPH $71 24 .alias NEXT $72 25 .alias NEXTH $73 26 .alias CURRENT $74 27 .alias CURRENTH $75 28 .alias USER $76 29 .alias USERH $77 30 .alias TEMP $78 31 .alias ARG0 $80 32 .alias ARG0H $81 33 .alias ARG1 $82 34 .alias ARG1H $83 35 36 .alias TASK_TABLE_LENGTH 10 37 38 ; "user space" stack for the main program where the invocations might be 39 ; interrupted and where the CPU stack might be disrupted 40 41 .alias main_stack $1ffc 42 .alias ABSTEMP $1ffe 43 44 45 46 .org $2000 47 .text 48 49 50 51 ; main program, installing the handler and adding example tasks 52 53 main: 54 .invoke store16 tasks, ARG1 55 .invoke store16 main_stack, USER 56 .invoke store16 first_task, ARG0 57 .invoke call new_task, + 58 * .invoke store16 second_task, ARG0 59 .invoke call new_task, + 60 * .invoke store16 third_task, ARG0 61 .invoke call new_task, + 62 * jsr install_handler 63 wait: 64 jmp wait ; wait for the switcher to take over 65 66 67 68 ; install the interrupt handler address in IRQ1V 69 ; 70 ; affects: A 71 72 install_handler: 73 sei 74 .invoke mov16 IRQ1V, old_handler 75 .invoke store16 handler, IRQ1V 76 cli 77 rts 78 79 80 81 ; handle interrupts 82 ; 83 ; affects: (temporary stack usage) 84 85 handler: 86 pha ; A -> stack 87 txa 88 pha ; X -> stack 89 tsx ; capture the stack pointer now for later use 90 ; (stack is <empty>, X, A, F, LSB, MSB) 91 tya 92 pha ; Y -> stack 93 94 ; save zero-page locations used in the handler 95 96 .invoke push16 SP 97 .invoke push16 NEXT 98 .invoke push16 CURRENT 99 100 init_sp: 101 102 ; initialise the stack frame pointer 103 104 txa 105 sta SP 106 lda #$01 ; $01xx 107 sta SPH 108 109 test_pc: 110 111 ; test PC for execution of ROM routines 112 ; these are probably not re-entrant 113 114 ; obtain the stack location of the stored PC MSB 115 116 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 117 lda (SP), y 118 119 ; reference the stack location and compute PC MSB & $80 120 and #$80 121 cmp #$80 122 123 ; exit if PC MSB & $80 != 0 124 beq exit_handler 125 126 load_task: 127 128 ; load next task 129 130 ldx task_offset 131 lda tasks, x 132 sta NEXT 133 inx 134 lda tasks, x 135 sta NEXTH 136 inx 137 138 ; reset the task index if necessary 139 140 cpx #TASK_TABLE_LENGTH 141 bne test_task 142 ldx #0 143 144 test_task: 145 stx task_offset 146 147 ; exit if null task 148 149 lda NEXTH 150 cmp #0 151 beq exit_handler 152 153 switch_task: 154 155 ; store flags, PC in current task structure 156 157 .invoke mov16 current_task, CURRENT 158 159 ; store the user stack for the task 160 161 .invoke mov16_to_ref USER, CURRENT 162 163 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 164 .invoke mov8_refs SP, CURRENT 165 dey 166 .invoke mov8_refs SP, CURRENT 167 dey 168 .invoke mov8_refs SP, CURRENT 169 170 ; load flags, PC from next task structure 171 172 .invoke mov8_refs NEXT, SP 173 iny 174 .invoke mov8_refs NEXT, SP 175 iny 176 .invoke mov8_refs NEXT, SP 177 iny 178 179 ; make the next task the current one 180 181 .invoke mov16 NEXT, current_task 182 183 ; set the user stack for the task 184 185 .invoke mov16_from_ref NEXT, USER 186 187 exit_handler: 188 189 ; restore zero-page locations used in the handler 190 191 .invoke pull16 CURRENT 192 .invoke pull16 NEXT 193 .invoke pull16 SP 194 195 pla 196 tay ; stack -> Y 197 pla 198 tax ; stack -> X 199 pla ; stack -> A 200 jmp (old_handler) 201 202 203 204 ; location of previous interrupt handler 205 206 old_handler: .word 0 207 208 ; location of current task (duplicated from the table) 209 210 current_task: .word 0 211 212 ; offset of current task in table (in multiples of 2) 213 214 task_offset: .byte 0 215 216 ; task table containing locations of each task 217 218 tasks: 219 .word 0 220 .word 0 221 .word 0 222 .word 0 223 .word 0 224 225 226 227 ; add a new task to the table 228 ; 229 ; ARG0, ARG0H: location of task structure 230 ; affects: A, Y 231 ; returns: 0 if successful, 1 if unsuccessful 232 233 new_task: 234 235 ; check the MSB of each task table entry 236 237 ldy #1 238 239 check_new_task: 240 lda tasks, y 241 cmp #0 242 beq add_new_task 243 iny 244 iny 245 246 ; if no space is found by the end of the table, exit 247 248 cpy #TASK_TABLE_LENGTH 249 bpl no_new_task 250 jmp check_new_task 251 252 add_new_task: 253 254 ; copy the task structure location to the table 255 256 sei 257 lda ARG0H 258 sta tasks, y 259 dey 260 lda ARG0 261 sta tasks, y 262 cli 263 264 lda #0 265 .invoke return 266 267 no_new_task: 268 lda #1 269 .invoke return 270 271 272 273 ; remove a task from the table 274 ; 275 ; A: task offset 276 ; affects: A, Y 277 278 remove_task: 279 280 ; zero out the table entry 281 282 tay 283 sei 284 lda #0 285 sta tasks, y 286 iny 287 sta tasks, y 288 cli 289 .invoke return 290 291 292 293 ; example tasks 294 295 first_task: 296 .word 0 ; user stack pointer 297 .byte 0 ; currently unused 298 .byte 0 ; saved flags 299 .word first_task_start ; saved PC 300 first_task_start: 301 lda #1 302 first_task_continue: 303 sta $7000 304 adc #1 305 jmp first_task_continue 306 307 308 309 second_task: 310 .word 0 ; user stack pointer 311 .byte 0 ; currently unused 312 .byte 0 ; saved flags 313 .word second_task_start ; saved PC 314 second_task_start: 315 lda #1 316 second_task_continue: 317 sta $7008 318 adc #1 319 jmp second_task_continue 320 321 322 323 third_task: 324 .word third_task_stack_base ; user stack pointer 325 .byte 0 ; currently unused 326 .byte 0 ; saved flags 327 .word third_task_start ; saved PC 328 third_task_start: 329 ldx #0 330 _begin: 331 ldy #0 ; reset counter LSB 332 lda #0 ; reset counter MSB 333 sta $7011 334 _loop: 335 sty $7010 336 cpy #$ff 337 bne _continue 338 clc 339 lda $7011 ; next MSB 340 adc #1 341 sta $7011 342 cmp #$ff 343 bne _continue 344 txa 345 .invoke pushA 346 .invoke call remove_task, + 347 * .invoke pullA 348 tax 349 inx ; move to next offset 350 inx ; which is 2 locations away 351 jmp _begin 352 _continue: 353 iny 354 jmp _loop 355 third_task_stack: 356 .word 0 357 .word 0 358 .word 0 359 .word 0 360 .word 0 361 third_task_stack_base: 362 .word 0 363 364 ; vim: tabstop=4 expandtab shiftwidth=4