; ; Startup code for cc65 (CBM 500 version) ; .export _exit, BRKVec .export __STARTUP__ : absolute = 1 ; Mark as startup .import _clrscr, initlib, donelib, callirq_y .import push0, callmain .import __CHARRAM_START__, __CHARRAM_SIZE__, __VIDRAM_START__ .import __BSS_RUN__, __BSS_SIZE__, __EXTZP_RUN__ .import __INTERRUPTOR_COUNT__ .import scnkey, UDTIM .include "zeropage.inc" .include "extzp.inc" .include "cbm510.inc" ; ------------------------------------------------------------------------ ; The BASIC header and a small BASIC program. Since it isn't possible to start ; programs in other banks using SYS, the BASIC program will write a small ; machine code program into memory at $100; and, start that machine code ; program. The machine code program will then start the machine language ; code in bank 0, which will initialize the system by copying stuff from ; the system bank, and start the application. ; ; Here's the BASIC program that's in the following lines: ; ; 10 for i=0 to 4 ; 20 read j ; 30 poke 256+i,j ; 40 next i ; 50 sys 256 ; 60 data 120,169,0,133,0 ; ; The machine program in the data lines is: ; ; sei ; lda #$00 ; sta $00 <-- Switch to bank 0 after this command ; ; Initialization is complex not only because of the jumping from one bank ; into another, but also because we want to save memory; and because of ; that, we will use the system memory ($00-$3FF) for initialization stuff ; that is overwritten later. ; .segment "EXEHDR" .byte $03,$00,$11,$00,$0a,$00,$81,$20,$49,$b2,$30,$20,$a4,$20,$34,$00 .byte $19,$00,$14,$00,$87,$20,$4a,$00,$27,$00,$1e,$00,$97,$20,$32,$35 .byte $36,$aa,$49,$2c,$4a,$00,$2f,$00,$28,$00,$82,$20,$49,$00,$39,$00 .byte $32,$00,$9e,$20,$32,$35,$36,$00,$4f,$00,$3c,$00,$83,$20,$31,$32 .byte $30,$2c,$31,$36,$39,$2c,$30,$2c,$31,$33,$33,$2c,$30,$00,$00,$00 ;------------------------------------------------------------------------------ ; A table that contains values that must be transferred from the system zero- ; page into our zero-page. Contains pairs of bytes; first one is the address ; in the system ZP, second one is our ZP address. The table goes into page 2; ; but, is declared here because it is needed earlier. .SEGMENT "PAGE2" ; (We use .proc because we need both a label and a scope.) .proc transfer_table .byte $9F, DEVNUM .byte $CA, CURS_Y .byte $CB, CURS_X .byte $E6, CURS_FLAG .byte $E7, CURS_BLINK .byte $EC, CHARCOLOR .endproc ;------------------------------------------------------------------------------ ; Page 3 data. This page contains the break vector and the bankswitch ; subroutine that is copied into high memory on startup. The space occupied by ; this routine will later be used for a copy of the bank 15 stack. It must be ; saved since we're going to destroy it when calling bank 15. .segment "PAGE3" BRKVec: .addr _exit ; BRK indirect vector .proc callbank15 excrts = $FEFE .org $FEC3 entry: php pha lda #$0F ; Bank 15 sta IndReg txa pha tya pha sei ldy #$FF lda (sysp1),y tay lda ExecReg sta (sysp1),y dey lda #.hibyte(excrts-1) sta (sysp1),y dey lda #.lobyte(excrts-1) sta (sysp1),y tya sec sbc #7 sta $1FF ; Save new sp tay tsx pla iny sta (sysp1),y pla iny sta (sysp1),y pla iny sta (sysp1),y pla iny sta (sysp1),y lda $105,x sec sbc #3 iny sta (sysp1),y lda $106,x sbc #0 iny sta (sysp1),y ldy $1FF ; Restore sp in bank 15 lda #.hibyte(expull-1) sta (sysp1),y dey lda #.lobyte(expull-1) sta (sysp1),y dey pla pla tsx stx $1FF tya tax txs lda IndReg jmp $FFF6 expull: pla tay pla tax pla plp rts .if (expull <> $FF26) .error "Symbol expull must be aligned with Kernal in bank 15" .endif .reloc .endproc ;------------------------------------------------------------------------------ ; The code in the target bank when switching back will be put at the bottom ; of the stack. We will jump here to switch segments. The range $F2..$FF is ; not used by any Kernal routine. .segment "STARTUP" Back: sta ExecReg ; We are at $100 now. The following snippet is a copy of the code that is poked ; in the system bank memory by the BASIC header program; it's only for ; documentation, and not actually used here: sei lda #$00 sta ExecReg ; This is the actual starting point of our code after switching banks for ; startup. Beware: The following code will get overwritten as soon as we ; use the stack (since it's in page 1)! We jump to another location since ; we need some space for subroutines that aren't used later. jmp Origin ; Hardware vectors, copied to $FFF6 .proc vectors sta ExecReg rts nop .word nmi ; NMI vector .word 0 ; Reset -- not used .word irq ; IRQ vector .endproc ; Initializers for the extended zero-page. See "extzp.s". .proc extzp .word $0100 ; sysp1 .word $0300 ; sysp3 .word $d800 ; vic .word $da00 ; sid .word $db00 ; cia1 .word $dc00 ; cia2 .word $dd00 ; acia .word $de00 ; tpi1 .word $df00 ; tpi2 .word $eab1 ; ktab1 .word $eb11 ; ktab2 .word $eb71 ; ktab3 .word $ebd1 ; ktab4 .endproc ; Switch the indirect segment to the system bank. Origin: lda #$0F sta IndReg ; Initialize the extended zero-page. ldx #.sizeof(extzp)-1 L1: lda extzp,x sta <__EXTZP_RUN__,x dex bpl L1 ; Save the old stack pointer from the system bank; and, set up our hw sp. tsx txa ldy #$FF sta (sysp1),y ; Save system stack point into $F:$1FF ldx #$FE ; Leave $1FF untouched for cross-bank calls txs ; Set up our own stack ; Copy stuff from the system zero-page to ours. lda #.sizeof(transfer_table) sta ktmp L2: ldx ktmp ldy transfer_table-2,x lda transfer_table-1,x tax lda (sysp0),y sta $00,x dec ktmp dec ktmp bne L2 ; Set the interrupt, NMI, and other vectors. ldx #.sizeof(vectors)-1 L3: lda vectors,x sta $10000 - .sizeof(vectors),x dex bpl L3 ; Set up the C stack. lda #.lobyte(callbank15::entry) sta sp lda #.hibyte(callbank15::entry) sta sp+1 ; Set up the subroutine and jump vector table that redirects Kernal calls to ; the system bank. ldy #.sizeof(callbank15) @L1: lda callbank15-1,y sta callbank15::entry-1,y dey bne @L1 ; Set up the jump vector table. Y is zero on entry. ldx #45-1 ; Number of vectors @L2: lda #$20 ; JSR opcode sta $FF6F,y iny lda #.lobyte(callbank15::entry) sta $FF6F,y iny lda #.hibyte(callbank15::entry) sta $FF6F,y iny dex bpl @L2 ; Set the indirect segment to the bank that we're executing in. lda ExecReg sta IndReg ; Zero the BSS segment. We will do that here instead of calling the routine ; in the common library, since we have the memory anyway; and this way, ; it's reused later. lda #<__BSS_RUN__ sta ptr1 lda #>__BSS_RUN__ sta ptr1+1 lda #0 tay ; Clear full pages. ldx #>__BSS_SIZE__ beq Z2 Z1: sta (ptr1),y iny bne Z1 inc ptr1+1 ; Next page dex bne Z1 ; Clear the remaining page. Z2: ldx #<__BSS_SIZE__ beq Z4 Z3: sta (ptr1),y iny dex bne Z3 Z4: jmp Init ; ------------------------------------------------------------------------ ; We are at $200 now. We may now start calling subroutines safely since ; the code we execute is no longer in the stack page. .segment "PAGE2" ; Copy the character ROM from the system bank into the execution bank. Init: lda #<$C000 sta ptr1 lda #>$C000 sta ptr1+1 lda #<__CHARRAM_START__ sta ptr2 lda #>__CHARRAM_START__ sta ptr2+1 lda #>__CHARRAM_SIZE__ ; 16 * 256 bytes to copy sta tmp1 ldy #$00 ccopy: lda #$0F sta IndReg ; Access the system bank ccopy1: lda (ptr1),y sta __VIDRAM_START__,y iny bne ccopy1 lda ExecReg sta IndReg ccopy2: lda __VIDRAM_START__,y sta (ptr2),y iny bne ccopy2 inc ptr1+1 inc ptr2+1 ; Bump high pointer bytes dec tmp1 bne ccopy ; Clear the video memory. We will do this before switching the video to bank 0, ; to avoid garbage when doing so. jsr _clrscr ; Reprogram the VIC so that the text screen and the character ROM are in the ; execution bank. This is done in three steps: lda #$0F ; We need access to the system bank sta IndReg ; Place the VIC video RAM into bank 0. ; CA (STATVID) = 0 ; CB (VICDOTSEL) = 0 ldy #TPI::CR lda (tpi1),y sta vidsave+0 and #%00001111 ora #%10100000 sta (tpi1),y ; Set bit 14/15 of the VIC address range to the high bits of __VIDRAM_START__. ; PC6/PC7 (VICBANKSEL 0/1) = 11 ldy #TPI::PRC lda (tpi2),y sta vidsave+1 and #$3F ora #<((>__VIDRAM_START__) & $C0) sta (tpi2),y ; Set the VIC base address register to the addresses of the video and ; character RAM. ldy #VIC_VIDEO_ADR lda (vic),y sta vidsave+2 and #$01 ora #<(((__VIDRAM_START__ >> 6) & $F0) | ((__CHARRAM_START__ >> 10) & $0E) | $02) ; and #$0F ; ora #<(((>__VIDRAM_START__) << 2) & $F0) sta (vic),y ; Switch back to the execution bank. lda ExecReg sta IndReg ; Activate the chained interrupt handlers; then, enable interrupts. lda #.lobyte(__INTERRUPTOR_COUNT__*2) sta irqcount cli ; Call module constructors. jsr initlib ; Push the command-line arguments; and, call main(). jsr callmain ; Call the module destructors. This is also the exit() entry and the default entry ; point for the break vector. _exit: pha ; Save the return code on stack jsr donelib ; Run module destructors lda #$00 sta irqcount ; Disable custom irq handlers ; Address the system bank. lda #$0F sta IndReg ; Switch back the video to the system bank. ldy #TPI::CR lda vidsave+0 sta (tpi1),y ldy #TPI::PRC lda vidsave+1 sta (tpi2),y ldy #VIC_VIDEO_ADR lda vidsave+2 sta (vic),y ; Copy stuff back from our zero-page to the system's. .if 0 lda #.sizeof(transfer_table) sta ktmp @L0: ldx ktmp ldy transfer_table-2,x lda transfer_table-1,x tax lda $00,x sta (sysp0),y dec ktmp dec ktmp bne @L0 .endif ; Place the program return code into BASIC's status variable. pla ldy #$9C ; ST sta (sysp0),y ; Set up the welcome code at the stack bottom in the system bank. ldy #$FF lda (sysp1),y ; Load system bank sp tax iny ; Y = 0 lda #$58 ; CLI opcode sta (sysp1),y iny lda #$60 ; RTS opcode sta (sysp1),y lda IndReg sei txs jmp Back ; ------------------------------------------------------------------------- ; The IRQ handler goes into PAGE2. For performance reasons, and to allow ; easier chaining, we do handle the IRQs in the execution bank (instead of ; passing them to the system bank). ; This is the mapping of the active IRQ register of the 6525 (tpi1): ; ; Bit 7 6 5 4 3 2 1 0 ; | | | | ^ 50 Hz. ; | | | ^ SRQ IEEE 488 ; | | ^ CIA ; | ^ IRQB ext. Port ; ^ ACIA irq: pha txa pha tya pha lda IndReg pha lda ExecReg sta IndReg ; Be sure to address our segment tsx lda $105,x ; Get the flags from the stack and #$10 ; Test break flag bne dobrk ; It's an IRQ. cld ; Call the chained IRQ handlers. ldy irqcount beq irqskip jsr callirq_y ; Call the functions ; Done with the chained IRQ handlers; check the TPI for IRQs, and handle them. irqskip:lda #$0F sta IndReg ldy #TPI::AIR lda (tpi1),y ; Interrupt Register 6525 beq noirq ; 50/60Hz. interrupt cmp #%00000001 ; ticker IRQ? bne irqend jsr scnkey ; Poll the keyboard jsr UDTIM ; Bump the time ; Done. irqend: ldy #TPI::AIR sta (tpi1),y ; Clear interrupt noirq: pla sta IndReg pla tay pla tax pla nmi: rti dobrk: jmp (BRKVec) ; ------------------------------------------------------------------------- ; Data area .data vidsave:.res 3 .bss irqcount: .byte 0