2 ; Startup code for cc65 (CBM 500 version)
6 .export __STARTUP__ : absolute = 1 ; Mark as startup
8 .import _clrscr, initlib, donelib, callirq_y
9 .import push0, callmain
10 .import __CHARRAM_START__, __CHARRAM_SIZE__, __VIDRAM_START__
11 .import __BSS_RUN__, __BSS_SIZE__, __EXTZP_RUN__
12 .import __INTERRUPTOR_COUNT__
15 .include "zeropage.inc"
20 ; ------------------------------------------------------------------------
21 ; The BASIC header and a small BASIC program. Since it isn't possible to start
22 ; programs in other banks using SYS, the BASIC program will write a small
23 ; machine code program into memory at $100; and, start that machine code
24 ; program. The machine code program will then start the machine language
25 ; code in bank 0, which will initialize the system by copying stuff from
26 ; the system bank, and start the application.
28 ; Here's the BASIC program that's in the following lines:
35 ; 60 data 120,169,0,133,0
37 ; The machine program in the data lines is:
41 ; sta $00 <-- Switch to bank 0 after this command
43 ; Initialization is complex not only because of the jumping from one bank
44 ; into another, but also because we want to save memory; and because of
45 ; that, we will use the system memory ($00-$3FF) for initialization stuff
46 ; that is overwritten later.
51 .byte $03,$00,$11,$00,$0a,$00,$81,$20,$49,$b2,$30,$20,$a4,$20,$34,$00
52 .byte $19,$00,$14,$00,$87,$20,$4a,$00,$27,$00,$1e,$00,$97,$20,$32,$35
53 .byte $36,$aa,$49,$2c,$4a,$00,$2f,$00,$28,$00,$82,$20,$49,$00,$39,$00
54 .byte $32,$00,$9e,$20,$32,$35,$36,$00,$4f,$00,$3c,$00,$83,$20,$31,$32
55 .byte $30,$2c,$31,$36,$39,$2c,$30,$2c,$31,$33,$33,$2c,$30,$00,$00,$00
57 ;------------------------------------------------------------------------------
58 ; A table that contains values that must be transferred from the system zero-
59 ; page into our zero-page. Contains pairs of bytes; first one is the address
60 ; in the system ZP, second one is our ZP address. The table goes into page 2;
61 ; but, is declared here because it is needed earlier.
65 ; (We use .proc because we need both a label and a scope.)
79 ;------------------------------------------------------------------------------
80 ; Page 3 data. This page contains the break vector and the bankswitch
81 ; subroutine that is copied into high memory on startup. The space occupied by
82 ; this routine will later be used for a copy of the bank 15 stack. It must be
83 ; saved since we're going to destroy it when calling bank 15.
87 BRKVec: .addr _exit ; BRK indirect vector
111 lda #.hibyte(excrts-1)
114 lda #.lobyte(excrts-1)
120 sta $1FF ; Save new sp
148 ldy $1FF ; Restore sp in bank 15
150 lda #.hibyte(expull-1)
153 lda #.lobyte(expull-1)
174 .if (expull <> $FF26)
175 .error "Symbol expull must be aligned with Kernal in bank 15"
182 ;------------------------------------------------------------------------------
183 ; The code in the target bank when switching back will be put at the bottom
184 ; of the stack. We will jump here to switch segments. The range $F2..$FF is
185 ; not used by any Kernal routine.
191 ; We are at $100 now. The following snippet is a copy of the code that is poked
192 ; in the system bank memory by the BASIC header program; it's only for
193 ; documentation, and not actually used here:
199 ; This is the actual starting point of our code after switching banks for
200 ; startup. Beware: The following code will get overwritten as soon as we
201 ; use the stack (since it's in page 1)! We jump to another location since
202 ; we need some space for subroutines that aren't used later.
206 ; Hardware vectors, copied to $FFF6
212 .word nmi ; NMI vector
213 .word 0 ; Reset -- not used
214 .word irq ; IRQ vector
217 ; Initializers for the extended zero-page. See "extzp.s".
235 ; Switch the indirect segment to the system bank.
240 ; Initialize the extended zero-page.
242 ldx #.sizeof(extzp)-1
248 ; Save the old stack pointer from the system bank; and, set up our hw sp.
253 sta (sysp1),y ; Save system stack point into $F:$1FF
254 ldx #$FE ; Leave $1FF untouched for cross-bank calls
255 txs ; Set up our own stack
257 ; Copy stuff from the system zero-page to ours.
259 lda #.sizeof(transfer_table)
262 ldy transfer_table-2,x
263 lda transfer_table-1,x
271 ; Set the interrupt, NMI, and other vectors.
273 ldx #.sizeof(vectors)-1
275 sta $10000 - .sizeof(vectors),x
279 ; Set up the C stack.
281 lda #.lobyte(callbank15::entry)
283 lda #.hibyte(callbank15::entry)
286 ; Set up the subroutine and jump vector table that redirects Kernal calls to
289 ldy #.sizeof(callbank15)
290 @L1: lda callbank15-1,y
291 sta callbank15::entry-1,y
295 ; Set up the jump vector table. Y is zero on entry.
297 ldx #45-1 ; Number of vectors
298 @L2: lda #$20 ; JSR opcode
301 lda #.lobyte(callbank15::entry)
304 lda #.hibyte(callbank15::entry)
310 ; Set the indirect segment to the bank that we're executing in.
315 ; Zero the BSS segment. We will do that here instead of calling the routine
316 ; in the common library, since we have the memory anyway; and this way,
333 inc ptr1+1 ; Next page
337 ; Clear the remaining page.
339 Z2: ldx #<__BSS_SIZE__
347 ; ------------------------------------------------------------------------
348 ; We are at $200 now. We may now start calling subroutines safely since
349 ; the code we execute is no longer in the stack page.
353 ; Copy the character ROM from the system bank into the execution bank.
359 lda #<__CHARRAM_START__
361 lda #>__CHARRAM_START__
363 lda #>__CHARRAM_SIZE__ ; 16 * 256 bytes to copy
367 sta IndReg ; Access the system bank
369 sta __VIDRAM_START__,y
374 ccopy2: lda __VIDRAM_START__,y
379 inc ptr2+1 ; Bump high pointer bytes
383 ; Clear the video memory. We will do this before switching the video to bank 0,
384 ; to avoid garbage when doing so.
388 ; Reprogram the VIC so that the text screen and the character ROM are in the
389 ; execution bank. This is done in three steps:
391 lda #$0F ; We need access to the system bank
394 ; Place the VIC video RAM into bank 0.
405 ; Set bit 14/15 of the VIC address range to the high bits of __VIDRAM_START__.
406 ; PC6/PC7 (VICBANKSEL 0/1) = 11
412 ora #<((>__VIDRAM_START__) & $C0)
415 ; Set the VIC base address register to the addresses of the video and
422 ora #<(((__VIDRAM_START__ >> 6) & $F0) | ((__CHARRAM_START__ >> 10) & $0E) | $02)
424 ; ora #<(((>__VIDRAM_START__) << 2) & $F0)
427 ; Switch back to the execution bank.
432 ; Activate the chained interrupt handlers; then, enable interrupts.
434 lda #.lobyte(__INTERRUPTOR_COUNT__*2)
438 ; Call module constructors.
442 ; Push the command-line arguments; and, call main().
446 ; Call the module destructors. This is also the exit() entry and the default entry
447 ; point for the break vector.
449 _exit: pha ; Save the return code on stack
450 jsr donelib ; Run module destructors
452 sta irqcount ; Disable custom irq handlers
454 ; Address the system bank.
459 ; Switch back the video to the system bank.
473 ; Copy stuff back from our zero-page to the system's.
476 lda #.sizeof(transfer_table)
479 ldy transfer_table-2,x
480 lda transfer_table-1,x
489 ; Place the program return code into BASIC's status variable.
495 ; Set up the welcome code at the stack bottom in the system bank.
498 lda (sysp1),y ; Load system bank sp
501 lda #$58 ; CLI opcode
504 lda #$60 ; RTS opcode
511 ; -------------------------------------------------------------------------
512 ; The IRQ handler goes into PAGE2. For performance reasons, and to allow
513 ; easier chaining, we do handle the IRQs in the execution bank (instead of
514 ; passing them to the system bank).
516 ; This is the mapping of the active IRQ register of the 6525 (tpi1):
518 ; Bit 7 6 5 4 3 2 1 0
520 ; | | | ^ SRQ IEEE 488
533 sta IndReg ; Be sure to address our segment
535 lda $105,x ; Get the flags from the stack
536 and #$10 ; Test break flag
543 ; Call the chained IRQ handlers.
547 jsr callirq_y ; Call the functions
549 ; Done with the chained IRQ handlers; check the TPI for IRQs, and handle them.
554 lda (tpi1),y ; Interrupt Register 6525
559 cmp #%00000001 ; ticker IRQ?
561 jsr scnkey ; Poll the keyboard
562 jsr UDTIM ; Bump the time
566 irqend: ldy #TPI::AIR
567 sta (tpi1),y ; Clear interrupt
580 ; -------------------------------------------------------------------------