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