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