gbdk-releases/gbdk-lib/libc/gb/crt0.s
2015-01-10 16:25:08 +01:00

716 lines
11 KiB
ArmAsm

.include "global.s"
;; ****************************************
;; Beginning of module
;; BANKED: checked
.title "Runtime"
.module Runtime
.area _HEADER (ABS)
;; Standard header for the GB
.org 0x00
RET ; Empty function (default for interrupts)
.org 0x10
.byte 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
.byte 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
;; Interrupt vectors
.org 0x40 ; VBL
.int_VBL:
PUSH HL
LD HL,#.int_0x40
JP .int
.org 0x48 ; LCD
.int_LCD:
PUSH HL
LD HL,#.int_0x48
JP .int
.org 0x50 ; TIM
.int_TIM:
PUSH HL
LD HL,#.int_0x50
JP .int
.org 0x58 ; SIO
.int_SIO:
PUSH HL
LD HL,#.int_0x58
JP .int
.org 0x60 ; JOY
.int_JOY:
PUSH HL
LD HL,#.int_0x60
JP .int
.int:
PUSH AF
PUSH BC
PUSH DE
1$:
LD A,(HL+)
OR (HL)
JR Z,2$
PUSH HL
LD A,(HL-)
LD L,(HL)
LD H,A
CALL 3$
POP HL
INC HL
JR 1$
2$:
POP DE
POP BC
POP AF
POP HL
RETI
3$:
JP (HL)
;; GameBoy Header
;; DO NOT CHANGE...
.org 0x100
.header:
NOP
JP 0x150
.byte 0xCE,0xED,0x66,0x66
.byte 0xCC,0x0D,0x00,0x0B
.byte 0x03,0x73,0x00,0x83
.byte 0x00,0x0C,0x00,0x0D
.byte 0x00,0x08,0x11,0x1F
.byte 0x88,0x89,0x00,0x0E
.byte 0xDC,0xCC,0x6E,0xE6
.byte 0xDD,0xDD,0xD9,0x99
.byte 0xBB,0xBB,0x67,0x63
.byte 0x6E,0x0E,0xEC,0xCC
.byte 0xDD,0xDC,0x99,0x9F
.byte 0xBB,0xB9,0x33,0x3E
;; Title of the game
.org 0x134
.asciz "Title"
.org 0x144
.byte 0,0,0
;; Cartridge type is ROM only
.org 0x147
.byte 0
;; ROM size is 32kB
.org 0x148
.byte 0
;; RAM size is 0kB
.org 0x149
.byte 0
;; Maker ID
.org 0x14A
.byte 0x00,0x00
;; Version number
.org 0x14C
.byte 0x01
;; Complement check
.org 0x14D
.byte 0x00
;; Checksum
.org 0x14E
.byte 0x00,0x00
;; ****************************************
.org 0x150
.code_start:
;; Beginning of the code
DI ; Disable interrupts
LD D,A ; Store CPU type in D
XOR A
;; Initialize the stack
LD SP,#.STACK
;; Clear from 0xC000 to 0xDFFF
LD HL,#0xDFFF
LD C,#0x20
LD B,#0x00
1$:
LD (HL-),A
DEC B
JR NZ,1$
DEC C
JR NZ,1$
;; Clear from 0xFE00 to 0xFEFF
LD HL,#0xFEFF
LD B,#0x00
2$:
LD (HL-),A
DEC B
JR NZ,2$
;; Clear from 0xFF80 to 0xFFFF
LD HL,#0xFFFF
LD B,#0x80
3$:
LD (HL-),A
DEC B
JR NZ,3$
; LD (.mode),A ; Clearing (.mode) is performed when clearing RAM
;; Store CPU type
LD A,D
LD (__cpu),A
;; Turn the screen off
CALL .display_off
;; Initialize the display
XOR A
LDH (.SCY),A
LDH (.SCX),A
LDH (.STAT),A
LDH (.WY),A
LD A,#0x07
LDH (.WX),A
;; Copy refresh_OAM routine to HIRAM
LD BC,#.refresh_OAM
LD HL,#.start_refresh_OAM
LD B,#.end_refresh_OAM-.start_refresh_OAM
4$:
LD A,(HL+)
LDH (C),A
INC C
DEC B
JR NZ,4$
;; Install interrupt routines
LD BC,#.vbl
CALL .add_VBL
LD BC,#.serial_IO
CALL .add_SIO
;; Standard color palettes
LD A,#0b11100100 ; Grey 3 = 11 (Black)
; Grey 2 = 10 (Dark grey)
; Grey 1 = 01 (Light grey)
; Grey 0 = 00 (Transparent)
LDH (.BGP),A
LDH (.OBP0),A
LD A,#0b00011011
LDH (.OBP1),A
;; Turn the screen on
LD A,#0b11000000 ; LCD = On
; WindowBank = 0x9C00
; Window = Off
; BG Chr = 0x8800
; BG Bank = 0x9800
; OBJ = 8x8
; OBJ = Off
; BG = Off
LDH (.LCDC),A
XOR A
LDH (.IF),A
LD A,#0b00001001 ; Pin P10-P13 = Off
; Serial I/O = On
; Timer Ovfl = Off
; LCDC = Off
; V-Blank = On
LDH (.IE),A
XOR A
LDH (.NR52),A ; Turn sound off
LDH (.SC),A ; Use external clock
LD A,#.DT_IDLE
LDH (.SB),A ; Send IDLE byte
LD A,#0x80
LDH (.SC),A ; Use external clock
XOR A ; Erase the malloc list
; LD (_malloc_heap_start+0),A
; LD (_malloc_heap_start+1),A
; LD (.sys_time+0),A ; Zero the system clock
; LD (.sys_time+1),A
call gsinit
; CALL .init
EI ; Enable interrupts
;; Call the main function
CALL banked_call
.dw _main
.if __RGBDS__
.dw BANK(_main)
.else
.dw 1
.endif
_exit::
99$:
HALT
JR 99$ ; Wait forever
.org .MODE_TABLE
;; Jump table for modes
RET
;; ****************************************
;; Ordering of segments for the linker
;; Code that really needs to be in bank 0
.area _HOME
;; Similar to _HOME
.area _BASE
;; Code
.area _CODE
;; Constant data
.area _LIT
;; Constant data used to init _DATA
.area _GSINIT
.area _GSINITTAIL
.area _GSFINAL
;; Initialised in ram data
.area _DATA
;; Uninitialised ram data
.area _BSS
;; For malloc
.area _HEAP
.area _BSS
__cpu::
.ds 0x01 ; GB type (GB, PGB, CGB)
.mode::
.ds 0x01 ; Current mode
__io_out::
.ds 0x01 ; Byte to send
__io_in::
.ds 0x01 ; Received byte
__io_status::
.ds 0x01 ; Status of serial IO
.vbl_done::
.ds 0x01 ; Is VBL interrupt finished?
__current_bank::
.ds 0x01 ; Current MBC1 style bank.
.sys_time::
_sys_time::
.ds 0x02 ; System time in VBL units
.int_0x40::
.blkw 0x08
.int_0x48::
.blkw 0x08
.int_0x50::
.blkw 0x08
.int_0x58::
.blkw 0x08
.int_0x60::
.blkw 0x08
;; Runtime library
.area _GSINIT
gsinit::
.area _GSINITTAIL
ret
.area _HOME
;; Call the initialization function for the mode specified in HL
.set_mode::
LD A,L
LD (.mode),A
;; AND to get rid of the extra flags
AND #0x03
LD L,A
LD BC,#.MODE_TABLE
SLA L ; Multiply mode by 4
SLA L
ADD HL,BC
JP (HL) ; Jump to initialization routine
;; Add interrupt routine in BC to the interrupt list
.remove_VBL::
LD HL,#.int_0x40
JP .remove_int
.remove_LCD::
LD HL,#.int_0x48
JP .remove_int
.remove_TIM::
LD HL,#.int_0x50
JP .remove_int
.remove_SIO::
LD HL,#.int_0x58
JP .remove_int
.remove_JOY::
LD HL,#.int_0x60
JP .remove_int
.add_VBL::
LD HL,#.int_0x40
JP .add_int
.add_LCD::
LD HL,#.int_0x48
JP .add_int
.add_TIM::
LD HL,#.int_0x50
JP .add_int
.add_SIO::
LD HL,#.int_0x58
JP .add_int
.add_JOY::
LD HL,#.int_0x60
JP .add_int
;; Remove interrupt BC from interrupt list HL if it exists
;; Abort if a 0000 is found (end of list)
;; Will only remove last int on list
.remove_int::
1$:
LD A,(HL+)
LD E,A
LD D,(HL)
OR D
RET Z ; No interrupt found
LD A,E
CP C
JR NZ,1$
LD A,D
CP B
JR NZ,1$
XOR A
LD (HL-),A
LD (HL),A
INC A ; Clear Z flag
;; Now do a memcpy from here until the end of the list
LD D,H
LD E,L
DEC DE
INC HL
2$:
LD A,(HL+)
LD (DE),A
LD B,A
INC DE
LD A,(HL+)
LD (DE),A
INC DE
OR B
RET Z
JR 2$
;; Add interrupt routine in BC to the interrupt list in HL
.add_int::
1$:
LD A,(HL+)
OR (HL)
JR Z,2$
INC HL
JR 1$
2$:
LD (HL),B
DEC HL
LD (HL),C
RET
;; VBlank interrupt
.vbl:
LD HL,#.sys_time
INC (HL)
JR NZ,2$
INC HL
INC (HL)
2$:
CALL .refresh_OAM
LD A,#0x01
LD (.vbl_done),A
RET
;; Wait for VBL interrupt to be finished
.wait_vbl_done::
_wait_vbl_done::
;; Check if the screen is on
LDH A,(.LCDC)
ADD A
RET NC ; Return if screen is off
XOR A
DI
LD (.vbl_done),A ; Clear any previous sets of vbl_done
EI
1$:
HALT ; Wait for any interrupt
NOP ; HALT sometimes skips the next instruction
LD A,(.vbl_done) ; Was it a VBlank interrupt?
;; Warning: we may lose a VBlank interrupt, if it occurs now
OR A
JR Z,1$ ; No: back to sleep!
XOR A
LD (.vbl_done),A
RET
.display_off::
_display_off::
;; Check if the screen is on
LDH A,(.LCDC)
ADD A
RET NC ; Return if screen is off
1$: ; We wait for the *NEXT* VBL
LDH A,(.LY)
CP #0x92 ; Smaller than or equal to 0x91?
JR NC,1$ ; Loop until smaller than or equal to 0x91
2$:
LDH A,(.LY)
CP #0x91 ; Bigger than 0x90?
JR C,2$ ; Loop until bigger than 0x90
LDH A,(.LCDC)
AND #0b01111111
LDH (.LCDC),A ; Turn off screen
RET
;; Copy OAM data to OAM RAM
.start_refresh_OAM:
LD A,#>.OAM
LDH (.DMA),A ; Put A into DMA registers
LD A,#0x28 ; We need to wait 160 ns
1$:
DEC A
JR NZ,1$
RET
.end_refresh_OAM:
;; Serial interrupt
.serial_IO::
LD A,(__io_status) ; Get status
CP #.IO_RECEIVING
JR NZ,10$
;; Receiving data
LDH A,(.SB) ; Get data byte
LD (__io_in),A ; Store it
LD A,#.IO_IDLE
JR 11$
10$:
CP #.IO_SENDING
JR NZ,99$
;; Sending data
LDH A,(.SB) ; Get data byte
CP #.DT_RECEIVING
JR Z,11$
LD A,#.IO_ERROR
JR 12$
11$:
LD A,#.IO_IDLE
12$:
LD (__io_status),A ; Store status
XOR A
LDH (.SC),A ; Use external clock
LD A,#.DT_IDLE
LDH (.SB),A ; Reply with IDLE byte
99$:
LD A,#0x80
LDH (.SC),A ; Enable transfer with external clock
RET
_mode::
LDA HL,2(SP) ; Skip return address
LD L,(HL)
LD H,#0x00
CALL .set_mode
RET
_get_mode::
LD HL,#.mode
LD E,(HL)
RET
_enable_interrupts::
EI
RET
_disable_interrupts::
DI
RET
.reset::
_reset::
LD A,(__cpu)
JP .code_start
_set_interrupts::
DI
LDA HL,2(SP) ; Skip return address
XOR A
LDH (.IF),A ; Clear pending interrupts
LD A,(HL)
LDH (.IE),A
EI ; Enable interrupts
RET
_remove_VBL::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .remove_VBL
POP BC
RET
_remove_LCD::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .remove_LCD
POP BC
RET
_remove_TIM::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .remove_TIM
POP BC
RET
_remove_SIO::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .remove_SIO
POP BC
RET
_remove_JOY::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .remove_JOY
POP BC
RET
_add_VBL::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .add_VBL
POP BC
RET
_add_LCD::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .add_LCD
POP BC
RET
_add_TIM::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .add_TIM
POP BC
RET
_add_SIO::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .add_SIO
POP BC
RET
_add_JOY::
PUSH BC
LDA HL,4(SP) ; Skip return address and registers
LD C,(HL)
INC HL
LD B,(HL)
CALL .add_JOY
POP BC
RET
_clock::
ld hl,#.sys_time
di
ld a,(hl+)
ei
;; Interrupts are disabled for the next instruction...
ld d,(hl)
ld e,a
ret
__printTStates::
ret
;; Performs a long call.
;; Basically:
;; call banked_call
;; .dw low
;; .dw bank
;; remainder of the code
;; Total m-cycles:
;; 3+4+4 + 2+2+2+2+2+2 + 4+4+ 3+4+1+1+1
;; = 41 for the call
;; 3+3+4+4+1
;; = 15 for the ret
banked_call::
pop hl ; Get the return address
ld a,(__current_bank)
push af ; Push the current bank onto the stack
ld e,(hl) ; Fetch the call address
inc hl
ld d,(hl)
inc hl
ld a,(hl+) ; ...and page
inc hl ; Yes this should be here
push hl ; Push the real return address
ld (__current_bank),a
ld (.MBC1_ROM_PAGE),a ; Perform the switch
ld hl,#banked_ret ; Push the fake return address
push hl
ld l,e
ld h,d
jp (hl)
banked_ret::
pop hl ; Get the return address
pop af ; Pop the old bank
ld (.MBC1_ROM_PAGE),a
ld (__current_bank),a
jp (hl)
.area _HEAP
_malloc_heap_start::