; async variables somewhere in RAM def vAsyncAF equ ASYNC_VARS_START def vAsyncHL EQU vAsyncAF+2 def vAsyncDE EQU vAsyncHL+2 def vAsyncBC equ vAsyncDE+2 def vAsyncPC equ vAsyncBC+2 def vAsyncNext equ vAsyncPC+2 def vAsyncAfter equ vAsyncNext+2 def vAsyncProgress equ vAsyncAfter+2 def vAsyncMainSP equ vAsyncProgress+2 def vAsyncThreadSP equ vAsyncMainSP+2 ; canonical ordering to push should be: AF, BC, DE, HL, def ASYNC_STACK_TOP equ $ffc0 ; stack top is ffc0 ; first value on the stack is the early return pointer, at ffbe = ffc0-2 ; second value is the destination of the async call, at ffbc = ffbe-2 = ffc0-2-2 def ASYNC_THREAD_CALL equ ASYNC_STACK_TOP - 2 - 2 def SAFE_ASYNC_START EQU 148 def SAFE_ASYNC_END EQU 153 Async_Spawn_HL: di ld a, l ld [ASYNC_THREAD_CALL], a ld a, h ld [ASYNC_THREAD_CALL+1], a Async_Spawn: di nop ; save all the registers push af ld a, l ld [vAsyncHL], a ld a, h ld [vAsyncHL+1], a ld a, e ld [vAsyncDE], a ld a, d ld [vAsyncDE+1], a ld a, c ld [vAsyncBC], a ld a, b ld [vAsyncBC+1], a pop hl ld a, l ld [vAsyncAF], a ld a, h ld [vAsyncAF+1], a ; save main sp ld hl, sp+0 ld a, l ld [vAsyncMainSP], a ld a, h ld [vAsyncMainSP+1], a ; switch to thread sp ld a, LOW(ASYNC_STACK_TOP) ld l, a ld a, HIGH(ASYNC_STACK_TOP) ld h, a ld sp, hl ; push early return onto thread stack ld l, LOW(Async_EarlyReturn) ld h, HIGH(Async_EarlyReturn) push hl ; requestedd pc is expected to be set at ASYNC_THREAD_CALL by the calling func ld hl, sp-2 ld sp, hl ; push registers ; canonical ordering to push should be: AF, BC, DE, HL, ld a, [vAsyncAF] ld l, a ld a, [vAsyncAF+1] ld h, a push hl ld a, [vAsyncBC] ld l, a ld a, [vAsyncBC+1] ld h, a push hl ld a, [vAsyncDE] ld l, a ld a, [vAsyncDE+1] ld h, a push hl ld a, [vAsyncHL] ld l, a ld a, [vAsyncHL+1] ld h, a push hl ; save current sp to vAsyncThreadSP ld hl, sp+0 ld a, l ld [vAsyncThreadSP], a ld a, h ld [vAsyncThreadSP+1], a ; now the stack looks like: early return, ASYNC_THREAD_CALL, AF, BC, DE, HL ; switch back to main thread. ; register enterthread ld a, LOW(Async_EnterThread) ld [INTERRUPT_LCD], a ld a, HIGH(Async_EnterThread) ld [INTERRUPT_LCD + 1], a; set interrupt handler to "ENTER THREAD" ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [rLYC], a ; set LYC ld hl, rIE set 1, [hl] ; enable vblank ld hl, rSTAT set 6, [hl] ; set the stat interrupt to LYC mode ld hl, rIF res 1, [hl] ; clear the interrupt so we don't immediately fire it ; restore main sp ld a, [vAsyncMainSP] ld l, a ld a, [vAsyncMainSP+1] ld h, a ld sp, hl ; restore registers from memory ld a, [vAsyncAF] ld l, a ld a, [vAsyncAF+1] ld h, a push hl ; this is so we can pop af when we're done ld a, [vAsyncHL] ld l, a ld a, [vAsyncHL+1] ld h, a ld a, [vAsyncDE] ld e, a ld a, [vAsyncDE+1] ld d, a ld a, [vAsyncBC] ld c, a ld a, [vAsyncBC+1] ld b, a pop af reti Async_Kill: ld hl, EndOfInstructions call Async_Spawn_HL ret Async_EnterThread: ;stack looks like: ;c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc push af push bc push de push hl ; check if there's anything queued up for executing; if no, just clean up ld a, LOW(ASYNC_STACK_TOP) ; we're checkng if the current async stack is empty ld hl, vAsyncThreadSP ; comparing to the current thread sp cp a, [hl] ; are they equal? jp z, Async_CleanUpThread ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), outer context pc ld a, LOW(Async_ExitThread) ld [INTERRUPT_LCD], a ld a, HIGH(Async_ExitThread) ld [INTERRUPT_LCD+1], a ld a, SAFE_ASYNC_END ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [rLYC], a ; set lcd interrupt handler to EXIT SAFE MODE on line 153 ; save main thread stack pointer ld hl, sp+0 ld a, l ld [vAsyncMainSP], a ld a, h ld [vAsyncMainSP+1], a ; load side thread stack pointer ld a, [vAsyncThreadSP] ld l, a ld a, [vAsyncThreadSP+1] ld h, a ld sp, hl ; pop registers ; canonical ordering to push should be: AF, BC, DE, HL, ; pop is HL, DE, BC, AF pop hl pop de pop bc pop af reti ; "return" to the vAsyncPC wee put on the stack previously. ; this is more or less a jump, not a return. ; is that the source of our problems? ; after this instruction executes, stack looks like: ; early return, af, de, bc, hl, PC (smc int), PC (hardwired int), PC (outer context) ; and we'll be in the async thread, executing Async_ExitThread: ; save interrupt registers (AF, BC, DE, HL) push af push bc push de push hl ;af, de, bc, hl, c113 (SMC interrupt pc), 004b (hardwired interrput pc), thread pc, return ld a, LOW(Async_EnterThread) ld [INTERRUPT_LCD], a ld a, HIGH(Async_EnterThread) ld [INTERRUPT_LCD+1], a ld a, SAFE_ASYNC_START ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [rLYC], a ; set lcd interrupt handler to EXIT SAFE MODE on line 153 ; save side thread stack pointer ld hl, sp+0 ld a, l ld [vAsyncThreadSP], a ld a, h ld [vAsyncThreadSP+1], a ; load main thread stack pointer ld a, [vAsyncMainSP] ld l, a ld a, [vAsyncMainSP+1] ld h, a ld sp, hl ; pop registers ; canonical ordering to push should be: AF, BC, DE, HL, ; pop is HL, DE, BC, AF pop hl pop de pop bc pop af reti Async_EarlyReturn: di ; don't care about current registers bc we're done executing. ; store side thread SP, so everyone knows that the side thread stack is empty ld hl, sp+0 ld a, l ld [vAsyncThreadSP], a ld a, h ld [vAsyncThreadSP+1], a ; unset next call ld hl, rIE res 1, [hl] ; disable vblank ld hl, rIF res 1, [hl] ; clear the interrupt ; load main thread stack pointer ld a, [vAsyncMainSP] ld l, a ld a, [vAsyncMainSP+1] ld h, a ld sp, hl ; restore the pre-interrupt registers, return and enable interrupts pop af pop de pop bc pop hl reti Async_CleanUpThread: ;stack looks like: ;hl, de, bc, af, c113 (SMC int @ LYC 90 pc), 004b (hw int @ LYC 90 pc), outer context pc pop hl pop de pop bc pop af reti