Now that I have the machine starting with the stock C64 kernel, I want to get it booting with the C65 ROM, which means the modified C65 C64-mode kernel.
This isn't just for C65 compatibility, but it also provides a nice way to integrate microSD storage, because I can override the internal DOS so that it uses the microSD connector instead of the floppy controller.
To do this, I need to implement some of the extra 4510 opcodes, as well as some of the other C65 memory mapper and other control features.
What I didn't know was what extra instructions and features I would need to implement to facilitate this. So I went looking for a disassembly of the C65 ROMs, but couldn't find any. So I set about making my own, using Marko Makela's well known C64 kernel disassembly as the reference, and making note of all the changes.
I could have done it automatically (although the extra opcodes would have complicated this), but I also wanted to gain a clear understanding of how the C65 modified kernel works, and how it intercepts DOS.
The whole process took a few hours to exhaustively map the differences.
The changes basically consisted of throwing out the cassette routines and putting in sufficient intercepts. Other smaller changes include making 8 the default device number:
; set parameters for load/verify/save
E1D4 A9 00 LDA #$00
E1D6 20 BD FF JSR $FFBD
; DIFF: C65: Make device 8 the default.
; C64: E1D9 A2 01 LDX #$01
E1D9 A2 08 LDX #$08
E1DB A0 00 LDY #$00
E1DD 20 BA FF JSR $FFBA
...
; get open/close parameters
E219 A9 00 LDA #$00
E21B 20 BD FF JSR $FFBD
E21E 20 11 E2 JSR $E211
E221 20 9E B7 JSR $B79E
E224 86 49 STX $49
E226 8A TXA
; DIFF: C65: Make device 8 the default
; C64 E227 A2 01 LDX #$01
E227 A2 08 LDX #$08
E229 A0 00 LDY #$00
E22B 20 BA FF JSR $FFBA
and changing the shift-RUN/STOP text so that it loads the first file from disk and runs it:
; Fill keyboard buffer with LOAD command when shift-RUNSTOP is pressed
E5EE A2 09 LDX #$0A ; C64: LDA #$09
E5F0 78 SEI
E5F1 86 C6 STX $C6
; C64: E5F3 BD E6 EC LDA $ECE6,X
; C65: Copy string L"0:*R into keyboard buffer
E5F3 BD 56 FC LDA $FC56,X ; C65
E5F6 9D 76 02 STA $0276,X
E5F9 CA DEX
E5FA D0 F7 BNE $E5F3
...
; C65: key sequence when shift-RUN/STOP is pressed
; L"0:*R
FC57 .BY $4C, $CF, $22, $30, $3A, $2A, $0D, $52, $D5, $0D
The DOS routines have been intercepted using an interesting approach. First, $C0 ceases to indicate the tape motor state, and instead indicates whether the current drive is on the serial IEC bus, or handled by the internal 1581 DOS. This is checked using one of the new 4510 opcodes, BBS7, which branches on whether bit 7 is set in a zero-page byte, without having to use the accumulator:
; C65: call DOS routine and return: send secondary address (talk)
F7E4 FF C0 09 BBS7 $C0,$F7F0 ; branch based on whether current device is internal 1581
F7E7 20 C7 ED JSR $F72C ; bank in C65 1581 DOS
F7EA 22 0A 80 JSR ($800A)
F7ED 20 3E F8 JSR $F83E ; C65: return from DOS context and set $90 status
F7F0 4C C7 ED JMP $EDC7 ; send secondary address (talk) on serial bus
As can be seen, if the drive is on the IEC bus, then the normal C64 kernel routine is used. However, if the drive is the internal one, then the C65 1581 DOS is banked in to $8000 - $BFFF (conveniently leaving the C64 kernel still in view), and then the appropriate vector in that ROM is called. Notice the use of the new indirect mode of the JSR instruction (opcode $22).
Banking involves the use of the MAP instruction, which sets the memory map. C65 memory mapping is too complex to cover in this post, so I will cover it in a separate post later. The interesting thing for now is to see how NOP is no longer really NOP. The MAP instruction prevents both IRQ
and NMI interrupts until a NOP instruction is run. NOP is consequentially also known as End Of Mapping (EOM) on the 4510.
All this and more can be seen in the routine for switching to the C65 1581 DOS memory context.
; C65: switch to C65 1581 DOS context
F72C 78 SEI
F72D 48 PHA
F72E A9 A5 LDA #$A5 ; C65: VIC-III enable sequence
F730 8D 2F D0 STA $D02F
F733 A9 96 LDA #$96
F735 8D 2F D0 STA $D02F ; C65: VIC-III enabled
F739 A9 40
F73A 0C 31 D0 TSB $D031 ; set bit 6 in $D031 to put CPU at 3.5MHz
F73D A9 21
F73F 0C 30 D0 TSB $D030 ; bank in $C000 interface ROM and remove CIAs from IO map
F742 68 PLA ; store registers
F743 8D F6 DF STA $DFF6
F746 8E F7 DF STX $DFF7
F749 8C F8 DF STY $DFF8
F74C 9C F9 DF STZ $DFF9
F74F 68 PLA
F750 8D FB DF STA $DFFB
F753 68 PLA
F754 8D FC DF STA $DFFC
F757 BA TSX
F758 8E FF DF STX $DFFF
; C65: bank in 1581 DOS
F75B A9 00 LDA #$00
F75D A2 11 LDX #$11 ; Map $0000-$1FFF to $10000-$11FFF ($0000+$10000)
F75F A0 80 LDY #$80
F761 A3 31 LDZ #$31 ; Map $8000-$BFFF to $20000-$23FFF ($8000+$18000)
F763 5C MAP ; activate new map
F764 A2 FF LDX #$FF
F766 9A TXS
F767 AD FC DF LDA $DFFC
F76A 48 PHA
F76B AD FB DF LDA $DFFB
F76E 48 PHA
F76F AD F6 DF LDA $DFF6
F772 AE F7 DF LDX $DFF7
F775 AC F8 DF LDY $DFF8
F778 AB FA DF LDZ $DFFA
F77B 60 RTS ; RTS (notice how the return address was copied from old stack to new stack)
Notice that there is no NOP or EOM in this routine. This prevents any interrupts occurring while the internal DOS is operating in its special memory map. The EOM appears in the routine for returning from the C65 1581 DOS context:
; C65: return from DOS call and set status in $90
F83E 68 PLA
F83F 8D FD DF STA $DFFD
F842 68 PLA
F843 8D FE DF STA $DFFE
F846 20 7C F7 JSR $F77C ; restore C64 memory map
F849 77 C0 RMB7 $C0 ; clear bit 7 in $C0
F84B 6B TZA
F84C 10 0C BPL $F85A
F84E A9 00 LDA #$00
F850 F7 C0 SMB7 $C0 ; set bit 7 in $C0
F852 AE FE DF LDX $DFFE
F855 DA PHX
F856 AE FD DF LDX $DFFD
F859 DA PHX
F85A 04 90 TSB $90 ; Set bits in $90 (status) if required
F85C AE F7 DF LDX $DFF7
F85F AC F8 DF LDY $DFF8
F862 AB F9 DF LDZ $DFF9
F865 AD F6 DF LDA $DFF6
F868 48 PHA
F869 A9 21 LDA #$21
F86B 1C 30 D0 TRB $D030 ; bank out $C000 ROM and bank CIAs back in.
F86E A9 40 LDA #$40
F870 1C 31 D0 TRB $D031 ; return CPU to 1MHz.
F873 8D 2F D0 STA $D02f ; return to VIC-II mode
F876 68 PLA
F877 EA EOM ; release IRQ & NMI after MAP change triggered at $F846
F878 58 CLI
F879 18 CLC
F87A 60 RTS
The $DFFx memory accesses are not to the CIAs, but to the end of screen RAM. Setting bit 0 in $D030 replaces $DC00-$DFFF with an extra 1KB of colour RAM, which is in fact the last 2KB of the 128KB of main RAM of a C65, and hence is 8 bit RAM, unlike the 4-bit colour RAM on the C64.
The $D030 flag is primarily for making the 2KB colour RAM conveniently available to the kernel when working with an 80-column, and hence 2,000 byte screen. Of course this leaves a few bytes spare at the end that are nicely used here to save and restore registers when the stack cannot be used because memory is being remapped.
The last interesting piece is to explore is the reset process. The reset vector has been changed:
FFFA .WD $FE43 ; NMI vector
; C64: FFFC .WD $FCE2 ; RESET vector
; C65 new reset vector
FFFC .WD $E4B8 ; RESET vector
FFFE .WD $FF48 ; IRQ/BRK vector
Reset proceeds from $E4B8, instead of $FCE2. The $E4B8 routine is quite simple, if a little curious:
; C65: CPU reset entry point.
; Check for cartridge, else normal reset sequence.
; (this is a little strance, since $FCE2 routine also calls $FD02
E4B8 20 02 FD JSR $FD02 ; check for cartridge
E4BB D0 03 BNE $E4C0
E4BD 4C E2 FC JMP $FCE2 ; RESET routine
; C65: Enable VIC-III mode, jump to interface
E4C0 78 SEI
E4C1 A9 A5 LDA #$A5
E4C3 8D 2F D0 STA $D02F
E4C6 A9 96 LDA #$96
E4C8 8D 2F D0 STA $D02F
E4CB A9 20 LDA #$20
E4CD 8D 30 D0 STA $D030 ; bank interface ROM in @ $C000
E4D0 4C 00 C8 JMP $C800 ; interface ROM entry point
E4D3 85 A9 STA $A9
E4D5 A9 01 LDA #$01
E4D7 85 AB STA $AB
E4D9 60 RTS
If a C64 cartridge is detected, then the usual C64 reset process is followed. If not, then the machine switches to C65 mode by banking in the interface ROM at $C000-$CFFF and jumping to the entry point there. Also, at another point the C65 1581 ROM is mapped into $8000 - $BFFF and the DOS setup routine is called. This means that I need to disassemble and examine those two ROMs as well I am to fully understand what is going on.
But for now, the complete C65 C64-mode kernel disassembly is available
here.