;
-; 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 __hinit, push0, doatexit, _main
- .import initconio
- .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
-
-sp = $02 ; stack pointer
-sreg = $04 ; secondary register/high 16 bit for longs
-regsave = $06 ; slot to save/restore (E)AX into
-ptr1 = $0A ;
-ptr2 = $0C
-ptr3 = $0E
-ptr4 = $10
-tmp1 = $12
-tmp2 = $13
-tmp3 = $14
-tmp4 = $15
-regbank = $16 ; 6 byte register bank
-zpspace = $1A ; Zero page space allocated
; ------------------------------------------------------------------------
; BASIC header and a small BASIC program. Since it is not possible to start
; 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
; 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 transfered from the system zero
+; page into out 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"
+
+.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.
- .res $F8-*
-Back: ldx spsave
- txs
- lda IndReg
- sta ExecReg
+.segment "STARTUP"
+
+Back: sta ExecReg
-; 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:
+; 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
+ 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)!
+; 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.
- tsx
- stx spsave ; Save the system stackpointer
- ldx #$FF
- txs ; Set up our own stack
+ jmp Origin
-; Set the interrupt, NMI and other vectors
+; Hardware vectors, copied to $FFF6
- ldy #vectable_size
-L0: lda vectable-1,y
- sta $FF80,y
- dey
- bne L0
+.proc vectors
+ sta ExecReg
+ rts
+ nop
+ .word nmi ; NMI vector
+ .word 0 ; Reset - not used
+ .word irq ; IRQ vector
+.endproc
+
+; Initializers for the extended zeropage. 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
- lda #$0F
- sta IndReg
-
-; Copy the kernal zero page ($90-$F2) from the system bank
-
- 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
+Origin: lda #$0F
+ sta IndReg
-; Set the indirect segment to bank we're executing in
+; Initialize the extended zeropage
- lda ExecReg
- sta IndReg
+ ldx #.sizeof(extzp)-1
+L1: lda extzp,x
+ sta <__EXTZP_RUN__,x
+ dex
+ bpl L1
-; 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,
-; it's reused later.
+; Save the old stack pointer from the system bank and setup our hw sp
- lda #<__BSS_RUN__
- sta ptr1
- lda #>__BSS_RUN__
- sta ptr1+1
- lda #0
- tay
+ 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
-; Clear full pages
+; Copy stuff from the system zeropage to ours
- ldx #>__BSS_SIZE__
- beq Z2
-Z1: sta (ptr1),y
- iny
- bne Z1
- inc ptr1+1 ; Next page
- dex
- bne Z1
+ 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
-; Clear the remaining page
+; Set the interrupt, NMI and other vectors
-Z2: ldx #<__BSS_SIZE__
- beq Z4
-Z3: sta (ptr1),y
- iny
- dex
- bne Z3
-Z4:
+ ldx #.sizeof(vectors)-1
+L3: lda vectors,x
+ sta $10000 - .sizeof(vectors),x
+ dex
+ bpl L3
; Setup the C stack
- lda #<$FF81
- sta sp
- lda #>$FF81
- sta sp+1
-
-; We expect to be in page 2 now
+ lda #.lobyte(callbank15::entry)
+ sta sp
+ lda #.hibyte(callbank15::entry)
+ sta sp+1
+
+; Setup 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
+
+; Setup 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
-.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.
+; Set the indirect segment to bank we're executing in
- jsr __hinit ; Initialize the heap
- jsr initconio ; Initialize conio stuff
+ lda ExecReg
+ sta IndReg
-; Create the (empty) command line for the program
+; 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,
+; it's reused later.
- jsr push0 ; argc
- jsr push0 ; argv
+ lda #<__BSS_RUN__
+ sta ptr1
+ lda #>__BSS_RUN__
+ sta ptr1+1
+ lda #0
+ tay
-; Execute the program code
+; Clear full pages
- jmp Start
+ ldx #>__BSS_SIZE__
+ beq Z2
+Z1: sta (ptr1),y
+ iny
+ bne Z1
+ inc ptr1+1 ; Next page
+ dex
+ bne Z1
-; ------------------------------------------------------------------------
-; 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
+; 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 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 arguments and call main()
-; Fall thru to exit.
+ jsr callmain
-_exit: jsr doatexit ; call exit functions
+; Call 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 zeropage to the systems
-; ------------------------------------------------------------------------
-; 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 ST
-; ------------------------------------------------------------------------
-; 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
+; Setup 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
+; 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).
- .reloc
+; 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 chained IRQ handlers
+
+ ldy irqcount
+ beq irqskip
+ jsr callirq_y ; Call the functions
+
+; Done with 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
-spsave: .res 1
+; -------------------------------------------------------------------------
+; Data area.
+.bss
+irqcount: .byte 0