Files
z80_monitor/boot.asm
T
2026-10-03 17:30:43 +02:00

395 lines
8.8 KiB
NASM

STACK_TOP = 0xFFFF
DELAY_LOOPS_PER_MS = 40
BANK_CONTROL_PORT = 0x00
BOOT_BANK_MAPPING = 0x06 ; ROM on, banking on, upper 16K at C000-FFFF
ROM_DISABLE_BIT = 0x01
BANK_SELECT_BIT = 0x02
BANK_ENABLE_BIT = 0x04
BANK_MODE_CLEAR_MASK = 0xF9 ; preserve ROM-disable and auxiliary outputs
.include "bios.inc"
.globl _main
.globl _delay_ms
.globl _io_out
.globl _io_in
.globl _enable_interrupts
.globl _disable_interrupts
.globl _jump_to
.globl _bios_putc
.globl _bios_try_getc
.globl _bios_getc
.globl _bios_init
.globl _bios_bank
.globl _bios_rom_disable
.globl _bios_rom_enable
.globl _bank_control_shadow
.globl resident_image_end
.globl s__DATA
.globl l__DATA
.globl s__INITIALIZER
.globl s__INITIALIZED
.globl l__INITIALIZER
.area _BOOT (ABS)
.org 0x00
jp boot_copy
.org 0x08
boot_copy:
di
; Keep ROM enabled while mapping the upper physical 16K at C000-FFFF.
; The resident copy therefore survives either banked mode after boot.
ld a, #BOOT_BANK_MAPPING
out (BANK_CONTROL_PORT), a
ld sp, #STACK_TOP
ld hl, #RESIDENT_ROM_OFFSET
ld de, #RESIDENT_RAM_BASE
ld bc, #(resident_image_end - RESIDENT_RAM_BASE)
ldir
jp init
.area _ITABLE (ABS)
; The executable vector page is part of the image copied to high RAM.
.org RESIDENT_RAM_BASE
jp init
.org RESIDENT_RAM_BASE + 0x08
reti
.org RESIDENT_RAM_BASE + 0x10
reti
.org RESIDENT_RAM_BASE + 0x18
reti
.org RESIDENT_RAM_BASE + 0x20
reti
.org RESIDENT_RAM_BASE + 0x28
reti
.org RESIDENT_RAM_BASE + 0x30
reti
.org RESIDENT_RAM_BASE + 0x38
reti
; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46.
.org RESIDENT_RAM_BASE + 0x40
.dw ctc_isr ; CTC channel 0: vector 0x40
.dw ctc_isr ; CTC channel 1: vector 0x42
.dw ctc_isr ; CTC channel 2: vector 0x44
.dw uart_isr ; CTC channel 3: vector 0x46
; Stable BIOS jump table for ROM-resident applications.
; C050 putc(A), C053 try_getc(), C056 getc(), C059 io_out(A,L),
; C05C io_in(A), C05F delay_ms(HL), C062 monitor warm start,
; C065 bank(A), C068 disable ROM, C06B enable ROM.
.org BIOS_PUTC
jp bios_service_putc
.org BIOS_TRY_GETC
jp bios_service_try_getc
.org BIOS_GETC
jp bios_service_getc
.org BIOS_IO_OUT
jp bios_service_io_out
.org BIOS_IO_IN
jp bios_service_io_in
.org BIOS_DELAY_MS
jp bios_service_delay_ms
.org BIOS_MONITOR
jp init
.org BIOS_BANK
jp _bios_bank
.org BIOS_ROM_DISABLE
jp _bios_rom_disable
.org BIOS_ROM_ENABLE
jp _bios_rom_enable
; Port 00 is write-only. Keep its last BIOS-managed value outside the C data
; areas so a monitor warm start does not reset it. The bootstrap establishes
; this same value in the hardware before the resident image is copied.
.org RESIDENT_RAM_BASE + 0x70
_bank_control_shadow::
.db BOOT_BANK_MAPPING
.area _HOME
init:
di
ld sp, #STACK_TOP
; A reset may have interrupted a CTC service routine without executing
; RETI. Release any stale daisy-chain in-service latches before the
; channels are reinitialized. RETI acts as RET for each CALL while
; also signalling end-of-interrupt to daisy-chained peripherals.
call release_daisy_chain
call release_daisy_chain
call release_daisy_chain
call release_daisy_chain
call gsinit
; Setup interrupt mode 2
ld a, #0xC0
ld i, a
im 2
call _bios_init
call _main
hang:
jr hang
release_daisy_chain:
reti
.area _GSINIT
gsinit:
ld bc, #l__INITIALIZER
ld a, b
or c
jr z, zero_data
ld de, #s__INITIALIZED
ld hl, #s__INITIALIZER
ldir
zero_data:
ld bc, #l__DATA
ld a, b
or c
jr z, gsinit_done
ld hl, #s__DATA
xor a
clear_loop:
xor a ; BC loop test below changes A
ld (hl), a
inc hl
dec bc
ld a, b
or c
jr nz, clear_loop
gsinit_done:
.area _GSFINAL
ret
resident_image_end:
.area _CODE
; Select a RAM mapping while retaining the shadowed ROM-disable bit.
; Input: A=0 selects lower 48K + upper 16K; any other value selects upper 64K.
; Both modes keep banking enabled. A and flags are preserved.
_bios_bank::
push af
or a
jr z, bios_bank_split
ld a, (_bank_control_shadow)
and #BANK_MODE_CLEAR_MASK
or #BANK_ENABLE_BIT
jr bios_bank_update
bios_bank_split:
ld a, (_bank_control_shadow)
and #BANK_MODE_CLEAR_MASK
or #(BANK_ENABLE_BIT | BANK_SELECT_BIT)
bios_bank_update:
ld (_bank_control_shadow), a
out (BANK_CONTROL_PORT), a
pop af
ret
; Hide ROM while preserving the selected RAM mapping.
; A and flags are preserved.
_bios_rom_disable::
push af
ld a, (_bank_control_shadow)
or #ROM_DISABLE_BIT
jr bios_bank_update
; Make ROM visible again while preserving the selected RAM mapping.
; A and flags are preserved.
_bios_rom_enable::
push af
ld a, (_bank_control_shadow)
and #0xFE
jr bios_bank_update
; BIOS ABI services preserve registers except documented return values.
bios_service_putc:
push af
push bc
push de
push hl
push ix
push iy
call _bios_putc
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
bios_service_try_getc:
push bc
push de
push hl
push ix
push iy
call _bios_try_getc
pop iy
pop ix
pop hl
pop de
pop bc
ret
bios_service_getc:
push bc
push de
push hl
push ix
push iy
call _bios_getc
pop iy
pop ix
pop hl
pop de
pop bc
ret
bios_service_io_out:
push af
push bc
push de
push hl
push ix
push iy
call _io_out
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
bios_service_io_in:
push bc
push de
push hl
push ix
push iy
call _io_in
pop iy
pop ix
pop hl
pop de
pop bc
ret
bios_service_delay_ms:
push af
push bc
push de
push hl
push ix
push iy
call _delay_ms
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ret
.macro MAKE_ISR entry, handler
.globl handler
entry:
push af
push bc
push de
push hl
push ix
push iy
ex af, af'
push af
exx
push bc
push de
push hl
call handler
pop hl
pop de
pop bc
exx
pop af
ex af, af'
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ei
reti
.endm
MAKE_ISR ctc_isr, _ctc_interrupt
MAKE_ISR uart_isr, _uart_interrupt
_enable_interrupts::
ei
ret
_disable_interrupts::
di
ret
; Write a byte to an I/O port.
; Input: A = port address, L = data byte.
_io_out::
ld c, a
ld a, l
out (c), a
ret
; Read a byte from an I/O port.
; Input: A = port address.
; Return: A = data byte.
_io_in::
ld c, a
in a, (c)
ret
; Execute code at HL. Because this routine is entered with CALL, a RET in the
; target returns to the monitor immediately after jump_to().
_jump_to::
jp (hl)
; Approximate millisecond delay.
; Input: HL = delay in milliseconds.
_delay_ms::
ld a, h
or l
ret z
delay_ms_outer$:
push hl
ld hl, #DELAY_LOOPS_PER_MS
delay_ms_inner$:
dec hl
ld a, h
or l
jr nz, delay_ms_inner$
pop hl
dec hl
ld a, h
or l
jr nz, delay_ms_outer$
ret