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 lda $fc ; obtain the original value of A 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 176 ; reference the stack location and compute PC MSB & $80 177 lda (SP), y 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 #1 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 #1 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 sta $fc ; override the default handler's restoration of A 286 jmp (old_handler) 287 288 289 290 ; location of previous interrupt handler 291 292 old_handler: .word 0 293 294 ; offset of current task in table (in multiples of 2) 295 296 task_offset: .byte 0 297 298 ; task table containing locations of each task 299 300 tasks: 301 .word 0 302 .word 0 303 .word 0 304 .word 0 305 .word 0 ; last entry 306 end_handler: 307 .word main_task ; end handler entry 308 309 310 311 ; add a new task to the table 312 ; 313 ; ARG0, ARG0H: location of task structure 314 ; affects: A, Y 315 ; returns: 0 if successful, 1 if unsuccessful 316 317 new_task: 318 319 ; check the MSB of each task table entry 320 321 ldy #1 322 323 new_task_check: 324 lda tasks, y 325 cmp #0 326 beq new_task_add 327 iny 328 iny 329 330 ; if no space is found by the end of the table, exit 331 332 cpy #TASK_TABLE_LENGTH 333 bpl new_task_failed 334 jmp new_task_check 335 336 new_task_add: 337 338 ; copy the task structure location to the table 339 340 sei 341 lda ARG0H 342 sta tasks, y 343 dey 344 lda ARG0 345 sta tasks, y 346 cli 347 348 lda #0 349 .invoke return 350 351 new_task_failed: 352 lda #1 353 .invoke return 354 355 356 357 ; remove a task from the table 358 ; 359 ; A: task offset 360 ; affects: A, Y 361 362 remove_task: 363 364 ; zero out the table entry 365 366 tay 367 sei 368 lda #0 369 sta tasks, y 370 iny 371 sta tasks, y 372 cli 373 .invoke return 374 375 376 377 ; example tasks 378 379 first_task: 380 .word 0 ; user stack pointer 381 .byte 0 ; Y 382 .byte 0 ; X 383 .byte 0 ; A 384 .byte 0 ; saved flags 385 .word first_task_start ; saved PC 386 first_task_start: 387 lda #1 388 sta $7000 389 lda #0 390 sta $7001 391 first_task_continue: 392 clc 393 lda $7000 394 adc #1 395 sta $7000 396 lda $7001 397 adc #0 398 sta $7001 399 jmp first_task_continue 400 401 402 403 second_task: 404 .word 0 ; user stack pointer 405 .byte 0 ; Y 406 .byte 0 ; X 407 .byte 0 ; A 408 .byte 0 ; saved flags 409 .word second_task_start ; saved PC 410 second_task_start: 411 lda #1 412 sta $7010 413 lda #0 414 sta $7011 415 second_task_continue: 416 clc 417 lda $7010 418 adc #1 419 sta $7010 420 lda $7011 421 adc #0 422 sta $7011 423 jmp second_task_continue 424 425 426 427 third_task: 428 .word third_task_stack_base ; user stack pointer 429 .byte 0 ; Y 430 .byte 0 ; X 431 .byte 0 ; A 432 .byte 0 ; saved flags 433 .word third_task_start ; saved PC 434 third_task_start: 435 ldx #2 ; offset of first task to remove 436 * stx $7028 437 .invoke call third_task_delay, + 438 * txa 439 .invoke call remove_task, + 440 * inx ; move to next offset 441 inx ; which is 2 locations away 442 cpx #TASK_TABLE_LENGTH 443 beq + 444 jmp --- 445 * ldx #0 446 jmp ---- 447 third_task_delay: 448 lda #0 ; reset counter MSB 449 * sta $7021 450 * ldy #0 451 * sty $7020 452 cpy #$ff 453 beq + 454 iny 455 jmp - 456 * clc 457 lda $7021 ; next MSB 458 adc #1 459 sta $7021 460 cmp #$ff 461 beq + 462 jmp --- 463 * .invoke return 464 third_task_stack: 465 .word 0 466 .word 0 467 .word 0 468 .word 0 469 .word 0 470 third_task_stack_base: 471 .word 0 472 473 ; vim: tabstop=4 expandtab shiftwidth=4