diff --git a/f256/jr.asm b/f256/jr.asm index c30baa8..05e5b11 100755 --- a/f256/jr.asm +++ b/f256/jr.asm @@ -83,6 +83,19 @@ platform .namespace .section dp irq_io .byte ? ; io_ctrl when an IRQ fires. irq_mmu .byte ? ; mmu_ctrl when an IRQ fires. +nmi_saved_sp .byte ? ; victim SP at NMI (low byte; high is $01 in emu) +nmi_saved_mmu .byte ? ; victim mmu_ctrl (active LUT) at NMI +nmi_saved_io .byte ? ; victim io_ctrl at NMI +nmi_saved_slot5 .byte ? ; (unused since Task1) legacy LUT0 slot-5 save +nmi_in_progress .byte ? ; non-zero while a handler is active (nested guard) +nmi_ptr .word ? ; (unused since Task1) legacy header entry pointer +nmi_orig_slot4 .byte ? ; victim LUT slot-4 bank displaced by the handler +nmi_orig_slot5 .byte ? ; victim LUT slot-5 bank displaced by the handler +nmi_save2 .byte ? ; LUT0 slot-2 saved during the flash->RAM copy +nmi_save4 .byte ? ; LUT0 slot-4 saved during the flash->RAM copy +nmi_src .word ? ; flash->RAM copy source pointer +nmi_dst .word ? ; flash->RAM copy dest pointer +nmi_fillb .byte ? ; fill byte for screen clear .send .section startup ; The following is ALWAYS at $E000 @@ -225,6 +238,9 @@ _zero stz Stack,x lda #>Buffers jsr kernel.page.init + ; Clear the NMI nested-break guard (dp RAM is uninitialized at power-on). + stz nmi_in_progress + ; Start the kernel jmp kernel.init @@ -263,15 +279,372 @@ yield wai rts +VIA_IFR = $DC0D ; 65C22 VIA Interrupt Flag Register +VIA_IER = $DC0E ; 65C22 VIA Interrupt Enable Register + +NMI_MON_BANK = $4a ; monitor flash block 0 ($40 + CSV 0a); block 1 = $4b +NMI_RAM_LO = $3e ; reserved RAM bank -> victim slot 4 ($8000): image block 0 +NMI_RAM_HI = $3f ; reserved RAM bank -> victim slot 5 ($A000): image block 1 +NMI_TEXT_BANK = $3c ; reserved RAM bank: saved victim text page +NMI_COLR_BANK = $3d ; reserved RAM bank: saved victim color page +NMI_SCR_CLEAR = $10 ; cleared color attribute (FG=white, BG=black) + hw_nmi: - pha + ; NMI dispatch: freeze the victim, gate on Foenix, copy the handler's + ; flash bank into the reserved RAM bank, map that RAM bank into the + ; VICTIM LUT's slot 5, and run the handler there (so it sees the victim's + ; memory directly); then restore slot 5 and resume the victim exactly. + pha ; A -> victim stack + phx ; X -> victim stack + phy ; Y -> victim stack + tsx ; X = victim SP + lda mmu_ctrl ; A = victim active LUT + stz mmu_ctrl ; -> kernel LUT (LUT0) + ldy nmi_in_progress + beq + + jmp nmi_reenter ++ + sta nmi_saved_mmu + stx nmi_saved_sp + lda @w platform.k2_kbd.state+0 + and #$20 + beq + + jmp nmi_not_ours ; Foenix not held -> ignore ++ + ; Decline a break INTO the monitor itself: it runs from reserved bank + ; NMI_RAM_HI ($3f) in slot 5, so freezing it would clobber its own RAM + ; during the flash->RAM copy and could not be resumed. If the victim's + ; slot 5 is that bank, ignore the NMI and resume it untouched. + jsr nmi_edit_vlut + lda mmu+5 + stz mmu_ctrl ; active LUT0, edit off + cmp #NMI_RAM_HI + bne _nmi_notmon + jmp nmi_not_ours +_nmi_notmon + lda #1 + sta nmi_in_progress lda io_ctrl + sta nmi_saved_io + + ; c2: save the victim's text+color screen and clear it for the monitor + jsr nmi_save_screen + + ; --- save the victim's original slot-4 and slot-5 banks --- + jsr nmi_edit_vlut ; edit-enable the victim's LUT + lda mmu+4 + sta nmi_orig_slot4 ; victim's original $8000 bank + lda mmu+5 + sta nmi_orig_slot5 ; victim's original $A000 bank + stz mmu_ctrl ; active LUT0, edit off + + ; --- copy the 2 handler flash blocks -> RAM_LO/RAM_HI (16 KB) --- + ; LUT0 slot2 ($4000) = flash source, slot4 ($8000) = RAM dest window. + lda #$80 + sta mmu_ctrl ; edit LUT0 + lda mmu+2 + sta nmi_save2 + lda mmu+4 + sta nmi_save4 + stz mmu_ctrl + ; block 0: flash NMI_MON_BANK -> RAM_LO + lda #$80 + sta mmu_ctrl + lda #NMI_MON_BANK + sta mmu+2 + lda #NMI_RAM_LO + sta mmu+4 + stz mmu_ctrl + jsr nmi_copy8k + ; poke the victim's slot-4 bank into the RAM image's reserved header byte + ; (offset 9); RAM_LO is mapped at LUT0 slot4 ($8000), so that is $8009. + lda nmi_orig_slot4 + sta $8009 + ; block 1: flash NMI_MON_BANK+1 -> RAM_HI + lda #$80 + sta mmu_ctrl + lda #NMI_MON_BANK+1 + sta mmu+2 + lda #NMI_RAM_HI + sta mmu+4 + stz mmu_ctrl + jsr nmi_copy8k + ; restore LUT0 slots 2 and 4 + lda #$80 + sta mmu_ctrl + lda nmi_save2 + sta mmu+2 + lda nmi_save4 + sta mmu+4 + stz mmu_ctrl + + ; --- map the RAM handler into the VICTIM LUT slots 4+5 --- + jsr nmi_edit_vlut + lda #NMI_RAM_LO + sta mmu+4 + lda #NMI_RAM_HI + sta mmu+5 + stz mmu_ctrl ; active LUT0, edit off + + ; --- enter the handler under the victim LUT (fixed entry $8100) --- + ldy nmi_saved_sp ; Y = victim SP + ldx nmi_orig_slot5 ; X = victim's original slot-5 bank + lda nmi_saved_mmu + sta mmu_ctrl ; active -> victim LUT + jsr $8100 ; do_entry_break; RTSs back (still victim LUT) + stz mmu_ctrl ; active -> LUT0 + + ; --- restore the victim LUT slots 4+5 --- + jsr nmi_edit_vlut + lda nmi_orig_slot4 + sta mmu+4 + lda nmi_orig_slot5 + sta mmu+5 + stz mmu_ctrl + + ; c2: restore the victim's text+color screen + jsr nmi_restore_screen + + ; --- inject a Foenix-key RELEASE for the resumed program --- + ; The victim cached the Foenix PRESS before the break but never saw the + ; release (the monitor consumed it), so it still thinks Foenix is held. + ; Queue a synthetic release so its next NextEvent clears that state. + jsr kernel.event.alloc + bcs _nmi_noinject + lda #0 + sta kernel.event.entry.key.ascii,y + lda #LMETA + sta kernel.event.entry.key.raw,y + lda #$80 ; META flag (no ASCII) + sta kernel.event.entry.key.flags,y + lda #kernel.event.key.RELEASED + sta kernel.event.entry.type,y + jsr kernel.event.enque +_nmi_noinject + + ; --- un-stick a free-running VIA timer IRQ --- + ; A program clocking itself off the VIA T1 in continuous mode (ACR bit6) + ; clears the T1 flag by reading T1C-L in its own IRQ handler. While frozen + ; that never happened, so the T1 flag is still set and the VIA IRQ line is + ; held low; with the edge-triggered interrupt controller no further VIA + ; IRQs would ever latch and the victim's frame loop would hang. If the VIA + ; has an enabled pending interrupt, clear its flags so the line releases + ; and the next timeout produces a fresh edge. No-op if the victim isn't + ; using the VIA (IER/IFR read is harmless). + stz $1 ; io page 0 -> VIA registers + lda VIA_IFR + and VIA_IER ; only sources the victim enabled + and #$7f ; ignore bit 7 (IRQ summary) + beq _nmi_no_via + lda #$7f + sta VIA_IFR ; write 1s to clear all VIA flags +_nmi_no_via + ; fall into nmi_resume + + ; nmi_resume: restore victim io/LUT/SP/regs and RTI back exactly. +nmi_resume + stz nmi_in_progress + ldx nmi_saved_sp + lda nmi_saved_io + sta io_ctrl + lda nmi_saved_mmu + sta mmu_ctrl ; victim LUT (active) + txs + ply + plx + pla + rti + + ; nmi_edit_vlut: set mmu_ctrl to EDIT the victim's LUT (from nmi_saved_mmu + ; active-LUT bits), leaving active LUT = 0 (kernel) so our code/stack/dp + ; stay valid. Uses A. +nmi_edit_vlut + lda nmi_saved_mmu + and #$03 + asl a + asl a + asl a + asl a + ora #$80 + sta mmu_ctrl + rts + + ; nmi_copy8k: copy 8 KB from LUT0 slot2 ($4000, flash source) to LUT0 slot4 + ; ($8000, RAM dest). Callers map the banks into those slots first. +nmi_copy8k + stz nmi_src + stz nmi_dst + lda #$40 + sta nmi_src+1 ; src = $4000 (LUT0 slot 2) + lda #$80 + sta nmi_dst+1 ; dst = $8000 (LUT0 slot 4) + ldx #$20 ; 32 pages of $100 = 8 KB +_nc ldy #0 +_nc1 lda (nmi_src),y + sta (nmi_dst),y + iny + bne _nc1 + inc nmi_src+1 + inc nmi_dst+1 + dex + bne _nc + rts + + ; ---- Screen save / restore (c2) --------------------------------------- + ; Runs under LUT0. Saves the victim's text + color pages into reserved + ; RAM banks and clears the screen for the monitor; restores on exit. + ; The 80x60 text/color screens are 4800 bytes each at $C000 (io page 2 + ; = text, io page 3 = color); a reserved bank is mapped into LUT0 slot 4 + ; ($8000) as the copy buffer. + + ; nmi_cp4800: copy 4800 bytes (nmi_src)->(nmi_dst). +nmi_cp4800 + ldx #18 ; 18 full pages +_cp0 ldy #0 +_cp1 lda (nmi_src),y + sta (nmi_dst),y + iny + bne _cp1 + inc nmi_src+1 + inc nmi_dst+1 + dex + bne _cp0 + ldy #0 ; + 192 bytes = 4800 +_cp2 lda (nmi_src),y + sta (nmi_dst),y + iny + cpy #192 + bne _cp2 + rts + + ; nmi_fill4800: fill 4800 bytes at $C000 with nmi_fillb (current io page). +nmi_fill4800 + stz nmi_dst + lda #$c0 + sta nmi_dst+1 + ldx #18 +_fl0 ldy #0 +_fl1 lda nmi_fillb + sta (nmi_dst),y + iny + bne _fl1 + inc nmi_dst+1 + dex + bne _fl0 + ldy #0 +_fl2 lda nmi_fillb + sta (nmi_dst),y + iny + cpy #192 + bne _fl2 + rts + + ; map bank A into LUT0 slot 4 ($8000), saving the displaced bank +nmi_map4 pha + lda #$80 + sta mmu_ctrl ; edit LUT0 + lda mmu+4 + sta nmi_save4 + pla + sta mmu+4 + stz mmu_ctrl + rts +nmi_unmap4 + lda #$80 + sta mmu_ctrl + lda nmi_save4 + sta mmu+4 + stz mmu_ctrl + rts + + ; nmi_save_screen: save text->$3d, color->$3e, clear screen + home cursor +nmi_save_screen + lda #NMI_TEXT_BANK + jsr nmi_map4 ; $3d -> slot 4 ($8000) + lda #2 + sta io_ctrl ; text page at $C000 + stz nmi_src + lda #$c0 + sta nmi_src+1 ; src = $C000 + stz nmi_dst + lda #$80 + sta nmi_dst+1 ; dst = $8000 + jsr nmi_cp4800 + lda #NMI_COLR_BANK + jsr nmi_map4 ; $3e -> slot 4 (nmi_save4 reused; ok, sequential) + lda #3 + sta io_ctrl ; color page at $C000 + stz nmi_src + lda #$c0 + sta nmi_src+1 + stz nmi_dst + lda #$80 + sta nmi_dst+1 + jsr nmi_cp4800 + jsr nmi_unmap4 + ; clear text (spaces) + color (attr), home the hardware cursor lda #2 sta io_ctrl - inc $c001 - pla + lda #$20 + sta nmi_fillb + jsr nmi_fill4800 + lda #3 + sta io_ctrl + lda #NMI_SCR_CLEAR + sta nmi_fillb + jsr nmi_fill4800 + lda #2 + sta io_ctrl ; leave text page selected + rts ; cursor is homed by the monitor (after it saves + ; the victim's cursor regs in its c1 block) + + ; nmi_restore_screen: restore text from $3d, color from $3e +nmi_restore_screen + lda #NMI_TEXT_BANK + jsr nmi_map4 + lda #2 + sta io_ctrl + stz nmi_src + lda #$80 + sta nmi_src+1 ; src = $8000 ($3d) + stz nmi_dst + lda #$c0 + sta nmi_dst+1 ; dst = $C000 (text) + jsr nmi_cp4800 + lda #NMI_COLR_BANK + jsr nmi_map4 + lda #3 sta io_ctrl + stz nmi_src + lda #$80 + sta nmi_src+1 + stz nmi_dst + lda #$c0 + sta nmi_dst+1 + jsr nmi_cp4800 + jsr nmi_unmap4 + rts + + ; Not our modifier combo: save area is valid (we are the outer handler); + ; restore victim LUT/SP/regs without any slot changes. +nmi_not_ours + ldx nmi_saved_sp + lda nmi_saved_mmu + sta mmu_ctrl + txs + ply + plx + pla + rti + + ; Re-entry while a handler is active: A still = victim LUT, X still = + ; victim SP (save area untouched). Restore and RTI back into the handler. +nmi_reenter + sta mmu_ctrl + txs + ply + plx pla rti diff --git a/kernel/kernel.asm b/kernel/kernel.asm index c918f43..8a40787 100755 --- a/kernel/kernel.asm +++ b/kernel/kernel.asm @@ -134,6 +134,14 @@ block_free rts tick + ; While an NMI/break handler is active the victim is frozen. Freeze the + ; timebase too, so its pending frame timers don't count down, expire, and + ; get freed out from under it -- they resume exactly on break exit. The + ; keyboard scan that follows this call in the frame IRQ still runs, so the + ; monitor keeps its input. + lda platform.nmi_in_progress + bne _frozen + ; Increment the tick count. inc kernel.ticks bne _done @@ -141,6 +149,7 @@ tick ; Dispatch delay events _done jmp kernel.delay.dispatch +_frozen rts set_timer