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 29 .alias TASK_TABLE_LENGTH 10 30 31 .org $2000 32 .text 33 main: 34 jsr install_handler 35 .invoke store16 first_task, $76 36 jsr new_task 37 .invoke store16 second_task, $76 38 jsr new_task 39 .invoke store16 third_task, $76 40 jsr new_task 41 wait: 42 jmp wait ; wait for the switcher to take over 43 44 45 46 ; install the interrupt handler address in IRQ1V 47 ; 48 ; affects: A 49 50 install_handler: 51 sei 52 .invoke mov16 IRQ1V, old_handler 53 .invoke store16 handler, IRQ1V 54 cli 55 rts 56 57 58 59 ; handle interrupts 60 ; 61 ; affects: (temporary stack usage) 62 63 handler: 64 pha ; A -> stack 65 txa 66 pha ; X -> stack 67 tsx ; capture the stack pointer now for later use 68 ; (stack is <empty>, X, A, F, LSB, MSB) 69 tya 70 pha ; Y -> stack 71 72 ; save zero-page locations used in the handler 73 74 .invoke push16 SP 75 .invoke push16 NEXT 76 .invoke push16 CURRENT 77 78 init_sp: 79 80 ; initialise the stack frame pointer 81 82 txa 83 sta SP 84 lda #$01 ; $01xx 85 sta SPH 86 87 test_pc: 88 89 ; test PC for execution of ROM routines 90 ; these are probably not re-entrant 91 92 ; obtain the stack location of the stored PC MSB 93 94 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 95 lda (SP), y 96 97 ; reference the stack location and compute PC MSB & $80 98 and #$80 99 cmp #$80 100 101 ; exit if PC MSB & $80 != 0 102 beq exit_handler 103 104 load_task: 105 106 ; load next task 107 108 ldx task_offset 109 lda tasks, x 110 sta NEXT 111 inx 112 lda tasks, x 113 sta NEXTH 114 inx 115 116 ; reset the task index if necessary 117 118 cpx #TASK_TABLE_LENGTH 119 bne test_task 120 ldx #0 121 122 test_task: 123 stx task_offset 124 125 ; exit if null task 126 127 lda NEXTH 128 cmp #0 129 beq exit_handler 130 131 switch_task: 132 133 ; store flags, PC in current task structure 134 135 .invoke mov16 current_task, CURRENT 136 137 ldy #5 ; offset of MSB (Y, X, A, F, LSB, MSB) 138 lda (SP), y 139 sta (CURRENT), y 140 dey 141 lda (SP), y 142 sta (CURRENT), y 143 dey 144 lda (SP), y 145 sta (CURRENT), y 146 147 ; load flags, PC from next task structure 148 149 lda (NEXT), y 150 sta (SP), y 151 iny 152 lda (NEXT), y 153 sta (SP), y 154 iny 155 lda (NEXT), y 156 sta (SP), y 157 iny 158 159 ; make the next task the current one 160 161 .invoke mov16 NEXT, current_task 162 163 exit_handler: 164 165 ; restore zero-page locations used in the handler 166 167 .invoke pull16 CURRENT 168 .invoke pull16 NEXT 169 .invoke pull16 SP 170 171 pla 172 tay ; stack -> Y 173 pla 174 tax ; stack -> X 175 pla ; stack -> A 176 jmp (old_handler) 177 178 179 180 ; location of previous interrupt handler 181 182 old_handler: .word 0 183 184 ; location of current task (duplicated from the table) 185 186 current_task: .word 0 187 188 ; offset of current task in table (in multiples of 2) 189 190 task_offset: .byte 0 191 192 ; task table containing locations of each task 193 194 tasks: 195 .word 0 196 .word 0 197 .word 0 198 .word 0 199 .word 0 200 201 202 203 ; add a new task to the table 204 ; 205 ; $76, $77: location of task structure 206 ; affects: A, Y 207 ; returns: A = 0 (success); A = 1 (failure) 208 209 new_task: 210 211 ; check the MSB of each task table entry 212 213 ldy #1 214 215 check_new_task: 216 lda tasks, y 217 cmp #0 218 beq add_new_task 219 iny 220 iny 221 222 ; if no space is found by the end of the table, exit 223 224 cpy #TASK_TABLE_LENGTH 225 bpl no_new_task 226 jmp check_new_task 227 228 add_new_task: 229 230 ; copy the task structure location to the table 231 232 sei 233 lda $77 234 sta tasks, y 235 dey 236 lda $76 237 sta tasks, y 238 cli 239 240 lda #0 241 rts 242 243 no_new_task: 244 lda #1 245 rts 246 247 248 249 ; remove a task from the table 250 ; 251 ; A: task offset 252 ; affects: A, Y 253 254 remove_task: 255 256 ; zero out the table entry 257 258 tay 259 sei 260 lda #0 261 sta tasks, y 262 iny 263 sta tasks, y 264 cli 265 rts 266 267 268 ; example tasks 269 270 first_task: 271 .byte 0 ; currently unused 272 .byte 0 ; currently unused 273 .byte 0 ; currently unused 274 .byte 0 ; saved flags 275 .word first_task_start ; saved PC 276 first_task_start: 277 .invoke add16 1, $7000 278 jmp first_task_start 279 280 281 282 second_task: 283 .byte 0 ; currently unused 284 .byte 0 ; currently unused 285 .byte 0 ; currently unused 286 .byte 0 ; saved flags 287 .word second_task_start ; saved PC 288 second_task_start: 289 .invoke add16 1, $7008 290 jmp second_task_start 291 292 293 294 third_task: 295 .byte 0 ; currently unused 296 .byte 0 ; currently unused 297 .byte 0 ; currently unused 298 .byte 0 ; saved flags 299 .word third_task_start ; saved PC 300 third_task_start: 301 .invoke store16 0, $7010 302 ldx #0 303 _loop: 304 lda $7010 305 cmp #$ff 306 bne _continue 307 lda $7011 308 cmp #$ff 309 bne _continue 310 txa 311 jsr remove_task 312 inx 313 _continue: 314 .invoke add16 1, $7010 315 jmp _loop 316 317 ; vim: tabstop=4 expandtab shiftwidth=4