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