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