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 $1fff 42 43 44 45 .org $2000 46 .text 47 48 49 50 ; main program, installing the handler and adding example tasks 51 52 main: 53 jsr install_handler 54 .invoke store16 tasks, ARG1 55 .invoke store16 main_stack, USER 56 .invoke store16 first_task, ARG0 57 .invoke call new_task 58 .invoke store16 second_task, ARG0 59 .invoke call new_task 60 .invoke store16 third_task, ARG0 61 .invoke call new_task 62 wait: 63 jmp wait ; wait for the switcher to take over 64 65 66 67 ; move CPU stack data to the "user space" stack 68 ; 69 ; affects: USER (gains LSB, MSB) 70 71 copy_to_user_stack: 72 pha ; A -> stack 73 txa 74 pha ; X -> stack 75 tsx ; capture the stack pointer now for later use 76 ; (stack is Y, X, A, LSB, MSB) 77 tya 78 pha ; Y -> stack 79 80 ; save zero-page locations used 81 82 .invoke push16 SP 83 84 ; initialise the stack frame pointer 85 86 txa 87 sta SP 88 lda #$01 ; $01xx 89 sta SPH 90 91 ; allocate "user space" stack space to be compatible with the CPU stack 92 93 .invoke sub16 5, USER 94 95 ; copy the PC, updated by 4 (see the call macro) 96 97 ldy #3 98 clc 99 lda (SP), y 100 adc #4 101 sta (USER), y 102 iny 103 lda (SP), y 104 adc #0 105 sta (USER), y 106 107 ; correct "user space" stack 108 109 .invoke add16 3, USER 110 111 ; restore zero-page locations used 112 113 .invoke pull16 SP 114 115 ; return to the caller 116 117 pla 118 tay ; stack -> Y 119 pla 120 tax ; stack -> X 121 pla ; stack -> A 122 rts 123 124 125 126 ; move CPU stack data from the "user space" stack 127 ; 128 ; affects: USER (loses LSB, MSB) 129 130 copy_from_user_stack: 131 pha ; padding 132 pha ; padding 133 pha ; A -> stack 134 txa 135 pha ; X -> stack 136 tsx ; capture the stack pointer now for later use 137 ; (stack is Y, X, A, LSB, MSB) 138 tya 139 pha ; Y -> stack 140 141 ; save zero-page locations used 142 143 .invoke push16 SP 144 145 ; initialise the stack frame pointer 146 147 txa 148 sta SP 149 lda #$01 ; $01xx 150 sta SPH 151 152 ; adjust the "user space" stack pointer to be compatible with the PC stack 153 154 .invoke sub16 3, USER 155 156 ; copy the PC 157 158 ldy #3 159 .invoke mov8_refs USER, SP 160 iny 161 .invoke mov8_refs USER, SP 162 163 ; update the "user space" stack pointer 164 165 .invoke add16 5, USER 166 167 ; restore zero-page locations used 168 169 .invoke pull16 SP 170 171 ; return to the stored PC 172 173 pla 174 tay ; stack -> Y 175 pla 176 tax ; stack -> X 177 pla ; stack -> A 178 rts 179 180 181 182 ; install the interrupt handler address in IRQ1V 183 ; 184 ; affects: A 185 186 install_handler: 187 sei 188 .invoke mov16 IRQ1V, old_handler 189 .invoke store16 handler, IRQ1V 190 cli 191 rts 192 193 194 195 ; handle interrupts 196 ; 197 ; affects: (temporary stack usage) 198 199 handler: 200 pha ; A -> stack 201 txa 202 pha ; X -> stack 203 tsx ; capture the stack pointer now for later use 204 ; (stack is <empty>, X, A, F, LSB, MSB) 205 tya 206 pha ; Y -> stack 207 208 ; save zero-page locations used in the handler 209 210 .invoke push16 SP 211 .invoke push16 NEXT 212 .invoke push16 CURRENT 213 214 init_sp: 215 216 ; initialise the stack frame pointer 217 218 txa 219 sta SP 220 lda #$01 ; $01xx 221 sta SPH 222 223 test_pc: 224 225 ; test PC for execution of ROM routines 226 ; these are probably not re-entrant 227 228 ; obtain the stack location of the stored PC MSB 229 230 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 231 lda (SP), y 232 233 ; reference the stack location and compute PC MSB & $80 234 and #$80 235 cmp #$80 236 237 ; exit if PC MSB & $80 != 0 238 beq exit_handler 239 240 load_task: 241 242 ; load next task 243 244 ldx task_offset 245 lda tasks, x 246 sta NEXT 247 inx 248 lda tasks, x 249 sta NEXTH 250 inx 251 252 ; reset the task index if necessary 253 254 cpx #TASK_TABLE_LENGTH 255 bne test_task 256 ldx #0 257 258 test_task: 259 stx task_offset 260 261 ; exit if null task 262 263 lda NEXTH 264 cmp #0 265 beq exit_handler 266 267 switch_task: 268 269 ; store flags, PC in current task structure 270 271 .invoke mov16 current_task, CURRENT 272 273 ; store the user stack for the task 274 275 .invoke mov16_to_ref USER, CURRENT 276 277 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 278 .invoke mov8_refs SP, CURRENT 279 dey 280 .invoke mov8_refs SP, CURRENT 281 dey 282 .invoke mov8_refs SP, CURRENT 283 284 ; load flags, PC from next task structure 285 286 .invoke mov8_refs NEXT, SP 287 iny 288 .invoke mov8_refs NEXT, SP 289 iny 290 .invoke mov8_refs NEXT, SP 291 iny 292 293 ; make the next task the current one 294 295 .invoke mov16 NEXT, current_task 296 297 ; set the user stack for the task 298 299 .invoke mov16_from_ref NEXT, USER 300 301 exit_handler: 302 303 ; restore zero-page locations used in the handler 304 305 .invoke pull16 CURRENT 306 .invoke pull16 NEXT 307 .invoke pull16 SP 308 309 pla 310 tay ; stack -> Y 311 pla 312 tax ; stack -> X 313 pla ; stack -> A 314 jmp (old_handler) 315 316 317 318 ; location of previous interrupt handler 319 320 old_handler: .word 0 321 322 ; location of current task (duplicated from the table) 323 324 current_task: .word 0 325 326 ; offset of current task in table (in multiples of 2) 327 328 task_offset: .byte 0 329 330 ; task table containing locations of each task 331 332 tasks: 333 .word 0 334 .word 0 335 .word 0 336 .word 0 337 .word 0 338 339 340 341 ; add a new task to the table 342 ; 343 ; ARG0, ARG0H: location of task structure 344 ; affects: A, Y 345 346 new_task: 347 348 ; check the MSB of each task table entry 349 350 ldy #1 351 352 check_new_task: 353 lda tasks, y 354 cmp #0 355 beq add_new_task 356 iny 357 iny 358 359 ; if no space is found by the end of the table, exit 360 361 cpy #TASK_TABLE_LENGTH 362 bpl no_new_task 363 jmp check_new_task 364 365 add_new_task: 366 367 ; copy the task structure location to the table 368 369 sei 370 lda ARG0H 371 sta tasks, y 372 dey 373 lda ARG0 374 sta tasks, y 375 cli 376 377 .invoke return 378 379 no_new_task: 380 .invoke return 381 382 383 384 ; remove a task from the table 385 ; 386 ; A: task offset 387 ; affects: A, Y 388 389 remove_task: 390 391 ; zero out the table entry 392 393 tay 394 sei 395 lda #0 396 sta tasks, y 397 iny 398 sta tasks, y 399 cli 400 .invoke return 401 402 403 404 ; example tasks 405 406 first_task: 407 .word 0 ; user stack pointer 408 .byte 0 ; currently unused 409 .byte 0 ; saved flags 410 .word first_task_start ; saved PC 411 first_task_start: 412 lda #1 413 first_task_continue: 414 sta $7000 415 adc #1 416 jmp first_task_continue 417 418 419 420 second_task: 421 .word 0 ; user stack pointer 422 .byte 0 ; currently unused 423 .byte 0 ; saved flags 424 .word second_task_start ; saved PC 425 second_task_start: 426 lda #1 427 second_task_continue: 428 sta $7008 429 adc #1 430 jmp second_task_continue 431 432 433 434 third_task: 435 .word third_task_stack_base ; user stack pointer 436 .byte 0 ; currently unused 437 .byte 0 ; saved flags 438 .word third_task_start ; saved PC 439 third_task_start: 440 ldx #0 441 ldy #0 442 _loop: 443 cpy #$ff 444 bne _continue 445 inc $7011 446 lda $7011 447 cmp #$ff 448 bne _continue 449 tya 450 .invoke pushA 451 txa 452 .invoke call remove_task 453 .invoke pullA 454 tay 455 inx 456 _continue: 457 iny 458 sty $7010 459 jmp _loop 460 third_task_stack: 461 .word 0 462 .word 0 463 .word 0 464 .word 0 465 .word 0 466 .word 0 467 third_task_stack_base: 468 469 ; vim: tabstop=4 expandtab shiftwidth=4