; crt0 — C runtime startup for the W65816 backend. ; ; Entry point invoked by the loader (or the OMF dispatcher). Sets up ; the processor mode the rest of the runtime expects, zeroes BSS, ; calls main, and halts on return. ; ; Conventions: ; - Native mode (E=0), 16-bit M and X (REP #$30) on entry to main. ; - DP=0, DBR=0 — assumed by the C runtime. ; - Linker-emitted symbols: __bss_start, __bss_end (16-bit addrs). .text .globl __start __start: ; Disable IRQ first — the IIgs ROM hands a vsync IRQ on every frame, ; and its handler runs in 8-bit M/X mode, corrupting our state if ; we leave I clear. SEI is fine in either emulation or native ; mode and is always 1 byte / 2 cycles. sei ; Native mode + 16-bit registers. clc xce rep #0x30 ; Disable IIgs peripheral interrupt sources at the chip level — ; SEI alone leaves the hardware lines asserted, and the IRQ trap ; in ROM keeps re-firing if the source isn't quiesced. STZ ; stores zero without going through A; in M=8 it stores 1 byte ; (matching the 8-bit registers), so no LDA #0 prelude is needed. sep #0x20 stz 0xc041 ; INTEN = 0 (clear AN3/mouse/0.25s/VBL/mouse-IRQ enables) stz 0xc023 ; VGCINT = 0 (clear external/1-sec/scan-line IRQ enables) stz 0xc032 ; SCANINT clear rep #0x20 ; Top-of-stack at $0FFF. Native-mode S is 16-bit, so we don't need ; to stay in page 1. Soft-double frames can be ~170 bytes plus the ; usual call-chain overhead — at $01FF stack growth wraps into the ; direct page ($0000-$00FF) which holds our libcall scratch ; ($E0-$F4) and IMG slots ($D0-$DE), corrupting them. $0FFF gives ; ~3.5 KB of headroom and stays below the text base ($1000). lda #0x0fff tcs ; Enable Language Card 1 RAM at $D000-$DFFF for read+write. ; By default the IIgs maps that range to ROM (read-only). Two ; reads of $C083 enable RAM-bank-1, second read also enables ; writes. Without this, BSS auto-relocated past $C000 lands on ; ROM and globals never initialise (writes drop on the floor; ; reads return ROM bytes). Caught by the expression-parser ; smoke test (#92) when runtime growth pushed bss past $BFFF. ; The reads must be 8-bit (one byte at a time) — a 16-bit M ; read at $C083 would also touch $C084 (a different soft ; switch), wiping the LC enable we just set. sep #0x20 lda 0xc083 lda 0xc083 rep #0x20 ; Zero BSS. X iterates from __bss_start to __bss_end; each ; iteration writes one byte of zero at addr X (via DP=0 + ; offset 0 — which is just X). STZ in M=8 stores 1 byte and ; doesn't touch A, so we don't need the LDA #0 prelude. rep #0x10 ; ensure X is 16-bit ldx #__bss_start .Lbss_loop: cpx #__bss_end bcs .Lbss_done ; X >= end -> done sep #0x20 ; 8-bit M for 1-byte store stz 0x0, x ; *(uint8_t *)X = 0 (DP=0) rep #0x20 inx bra .Lbss_loop .Lbss_done: ; Run static constructors. The linker emits ; __init_array_start / __init_array_end around the .init_array ; section; each entry is a 16-bit function pointer. Walk and ; JSL each via __jsl_indir. rep #0x30 ; native, 16-bit M and X ldx #__init_array_start .Linit_loop: cpx #__init_array_end bcs .Linit_done ; __jsl_indir does `JMP (__indirTarget)` — reads a 16-bit ptr ; from __indirTarget and JMPs there. So __indirTarget must ; hold the function pointer itself (NOT the address of the ; init_array slot). Dereference the entry: ($E0)→A. stx 0xe0 ; entry addr -> DP scratch ldy #0 lda (0xe0), y ; A = mem[X] (DP-indirect-Y, opcode 0xb1) sta __indirTarget ; __indirTarget = function pointer phx ; preserve X across the call jsl __jsl_indir plx inx inx bra .Linit_loop .Linit_done: ; Call main. Standard W65816 ABI: i16 first arg in A; we pass ; nothing. After return, A holds the exit code. jsl main ; Halt via BRK $00. MAME / debuggers catch this as a clean ; program termination. .byte 0x00, 0x00 .size __start, . - __start