;; ---------------------------------------------------------------------- ;; bank.s — Sprinter banked-call trampolines for SDCC __banked functions. ;; ;; ABI (verified empirically with SDCC 4.5, see memory/sdcc_banking.md): ;; On entry to ___sdcc_bcall_ehl: ;; E = bank id (1..15), HL = target address (low 16 bits). ;; Args already on stack, pushed by the caller right-to-left. ;; Stack picture once the trampoline finishes set-up and jp(hl) fires: ;; SP+0..1 = bjump return (callee's "ret address") ;; SP+2 = saved physical page (1 byte) ;; SP+3..4 = bcall caller return ;; SP+5.. = args ← __banked callees read from here ;; ;; The 3-byte spacer between callee ret and args is the contract SDCC bakes ;; into its codegen — touching it breaks all __banked calls. ;; ---------------------------------------------------------------------- .module bank .globl _bank_pages .globl ___sdcc_bcall_ehl .globl ___sdcc_bjump_ehl ;; Choose between W3 (HUGE mode, default) and W1 (BIG mode, sprinter-cc ;; prepends `BANK_W1 = 1`). Keep in sync with crt0_banked.s. .ifdef BANK_W1 BANK_PORT = 0xA2 .else BANK_PORT = 0xE2 .endif ;; HOME area — always mapped, so the trampoline is reachable from any bank. .area _CODE ___sdcc_bcall_ehl:: ;; The 3-byte spacer between bcall-ret and args (1-byte saved page ;; + 2-byte bjump return address) is baked into SDCC's __banked ;; codegen — callees access their args at fixed offsets from SP. ;; We MUST keep that contract intact. ;; ;; The old `pop af; out (n), a` restore sequence clobbered A, ;; dropping uint8_t return values. We use the `OUT (C), r` form ;; (port via C, byte via B) so A is preserved. BC is scratch in ;; SDCC __sdcccall(1), so clobbering it here is harmless. in a, (#BANK_PORT) ; A = current bank-window page push af ; push A:F (2 bytes) inc sp ; keep only the A byte (1-byte spacer) call ___sdcc_bjump_ehl ;; Callee returned. A may hold a uint8_t return value. dec sp ; widen back to 2 bytes pop bc ; B = saved page (C = whatever F was) ld c, #BANK_PORT ; C = bank-window select port out (c), b ; OUT (port), B — A and F untouched ret ; back to bcall caller ___sdcc_bjump_ehl:: push hl ; preserve target address ld d, #0 ; D:E = 0:bank_id ld hl, #_bank_pages add hl, de ; HL = &_bank_pages[bank_id] ld a, (hl) ; A = physical page for this bank pop hl ; restore target address out (#BANK_PORT), a ; map bank into banking window jp (hl) ; tail-jump to callee ;; Per-bank physical page table. Index = bank id (1-based; [0] is unused). ;; Filled at startup by crt0_banked.s. .area _DATA _bank_pages:: .ds 16