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 .invoke push16 TEMP 149 150 init_sp: 151 152 ; initialise the stack frame pointer 153 154 dex ; make the frame compatible with the task structure 155 txa 156 sta SP 157 lda #$01 ; $01xx 158 sta SPH 159 160 test_pc: 161 162 ; test PC for execution of ROM routines 163 ; these are probably not re-entrant 164 165 ; obtain the stack location of the stored PC MSB 166 167 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 168 lda (SP), y 169 170 ; reference the stack location and compute PC MSB & $80 171 and #$80 172 cmp #$80 173 174 ; exit if PC MSB & $80 != 0 175 beq exit_handler 176 177 evict_task: 178 179 ; obtain the current task using the task offset 180 181 ldx task_offset 182 lda tasks, x 183 sta CURRENT 184 inx 185 lda tasks, x 186 sta CURRENTH 187 dex 188 189 ; store the user stack for the task 190 191 .invoke mov16_to_ref USER, CURRENT 192 193 ; store registers, flags, PC in current task structure 194 195 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 196 * .invoke mov8_refs SP, CURRENT 197 dey 198 cpy #2 199 bpl - 200 201 load_task: 202 203 ; examine the next task 204 205 inx 206 inx 207 208 ; reset the task index if necessary 209 210 cpx #TASK_TABLE_LENGTH 211 bne test_task 212 ldx #0 213 214 test_task: 215 216 ; obtain task location MSB 217 218 inx 219 lda tasks, x 220 sta NEXTH 221 222 ; install if not a null task (tasks do not reside in zero page) 223 224 cmp #0 225 bne restore_task 226 227 ; test against the start index 228 ; where a complete circuit of the task table has not been performed, 229 ; continue 230 231 dex 232 txa 233 cmp task_offset 234 bne load_task 235 236 ; where a complete circuit has been performed without finding any tasks 237 ; uninstall the handler and install the details of the end handler 238 239 sei 240 .invoke mov16 old_handler, IRQ1V 241 cli 242 243 ; the end handler resides at the end of the task table 244 245 ldx #TASK_TABLE_LENGTH 246 inx 247 lda tasks, x 248 sta NEXTH 249 250 restore_task: 251 252 dex 253 lda tasks, x 254 sta NEXT 255 stx task_offset 256 257 ; load registers, flags, PC from next task structure 258 259 ldy #7 ; offset of MSB (_, _, Y, X, A, F, LSB, MSB) 260 * .invoke mov8_refs NEXT, SP 261 dey 262 cpy #2 263 bpl - 264 265 ; set the user stack for the task 266 267 .invoke mov16_from_ref NEXT, USER 268 269 exit_handler: 270 271 ; restore zero-page locations used in the handler 272 273 .invoke pull16 TEMP 274 .invoke pull16 CURRENT 275 .invoke pull16 NEXT 276 .invoke pull16 SP 277 278 pla 279 tay ; stack -> Y 280 pla 281 tax ; stack -> X 282 pla ; stack -> A 283 jmp (old_handler) 284 285 286 287 ; location of previous interrupt handler 288 289 old_handler: .word 0 290 291 ; offset of current task in table (in multiples of 2) 292 293 task_offset: .byte 0 294 295 ; task table containing locations of each task 296 297 tasks: 298 .word 0 299 .word 0 300 .word 0 301 .word 0 302 .word 0 ; last entry 303 end_handler: 304 .word main_task ; end handler entry 305 306 307 308 ; add a new task to the table 309 ; 310 ; ARG0, ARG0H: location of task structure 311 ; affects: A, Y 312 ; returns: 0 if successful, 1 if unsuccessful 313 314 new_task: 315 316 ; check the MSB of each task table entry 317 318 ldy #1 319 320 check_new_task: 321 lda tasks, y 322 cmp #0 323 beq add_new_task 324 iny 325 iny 326 327 ; if no space is found by the end of the table, exit 328 329 cpy #TASK_TABLE_LENGTH 330 bpl no_new_task 331 jmp check_new_task 332 333 add_new_task: 334 335 ; copy the task structure location to the table 336 337 sei 338 lda ARG0H 339 sta tasks, y 340 dey 341 lda ARG0 342 sta tasks, y 343 cli 344 345 lda #0 346 .invoke return 347 348 no_new_task: 349 lda #1 350 .invoke return 351 352 353 354 ; remove a task from the table 355 ; 356 ; A: task offset 357 ; affects: A, Y 358 359 remove_task: 360 361 ; zero out the table entry 362 363 tay 364 sei 365 lda #0 366 sta tasks, y 367 iny 368 sta tasks, y 369 cli 370 .invoke return 371 372 373 374 ; example tasks 375 376 first_task: 377 .word 0 ; user stack pointer 378 .byte 0 ; Y 379 .byte 0 ; X 380 .byte 0 ; A 381 .byte 0 ; saved flags 382 .word first_task_start ; saved PC 383 first_task_start: 384 lda #1 385 sta $7000 386 first_task_continue: 387 inc $7000 388 jmp first_task_continue 389 390 391 392 second_task: 393 .word 0 ; user stack pointer 394 .byte 0 ; Y 395 .byte 0 ; X 396 .byte 0 ; A 397 .byte 0 ; saved flags 398 .word second_task_start ; saved PC 399 second_task_start: 400 lda #1 401 sta $7010 402 second_task_continue: 403 inc $7010 404 jmp second_task_continue 405 406 407 408 third_task: 409 .word third_task_stack_base ; user stack pointer 410 .byte 0 ; Y 411 .byte 0 ; X 412 .byte 0 ; A 413 .byte 0 ; saved flags 414 .word third_task_start ; saved PC 415 third_task_start: 416 ldx #2 417 _begin: 418 ldy #0 ; reset counter LSB 419 lda #0 ; reset counter MSB 420 sta $7021 421 _loop: 422 stx $7028 423 sty $7020 424 cpy #$ff 425 bne _continue 426 clc 427 lda $7021 ; next MSB 428 adc #1 429 sta $7021 430 cmp #$ff 431 bne _continue 432 txa 433 .invoke pushA 434 .invoke call remove_task, + 435 * .invoke pullA 436 tax 437 inx ; move to next offset 438 inx ; which is 2 locations away 439 cpx #TASK_TABLE_LENGTH 440 beq _next 441 jmp _begin 442 _next: 443 ldx #0 444 jmp _begin 445 _continue: 446 iny 447 jmp _loop 448 third_task_stack: 449 .word 0 450 .word 0 451 .word 0 452 .word 0 453 .word 0 454 third_task_stack_base: 455 .word 0 456 457 ; vim: tabstop=4 expandtab shiftwidth=4