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_base $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 ; for debugging 55 56 ; initialise a user stack and add this program as a task 57 58 .invoke store16 main_stack_base, USER 59 .invoke store16 main_task, ARG0 60 .invoke call new_task, + 61 62 ; install the interrupt handler 63 64 * jsr install_handler 65 66 ; perform task installation 67 68 .invoke store16 first_task, ARG0 69 .invoke call new_task, + 70 * .invoke store16 second_task, ARG0 71 .invoke call new_task, + 72 * .invoke store16 third_task, ARG0 73 .invoke call new_task, wait 74 75 ; loop while other tasks exist 76 77 wait: 78 ldx #7 79 sei 80 lda tasks, x ; check for the third task 81 cli 82 cmp #0 83 bne wait 84 85 ; where no other tasks exist, remove this task by updating its PC to the 86 ; end label and removing its entry 87 88 .invoke store16 end, main_task+6 89 lda #0 90 .invoke call remove_task, shutdown 91 92 ; wait for shutdown 93 94 shutdown: 95 jmp shutdown 96 97 ; return here with the initial main program stack 98 99 end: 100 rts 101 102 103 104 ; main program task structure 105 106 main_task: 107 .word main_stack_base ; user stack pointer 108 .byte 0 ; Y 109 .byte 0 ; X 110 .byte 0 ; A 111 .byte 0 ; saved flags 112 .word 0 ; saved PC 113 114 115 116 ; install the interrupt handler address in IRQ1V 117 ; 118 ; affects: A 119 120 install_handler: 121 sei 122 .invoke mov16 IRQ1V, old_handler 123 .invoke store16 handler, IRQ1V 124 cli 125 rts 126 127 128 129 ; handle interrupts 130 ; 131 ; affects: (temporary stack usage) 132 133 handler: 134 pha ; A -> stack 135 txa 136 pha ; X -> stack 137 tya 138 pha ; Y -> stack 139 140 tsx ; capture the stack pointer now for later use 141 ; (stack is <empty>, Y, X, A, F, LSB, MSB) 142 143 ; save zero-page locations used in the handler 144 145 .invoke push16 SP 146 .invoke push16 NEXT 147 .invoke push16 CURRENT 148 149 init_sp: 150 151 ; initialise the stack frame pointer 152 153 dex ; make the frame compatible with the task structure 154 txa 155 sta SP 156 lda #$01 ; $01xx 157 sta SPH 158 159 test_pc: 160 161 ; test PC for execution of ROM routines 162 ; these are probably not re-entrant 163 164 ; obtain the stack location of the stored PC MSB 165 166 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 167 lda (SP), y 168 169 ; reference the stack location and compute PC MSB & $80 170 and #$80 171 cmp #$80 172 173 ; exit if PC MSB & $80 != 0 174 beq exit_handler 175 176 evict_task: 177 178 ; obtain the current task using the task offset 179 180 ldx task_offset 181 lda tasks, x 182 sta CURRENT 183 inx 184 lda tasks, x 185 sta CURRENTH 186 dex 187 188 ; store the user stack for the task 189 190 .invoke mov16_to_ref USER, CURRENT 191 192 ; store registers, flags, PC in current task structure 193 194 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 195 * .invoke mov8_refs SP, CURRENT 196 dey 197 cpy #2 198 bpl - 199 200 load_task: 201 202 ; examine the next task 203 204 inx 205 inx 206 207 ; reset the task index if necessary 208 209 cpx #TASK_TABLE_LENGTH 210 bne test_task 211 ldx #0 212 213 test_task: 214 215 ; obtain task location MSB 216 217 inx 218 lda tasks, x 219 sta NEXTH 220 221 ; install if not a null task (tasks do not reside in zero page) 222 223 cmp #0 224 bne restore_task 225 226 ; test against the start index 227 ; where a complete circuit of the task table has not been performed, 228 ; continue 229 230 dex 231 txa 232 cmp task_offset 233 bne load_task 234 235 ; where a complete circuit has been performed without finding any tasks 236 ; uninstall the handler and install the details of the end handler 237 238 sei 239 .invoke mov16 old_handler, IRQ1V 240 cli 241 242 ; the end handler resides at the end of the task table 243 244 ldx #TASK_TABLE_LENGTH 245 inx 246 lda tasks, x 247 sta NEXTH 248 249 restore_task: 250 251 dex 252 lda tasks, x 253 sta NEXT 254 stx task_offset 255 256 ; load registers, flags, PC from next task structure 257 258 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 259 * .invoke mov8_refs NEXT, SP 260 dey 261 cpy #2 262 bpl - 263 264 ; set the user stack for the task 265 266 .invoke mov16_from_ref NEXT, USER 267 268 exit_handler: 269 270 ; restore zero-page locations used in the handler 271 272 .invoke pull16 CURRENT 273 .invoke pull16 NEXT 274 .invoke pull16 SP 275 276 pla 277 tay ; stack -> Y 278 pla 279 tax ; stack -> X 280 pla ; stack -> A 281 jmp (old_handler) 282 283 284 285 ; location of previous interrupt handler 286 287 old_handler: .word 0 288 289 ; offset of current task in table (in multiples of 2) 290 291 task_offset: .byte 0 292 293 ; task table containing locations of each task 294 295 tasks: 296 .word 0 297 .word 0 298 .word 0 299 .word 0 300 .word 0 ; last entry 301 end_handler: 302 .word main_task ; end handler entry 303 304 305 306 ; add a new task to the table 307 ; 308 ; ARG0, ARG0H: location of task structure 309 ; affects: A, Y 310 ; returns: 0 if successful, 1 if unsuccessful 311 312 new_task: 313 314 ; check the MSB of each task table entry 315 316 ldy #1 317 318 check_new_task: 319 lda tasks, y 320 cmp #0 321 beq add_new_task 322 iny 323 iny 324 325 ; if no space is found by the end of the table, exit 326 327 cpy #TASK_TABLE_LENGTH 328 bpl no_new_task 329 jmp check_new_task 330 331 add_new_task: 332 333 ; copy the task structure location to the table 334 335 sei 336 lda ARG0H 337 sta tasks, y 338 dey 339 lda ARG0 340 sta tasks, y 341 cli 342 343 lda #0 344 .invoke return 345 346 no_new_task: 347 lda #1 348 .invoke return 349 350 351 352 ; remove a task from the table 353 ; 354 ; A: task offset 355 ; affects: A, Y 356 357 remove_task: 358 359 ; zero out the table entry 360 361 tay 362 sei 363 lda #0 364 sta tasks, y 365 iny 366 sta tasks, y 367 cli 368 .invoke return 369 370 371 372 ; example tasks 373 374 first_task: 375 .word 0 ; user stack pointer 376 .byte 0 ; Y 377 .byte 0 ; X 378 .byte 0 ; A 379 .byte 0 ; saved flags 380 .word first_task_start ; saved PC 381 first_task_start: 382 lda #1 383 sta $7000 384 first_task_continue: 385 inc $7000 386 jmp first_task_continue 387 388 389 390 second_task: 391 .word 0 ; user stack pointer 392 .byte 0 ; Y 393 .byte 0 ; X 394 .byte 0 ; A 395 .byte 0 ; saved flags 396 .word second_task_start ; saved PC 397 second_task_start: 398 lda #1 399 sta $7010 400 second_task_continue: 401 inc $7010 402 jmp second_task_continue 403 404 405 406 third_task: 407 .word third_task_stack_base ; user stack pointer 408 .byte 0 ; Y 409 .byte 0 ; X 410 .byte 0 ; A 411 .byte 0 ; saved flags 412 .word third_task_start ; saved PC 413 third_task_start: 414 ldx #2 415 _begin: 416 ldy #0 ; reset counter LSB 417 lda #0 ; reset counter MSB 418 sta $7021 419 _loop: 420 stx $7028 421 sty $7020 422 cpy #$ff 423 bne _continue 424 clc 425 lda $7021 ; next MSB 426 adc #1 427 sta $7021 428 cmp #$ff 429 bne _continue 430 txa 431 .invoke pushA 432 .invoke call remove_task, + 433 * .invoke pullA 434 tax 435 inx ; move to next offset 436 inx ; which is 2 locations away 437 cpx #TASK_TABLE_LENGTH 438 beq _next 439 jmp _begin 440 _next: 441 ldx #0 442 jmp _begin 443 _continue: 444 iny 445 jmp _loop 446 third_task_stack: 447 .word 0 448 .word 0 449 .word 0 450 .word 0 451 .word 0 452 third_task_stack_base: 453 .word 0 454 455 ; vim: tabstop=4 expandtab shiftwidth=4