;
-; Startup code for cc65 (Plus/4 version)
-;
-; This must be the *first* file on the linker command line
+; Startup code for cc65 (CBM 600/700 version)
;
- .export _exit
- .import initlib, donelib
- .import push0, _main
- .import __BSS_RUN__, __BSS_SIZE__
- .import irq, nmi
- .import k_irq, k_nmi, k_plot, k_udtim, k_scnkey
+ .export _exit, BRKVec
+ .export __STARTUP__ : absolute = 1 ; Mark as startup
- .include "zeropage.inc"
- .include "io.inc"
+ .import callirq_y, initlib, donelib
+ .import push0, callmain
+ .import __BSS_RUN__, __BSS_SIZE__, __EXTZP_RUN__
+ .import __INTERRUPTOR_COUNT__
+ .import scnkey, UDTIM
+ .include "zeropage.inc"
+ .include "extzp.inc"
+ .include "cbm610.inc"
-; ------------------------------------------------------------------------
-; Define and export the ZP variables for the CBM610 runtime
-
- .exportzp sp, sreg, regsave
- .exportzp ptr1, ptr2, ptr3, ptr4
- .exportzp tmp1, tmp2, tmp3, tmp4
- .exportzp regbank, zpspace
- .exportzp crtc, sid, IPCcia, cia, acia, tpi1, tpi2
- .exportzp ktab1, ktab2, ktab3, ktab4, time, RecvBuf, SendBuf
-
-.zeropage
-
-zpstart = *
-sp: .res 2 ; Stack pointer
-sreg: .res 2 ; Secondary register/high 16 bit for longs
-regsave: .res 2 ; slot to save/restore (E)AX into
-ptr1: .res 2
-ptr2: .res 2
-ptr3: .res 2
-ptr4: .res 2
-tmp1: .res 1
-tmp2: .res 1
-tmp3: .res 1
-tmp4: .res 1
-regbank: .res 6 ; 6 byte register bank
-
-zpspace = * - zpstart ; Zero page space allocated
-
-.code
; ------------------------------------------------------------------------
-; BASIC header and a small BASIC program. Since it is not possible to start
+; 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
+; 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 1, 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:
+; Here's the BASIC program that's in the following lines:
;
; 10 for i=0 to 4
; 20 read j
; The machine program in the data lines is:
;
; sei
-; lda #$01
-; sta $00 <-- Switch to bank 1 after this command
+; lda #$01
+; sta $00 <-- Switch to bank 1 after this command
;
-; Initialization is not only complex because of the jumping from one bank
-; into another. but also because we want to save memory, and because of
-; this, we will use the system memory ($00-$3FF) for initialization stuff
+; 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.
;
-; To make things more simple, make the code of this module absolute.
-
- .org $0001
-Head: .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,$31,$2c,$31,$33,$33,$2c,$30,$00,$00,$00
-
-; Since we need some vectors to access stuff in the system bank for our own,
-; we will include them here, starting from $60:
+.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,$31,$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 $CC, graphmode
+ .byte $D4, config
+
+.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 := $FF05 ; In bank 15 ROM
+
+.org $FECB
+
+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
- .res $60-*
+.if (expull <> $FF2E)
+.error "Symbol expull must be aligned with Kernal in bank 15"
+.endif
-crtc: .word $d800
-sid: .word $da00
-IPCcia: .word $db00
-cia: .word $dc00
-acia: .word $dd00
-tpi1: .word $de00
-tpi2: .word $df00
-ktab1: .word $ea29
-ktab2: .word $ea89
-ktab3: .word $eae9
-ktab4: .word $eb49
-time: .dword $0000
-RecvBuf: .word $0100 ; RS232 received buffer
-SendBuf: .word $0200 ; RS232 send buffer
+.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.
+; not used by any Kernal routine.
- .res $F8-*
-Back: ldx spsave
- txs
- lda IndReg
- sta ExecReg
+.segment "STARTUP"
-; The following code 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:
+Back: sta ExecReg
- sei
- lda #$01
- 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 #$01
+ 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)!
-
- tsx
- stx spsave ; Save the system stackpointer
- ldx #$FF
- txs ; Set up our own stack
-
-; Set the interrupt, NMI and other vectors
-
- ldy #vectable_size
-L0: lda vectable-1,y
- sta $FF80,y
- dey
- bne L0
-
-; Switch the indirect segment to the system bank
+; 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.
- lda #$0F
- sta IndReg
+ jmp Origin
-; Copy the kernal zero page ($90-$F2) from the system bank
+; Hardware vectors, copied to $FFF6
- lda #$90
- sta ptr1
- lda #$00
- sta ptr1+1
- ldy #$62-1
-L1: lda (ptr1),y
- sta $90,y
- dey
- bpl L1
-
-; Copy the page 3 vectors in place
-
- ldy #$00
-L2: lda p3vectable,y
- sta $300,y
- iny
- cpy #p3vectable_size
- bne L2
-
-; Copy the rest of page 3 from the system bank
-
- lda #$00
- sta ptr1
- lda #$03
- sta ptr1+1
-L3: lda (ptr1),y
- sta $300,y
- iny
- bne L3
-
-; Set the indirect segment to bank we're executing in
-
- lda ExecReg
- sta IndReg
-
-; Zero the BSS segment. We will do that here instead calling the routine
-; in the common library, since we have the memory anyway, and this way,
+.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 ; crtc
+ .word $da00 ; sid
+ .word $db00 ; ipccia
+ .word $dc00 ; cia
+ .word $dd00 ; acia
+ .word $de00 ; tpi1
+ .word $df00 ; tpi2
+ .word $ea29 ; ktab1
+ .word $ea89 ; ktab2
+ .word $eae9 ; ktab3
+ .word $eb49 ; 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:
-
-; Setup the C stack
-
- lda #<$FF81
- sta sp
- lda #>$FF81
- sta sp+1
-
-; We expect to be in page 2 now
-
-.if (* < $1FD)
- jmp $200
- .res $200-*
-.endif
-.if (* < $200)
- .res $200-*,$EA
-.endif
-.if (* >= $2F0)
-.error "Code range invalid"
-.endif
-
-; This code is in page 2, so we may now start calling subroutines safely,
-; since the code we execute is no longer in the stack page.
-; Call module constructors
-
- jsr initlib
-
-; Create the (empty) command line for the program
-
- jsr push0 ; argc
- jsr push0 ; argv
-
-; Execute the program code
-
- jmp Start
-
-; ------------------------------------------------------------------------
-; Additional data that we need for initialization and that's overwritten
-; later
-
-vectable:
- jmp $0000 ; CINT
- jmp $0000 ; IOINIT
- jmp $0000 ; RAMTAS
- jmp $0000 ; RESTOR
- jmp $0000 ; VECTOR
- jmp $0000 ; SETMSG
- jmp $0000 ; SECOND
- jmp $0000 ; TKSA
- jmp $0000 ; MEMTOP
- jmp $0000 ; MEMBOT
- jmp k_scnkey ; SCNKEY
- jmp $0000 ; SETTMO
- jmp $0000 ; ACPTR
- jmp $0000 ; CIOUT
- jmp $0000 ; UNTLK
- jmp $0000 ; UNLSN
- jmp $0000 ; LISTEN
- jmp $0000 ; TALK
- jmp $0000 ; READST
- jmp k_setlfs ; SETLFS
- jmp k_setnam ; SETNAM
- jmp $0000 ; OPEN
- jmp $0000 ; CLOSE
- jmp $0000 ; CHKIN
- jmp $0000 ; CKOUT
- jmp $0000 ; CLRCH
- jmp $0000 ; BASIN
- jmp $0000 ; BSOUT
- jmp $0000 ; LOAD
- jmp $0000 ; SAVE
- jmp k_settim ; SETTIM
- jmp k_rdtim ; RDTIM
- jmp $0000 ; STOP
- jmp $0000 ; GETIN
- jmp $0000 ; CLALL
- jmp k_udtim ; UDTIM
- jmp k_screen ; SCREEN
- jmp k_plot ; PLOT
- jmp k_iobase ; IOBASE
- sta ExecReg
- rts
- .byte $01 ; Filler
- .word nmi
- .word 0 ; Reset - not used
- .word irq
-vectable_size = * - vectable
-
-p3vectable:
- .word k_irq ; IRQ user vector
- .word k_brk ; BRK user vector
- .word k_nmi ; NMI user vector
-p3vectable_size = * - p3vectable
-
+ 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
; ------------------------------------------------------------------------
-; This is the program code after setup. It starts at $400
+; We are at $200 now. We may now start calling subroutines safely since
+; the code we execute is no longer in the stack page.
- .res $400-*
+.segment "PAGE2"
-Start:
+; Activate the chained interrupt handlers; then, enable interrupts.
-; Enable interrupts
+Init: lda #.lobyte(__INTERRUPTOR_COUNT__*2)
+ sta irqcount
+ cli
- cli
+; Call module constructors.
-; Call the user code
+ jsr initlib
- ldy #4 ; Argument size
- jsr _main ; call the users code
+; Push the command-line arguments; and, call main().
-; Call module destructors. This is also the _exit entry.
+ jsr callmain
-_exit: jsr donelib ; Run module destructors
+; Call the module destructors. This is also the exit() entry and the default entry
+; point for the break vector.
-; Clear the start of the zero page, since it will be interpreted as a
-; (garbage) BASIC program otherwise. This is also the default entry for
-; the break vector.
+_exit: pha ; Save the return code
+ jsr donelib ; Run module destructors
+ lda #$00
+ sta irqcount ; Disable custom irq handlers
-k_brk: sei
- lda #$00
- ldx #$3E
-Clear: sta $02,x
- dex
- bne Clear
+; Address the system bank.
-; Setup the welcome code at the stack bottom in the system bank. Use
-; the F4/F5 vector to access the system bank
+ lda #$0F
+ sta IndReg
- lda #$0F
- sta IndReg
- ldy #$00
- sty $F4
- iny
- sty $F5
- ldy #reset_size-1
-@L1: lda reset,y
- sta ($F4),y
- dey
- bne @L1
- jmp Back
+; Copy stuff back from our zero-page to the system's.
-; ------------------------------------------------------------------------
-; Code that is copied into the system bank at $100 when switching back
+.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
-reset: cli
- jmp $8000 ; BASIC cold start
-reset_size = * - reset
+; Place the program return code into BASIC's status variable.
-; ------------------------------------------------------------------------
-; Code for a few simpler kernal calls goes here
-
-k_iobase:
- ldx cia
- ldy cia+1
- rts
-
-k_screen:
- ldx #80 ; Columns
- ldy #25 ; Lines
- rts
-
-k_setlfs:
- sta LogicalAdr
- stx FirstAdr
- sty SecondAdr
- rts
+ pla
+ ldy #$9C ; ST
+ sta (sysp0),y
-k_setnam:
- sta FileNameLen
- lda $00,x
- sta FileNameAdrLo
- lda $01,x
- sta FileNameAdrHi
- lda $02,x
- sta FileNameAdrSeg
- rts
+; Set up the welcome code at the stack bottom in the system bank.
-k_rdtim:
- sei
- lda time+0
- ldx time+1
- ldy time+2
- cli
- rts
-
-k_settim:
- sei
- sta time+0
- stx time+1
- sty time+2
- cli
- rts
+ 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
; -------------------------------------------------------------------------
-; Data area - switch back to relocatable mode
-
- .reloc
+; 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).
-.data
-spsave: .res 1
+; 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
+.bss
+irqcount: .byte 0