mirror of
https://github.com/cc65/cc65.git
synced 2024-08-19 17:29:37 +00:00
The CBM510 runs programs in a non-system memory bank. It has its own zero page. Some things are copied from the system zero page; but, the cursor flags weren't copied. So, the cursor always blinked. That bug sometimes left cursor ghosts (reversed spaces) at the end of lines.
588 lines
15 KiB
ArmAsm
588 lines
15 KiB
ArmAsm
;
|
|
; 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"
|
|
|
|
|
|
; ------------------------------------------------------------------------
|
|
; BASIC header and a small BASIC program. Since it is not 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 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
|
|
; 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 transfered 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"
|
|
|
|
.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 $FFFA
|
|
|
|
.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 ; 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 zeropage
|
|
|
|
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 setup 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 zeropage 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
|
|
|
|
; Setup the C stack
|
|
|
|
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
|
|
|
|
; 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,
|
|
; 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 is 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 chained interrupt handlers, then enable interrupts.
|
|
|
|
lda #.lobyte(__INTERRUPTOR_COUNT__*2)
|
|
sta irqcount
|
|
cli
|
|
|
|
; Call module constructors.
|
|
|
|
jsr initlib
|
|
|
|
; Push arguments and call main()
|
|
|
|
jsr callmain
|
|
|
|
; Call 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 zeropage to the systems
|
|
|
|
.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 ST
|
|
|
|
pla
|
|
ldy #$9C ; ST
|
|
sta (sysp0),y
|
|
|
|
; Setup 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 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 area
|
|
|
|
.data
|
|
vidsave:.res 3
|
|
|
|
.bss
|
|
irqcount: .byte 0
|
|
|
|
|