2 ; Startup code for cc65 (CBM 500 version)
4 ; This must be the *first* file on the linker command line
9 .import _clrscr, initlib, donelib, callirq_y
10 .import push0, callmain
11 .import __CHARRAM_START__, __CHARRAM_SIZE__, __VIDRAM_START__
12 .import __BSS_RUN__, __BSS_SIZE__, __EXTZP_RUN__
13 .import __INTERRUPTOR_COUNT__
16 .include "zeropage.inc"
21 ; ------------------------------------------------------------------------
22 ; BASIC header and a small BASIC program. Since it is not possible to start
23 ; programs in other banks using SYS, the BASIC program will write a small
24 ; machine code program into memory at $100 and start that machine code
25 ; program. The machine code program will then start the machine language
26 ; code in bank 0, which will initialize the system by copying stuff from
27 ; the system bank, and start the application.
29 ; Here's the basic program that's in the following lines:
36 ; 60 data 120,169,0,133,0
38 ; The machine program in the data lines is:
42 ; sta $00 <-- Switch to bank 0 after this command
44 ; Initialization is not only complex because of the jumping from one bank
45 ; into another. but also because we want to save memory, and because of
46 ; this, we will use the system memory ($00-$3FF) for initialization stuff
47 ; that is overwritten later.
52 .byte $03,$00,$11,$00,$0a,$00,$81,$20,$49,$b2,$30,$20,$a4,$20,$34,$00
53 .byte $19,$00,$14,$00,$87,$20,$4a,$00,$27,$00,$1e,$00,$97,$20,$32,$35
54 .byte $36,$aa,$49,$2c,$4a,$00,$2f,$00,$28,$00,$82,$20,$49,$00,$39,$00
55 .byte $32,$00,$9e,$20,$32,$35,$36,$00,$4f,$00,$3c,$00,$83,$20,$31,$32
56 .byte $30,$2c,$31,$36,$39,$2c,$30,$2c,$31,$33,$33,$2c,$30,$00,$00,$00
58 ;------------------------------------------------------------------------------
59 ; A table that contains values that must be transfered from the system zero
60 ; page into out zero page. Contains pairs of bytes, first one is the address
61 ; in the system ZP, second one is our ZP address. The table goes into page 2,
62 ; but is declared here, because it is needed earlier.
75 ;------------------------------------------------------------------------------
76 ; Page 3 data. This page contains the break vector and the bankswitch
77 ; subroutine that is copied into high memory on startup. The space occupied by
78 ; this routine will later be used for a copy of the bank 15 stack. It must be
79 ; saved, since we're going to destroy it when calling bank 15.
83 BRKVec: .addr _exit ; BRK indirect vector
107 lda #.hibyte(excrts-1)
110 lda #.lobyte(excrts-1)
116 sta $1FF ; Save new sp
144 ldy $1FF ; Restore sp in bank 15
146 lda #.hibyte(expull-1)
149 lda #.lobyte(expull-1)
170 .if (expull <> $FF26)
171 .error "Symbol expull must be aligned with kernal in bank 15"
178 ;------------------------------------------------------------------------------
179 ; The code in the target bank when switching back will be put at the bottom
180 ; of the stack. We will jump here to switch segments. The range $F2..$FF is
181 ; not used by any kernal routine.
187 ; We are at $100 now. The following snippet is a copy of the code that is poked
188 ; in the system bank memory by the basic header program, it's only for
189 ; documentation and not actually used here:
195 ; This is the actual starting point of our code after switching banks for
196 ; startup. Beware: The following code will get overwritten as soon as we
197 ; use the stack (since it's in page 1)! We jump to another location, since
198 ; we need some space for subroutines that aren't used later.
202 ; Hardware vectors, copied to $FFFA
208 .word nmi ; NMI vector
209 .word 0 ; Reset - not used
210 .word irq ; IRQ vector
213 ; Initializers for the extended zeropage. See extzp.s
231 ; Switch the indirect segment to the system bank
236 ; Initialize the extended zeropage
238 ldx #.sizeof(extzp)-1
244 ; Save the old stack pointer from the system bank and setup our hw sp
249 sta (sysp1),y ; Save system stack point into $F:$1FF
250 ldx #$FE ; Leave $1FF untouched for cross bank calls
251 txs ; Set up our own stack
253 ; Copy stuff from the system zeropage to ours
255 lda #.sizeof(transfer_table)
258 ldy transfer_table-2,x
259 lda transfer_table-1,x
267 ; Set the interrupt, NMI and other vectors
269 ldx #.sizeof(vectors)-1
271 sta $10000 - .sizeof(vectors),x
277 lda #.lobyte(callbank15::entry)
279 lda #.hibyte(callbank15::entry)
282 ; Setup the subroutine and jump vector table that redirects kernal calls to
285 ldy #.sizeof(callbank15)
286 @L1: lda callbank15-1,y
287 sta callbank15::entry-1,y
291 ; Setup the jump vector table. Y is zero on entry.
293 ldx #45-1 ; Number of vectors
294 @L2: lda #$20 ; JSR opcode
297 lda #.lobyte(callbank15::entry)
300 lda #.hibyte(callbank15::entry)
306 ; Set the indirect segment to bank we're executing in
311 ; Zero the BSS segment. We will do that here instead calling the routine
312 ; in the common library, since we have the memory anyway, and this way,
329 inc ptr1+1 ; Next page
333 ; Clear the remaining page
335 Z2: ldx #<__BSS_SIZE__
343 ; ------------------------------------------------------------------------
344 ; We are at $200 now. We may now start calling subroutines safely, since
345 ; the code we execute is no longer in the stack page.
349 ; Copy the character rom from the system bank into the execution bank
355 lda #<__CHARRAM_START__
357 lda #>__CHARRAM_START__
359 lda #>__CHARRAM_SIZE__ ; 16 * 256 bytes to copy
363 sta IndReg ; Access the system bank
365 sta __VIDRAM_START__,y
370 ccopy2: lda __VIDRAM_START__,y
375 inc ptr2+1 ; Bump high pointer bytes
379 ; Clear the video memory. We will do this before switching the video to bank 0
380 ; to avoid garbage when doing so.
384 ; Reprogram the VIC so that the text screen and the character ROM is in the
385 ; execution bank. This is done in three steps:
387 lda #$0F ; We need access to the system bank
390 ; Place the VIC video RAM into bank 0
401 ; Set bit 14/15 of the VIC address range to the high bits of __VIDRAM_START__
402 ; PC6/PC7 (VICBANKSEL 0/1) = 11
408 ora #<((>__VIDRAM_START__) & $C0)
411 ; Set the VIC base address register to the addresses of the video and
418 ora #<(((__VIDRAM_START__ >> 6) & $F0) | ((__CHARRAM_START__ >> 10) & $0E) | $02)
420 ; ora #<(((>__VIDRAM_START__) << 2) & $F0)
423 ; Switch back to the execution bank
428 ; Call module constructors, enable chained IRQs afterwards.
431 lda #.lobyte(__INTERRUPTOR_COUNT__*2)
438 ; Push arguments and call main()
442 ; Call module destructors. This is also the _exit entry and the default entry
443 ; point for the break vector.
445 _exit: pha ; Save the return code on stack
447 sta irqcount ; Disable custom irq handlers
448 jsr donelib ; Run module destructors
450 ; Address the system bank
455 ; Switch back the video to the system bank
469 ; Copy stuff back from our zeropage to the systems
472 lda #.sizeof(transfer_table)
475 ldy transfer_table-2,x
476 lda transfer_table-1,x
485 ; Place the program return code into ST
491 ; Setup the welcome code at the stack bottom in the system bank.
494 lda (sysp1),y ; Load system bank sp
497 lda #$58 ; CLI opcode
500 lda #$60 ; RTS opcode
507 ; -------------------------------------------------------------------------
508 ; The IRQ handler goes into PAGE2. For performance reasons, and to allow
509 ; easier chaining, we do handle the IRQs in the execution bank (instead of
510 ; passing them to the system bank).
512 ; This is the mapping of the active irq register of the 6525 (tpi1):
514 ; Bit 7 6 5 4 3 2 1 0
516 ; | | | ^ SRQ IEEE 488
529 sta IndReg ; Be sure to address our segment
531 lda $105,x ; Get the flags from the stack
532 and #$10 ; Test break flag
539 ; Call chained IRQ handlers
543 jsr callirq_y ; Call the functions
545 ; Done with chained IRQ handlers, check the TPI for IRQs and handle them
550 lda (tpi1),y ; Interrupt Register 6525
555 cmp #%00000001 ; ticker irq?
557 jsr scnkey ; Poll the keyboard
558 jsr UDTIM ; Bump the time
562 irqend: ldy #TPI::AIR
563 sta (tpi1),y ; Clear interrupt
576 ; -------------------------------------------------------------------------