Skip to content

Instantly share code, notes, and snippets.

@kalj

kalj/monitor.S Secret

Created January 26, 2022 19:57
Show Gist options
  • Save kalj/66b23c440557839b82728850555af283 to your computer and use it in GitHub Desktop.
Save kalj/66b23c440557839b82728850555af283 to your computer and use it in GitHub Desktop.
W65C02SXB monitor code
#define STATE_A $7e00
#define STATE_Ahi $7e01
#define STATE_X $7e02
#define STATE_Xhi $7e03
#define STATE_Y $7e04
#define STATE_Yhi $7e05
#define STATE_PClo $7e06
#define STATE_PChi $7e07
#define STATE_SP $7e0a
#define STATE_P $7e0c
#define STATE_ZP $7e14
#define STATE_ZP0 $7e14
#define STATE_ZP1 $7e15
#define STATE_ZP2 $7e16
#define STATE_ZP3 $7e17
#define STATE_ZP4 $7e18
;; Various irrelevant processor state bank etc
#define STATE_VAR08 $7e08
#define STATE_VAR09 $7e09
#define STATE_VAR0b $7e0b
#define STATE_VAR0d $7e0d
#define STATE_VAR0e $7e0e
#define STATE_VAR0f $7e0f
#define STATE_IS816 $7e10
;; used to determine if inside the "kernel"
#define STATE_INKERNEL $7e11
#define STATE_VAR12 $7e12 ; unused?
#define STATE_VAR13 $7e13 ; some temporary storage
#define STATE_VAR19 $7e19 ; some temporary storage
#define STATE_PCR $7e1c
#define STATE_DDRB $7e1d
#define STATE_DDRA $7e1e
#define IRQ_HANDLER_ADDRESS $7e70
#define NMI_HANDLER_ADDRESS $7e72
#define SHADOW_VECTOR_BASE $7efa
#define USR_NMI_HANDLER_ADDRESS $7efa
#define USR_RST_HANDLER_ADDRESS $7efc
#define USR_IRQ_HANDLER_ADDRESS $7efe
#define VIA_TIDE_PORTB $7fe0
#define VIA_TIDE_PORTA $7fe1
#define VIA_TIDE_DDRB $7fe2
#define VIA_TIDE_DDRA $7fe3
#define VIA_TIDE_ACR $7feb
#define VIA_TIDE_PCR $7fec
#define VIA_TIDE_STATCTRL VIA_TIDE_PORTB
#define VIA_TIDE_DATA_DIR VIA_TIDE_DDRA
#define VIA_TIDE_DATA VIA_TIDE_PORTA
#define VIA_TIDE_STATCTRL_TXEB %00000001
#define VIA_TIDE_STATCTRL_RXFB %00000010
#define VIA_TIDE_STATCTRL_WR %00000100
#define VIA_TIDE_STATCTRL_RDB %00001000
#define VIA_TIDE_STATCTRL_PWRENB %00100000
.org $8000
.ascii "WDC"
.byte $ff
.org $8004
.byte $4c, $15, $81
.byte $ff
.org $8008
.asciiz "WDC65c02SK WDCMON Version = 2.0.4.3Version Date = Tue Jul 2 2013 16:24"
;; Q: what is this data??
.org $8080
.word $8004
.word $8121
.word $84b9
.word $84cb
.word $84f0
.word $7e11
.word $8008
.word $7e70
.word $8404
.word $841e
.word $8404
.word $841e
.org $8100
IRQ:
JMP (IRQ_HANDLER_ADDRESS)
NOP
.org $8104
NMI:
JMP (NMI_HANDLER_ADDRESS)
NOP
;; unclear purpose of this section:
JMP (IRQ_HANDLER_ADDRESS)
NOP
JMP (NMI_HANDLER_ADDRESS)
NOP
;; at $8110
infinite_wait:
JSR do_rts3
BRA infinite_wait
.org $8115
RST:
JSR initialize_state
JMP main_loop_with_zp_save
;; at $811b
initialize_state:
PHP
PHA
LDA #$01
BRA @skip1
;; can it start here too? ($8121)
PHP
PHA
@skip1:
SEI
CLD
STZ $7e1a
STZ $7e19
STX STATE_X
STY STATE_Y
PLX
STX STATE_A
PLY
STY STATE_P
PHA
TSX
INX
INX
INX
STX STATE_SP
LDA #$01
STA STATE_VAR0b
LDA #$00
STA STATE_Ahi
STA STATE_Xhi
STA STATE_Yhi
STA STATE_VAR08
STA STATE_VAR09
STA STATE_VAR0e
STA STATE_VAR0f
LDA #$01
STA $7e0d
SEC
.byte $fb ; INV/XCE
CLC
.byte $fb ; INV/XCE
LDX #$00
BCC @not_has_xce
INX
@not_has_xce:
.byte $fb ; INV/XCE
STX STATE_IS816
PLA
BEQ @skip_fill_shadow_vector
;; fill 7efe with $10, $81 = $8110
;; fill 7efc
;; fill 7efa
LDA #<infinite_wait
LDX #$06
@loop_low_byte:
STA SHADOW_VECTOR_BASE-2,X
DEX
DEX
BNE @loop_low_byte
LDA #>infinite_wait
LDX #$06
@loop_high_byte:
STA SHADOW_VECTOR_BASE-1,X
DEX
DEX
BNE @loop_high_byte
@skip_fill_shadow_vector:
;; set up
LDA #$01
STA STATE_INKERNEL
DEC A
STA STATE_VAR12
LDA #<irq_handler
STA IRQ_HANDLER_ADDRESS
LDA #>irq_handler
STA IRQ_HANDLER_ADDRESS+1
LDA #<nmi_handler
STA NMI_HANDLER_ADDRESS
LDA #>nmi_handler
STA NMI_HANDLER_ADDRESS+1
JSR setup_via_tide
RTS
;; at $81a9
main_loop_with_zp_save:
;; save zero page state (first 5 bytes)
;; copy 5 bytes from $00 to $7e14
LDX #$04
@loop_copy_5bytes:
LDA $00,X
STA STATE_ZP,X
DEX
BPL @loop_copy_5bytes
;; at $81b3
main_loop_start:
JSR read_byte ; receive byte
@loop3:
CMP #$55 ; continue if received $55
BNE main_loop_start
JSR read_byte ; receive another byte
CMP #$aa ; continue if $aa
BNE @loop3 ; else wait for another $55
LDA #$cc
JSR write_byte ; send $cc byte
JSR read_byte ; receive byte
SEC
SBC #$00
CMP #$0a
BCS main_loop_start
;; transform command into jump offset by doing *2
ASL A
TAX
;; spoof return address as $81b2 (+1) == @loop2 above
LDA #>(main_loop_start-1) ; $81
PHA
LDA #<(main_loop_start-1) ; $b2
PHA
;; set read address to jump to from table, indexing with X
LDA (jumptable+1),X
PHA
LDA jumptable,X
PHA
RTS
jumptable:
.address handle_cmd_sync-1 ; 0 : cmd_sync
.address handle_cmd_echo-1 ; 1 : cmd_echo
.address handle_cmd_write-1 ; 2 : cmd_write
.address handle_cmd_read-1 ; 3 : cmd_read
.address handle_cmd_getinfo-1 ; 4 : cmd_getinfo
.address handle_cmd_exec-1 ; 5 : cmd_exec
.address do_rts-1 ; 6 : (do nothing)
.address do_rts-1 ; 7 : (do nothing)
.address complicated_write_null-1 ; 8 : jump do case below
.address read_byte_and_write_null-1 ; 9 :
;; at $81f5
handle_cmd_sync:
LDA #$00
JMP write_byte
;; at $81fa
handle_cmd_echo:
LDA #$00 ; read two bytes and set 3rd to 0
STA $02
JSR read_byte
STA $00
JSR read_byte
STA $01
ORA $00 ; check if received two 0's
BEQ @done ; then end
LDA $00
BEQ @receive_next ; if lsb 0,
INC $01 ; then increment msb
@receive_next:
JSR read_byte
CMP $02
BEQ @inc_and_write ; if
@hang:
JSR do_rts2
BRA @hang
@inc_and_write:
INC A
JSR write_byte
INC $02
DEC $00
BNE @receive_next
DEC $01
BNE @receive_next
@done:
RTS
;; at $822d
handle_cmd_write:
JSR read_byte
STA $00
JSR read_byte
STA $01
JSR read_byte
STA $02
;; sanity check of address
LDA #$00
CMP $02
BCC @read_size ; continue if Bank > 0
LDA #$80
CMP $01
BCC @read_size ; continue if MSB > $80
LDA #$00
CMP $00
BCC @read_size ; continue if LSB > 0
LDA #$01
CMP $02
BCS @read_size ; continue if Bank < 2 -- always true since Bank <= 0
LDA #$01
STA STATE_VAR19
BRA @increment_address ; what is this case?
@read_size:
JSR read_byte
STA $03
JSR read_byte
STA $04
ORA $03 ; or msb and lsb of size
BEQ @done ; both are zero, then done
LDY #$00
LDA $03
BEQ @lbl2 ; increment size msb if lsb is !=0
INC $04
@lbl2:
LDA $01
ORA $02 ; check if both bank and msb == 0, i.e. in zp
BNE @regular_store
LDA $00 ; if in zp, check if
CMP #$05
BCS @regular_store ; branch if lsb >= 5
; otherwise, we instead write to the zero page image.
; since the first 5 (0,..,4) zp entries should not be touched
TAY
JSR read_byte
STA STATE_ZP,Y
LDY #$00
BEQ @increment_address
@regular_store:
JSR read_byte
LDX STATE_IS816 ; por que??
BNE @increment_address
STA ($00),Y
BEQ @increment_address
@increment_address:
INC $00 ; increment address
BNE @addr_inc_done ; done if no carry
INC $01 ; increment msb if carry
BNE @addr_inc_done ; done if no carry
INC $02 ; increment bank if carry
@addr_inc_done:
DEC $03 ; decrement size
BNE @lbl2 ; if not zero continue loop
DEC $04 ; if zero decrement msb of size ??
BNE @lbl2 ; if msb not zero continue loop
@done:
LDA STATE_VAR19 ; if not zero
BEQ @return
STZ STATE_VAR19 ; write zero
@return:
RTS
;; at $82af
handle_cmd_read:
JSR read_byte
STA $00
JSR read_byte
STA $01
JSR read_byte
STA $02
JSR read_byte
STA $03
JSR read_byte
STA $04
ORA $03
BEQ @done ; if size is all 0, then done
;; if lsb of size is 0, increment msb (?)
LDY #$00
LDA $03
BEQ @loop
INC $04
@loop:
LDA $01
ORA $02 ; if bank and msb are 0 (ie in zp)
BNE @regular_read
LDA $00
CMP #$05 ; and if the first 5 bytes,
BCS @regular_read
TAY ; then read from state instead
LDA STATE_ZP,Y
LDY #$00
BEQ @send_read_byte
@regular_read:
LDA STATE_IS816 ; weird stuff ...
BNE @send_read_byte ; if not 0, ie if 816, then skip reading
LDA ($00),Y
BRA @send_read_byte
@send_read_byte:
JSR write_byte
INC $00 ; increment address
BNE @addr_incr_done
INC $01 ; carry into msb
BNE @addr_incr_done
INC $02 ; carry into bank
@addr_incr_done:
DEC $03 ; decrement size
BNE @loop
DEC $04
BNE @loop ; if both are 0, fall through to return
@done:
RTS
;; at $8307
handle_cmd_getinfo:
LDA #$00 ; 00 - 00
JSR write_byte
LDA #$7e ; 01 - 7e
JSR write_byte
LDA #$00 ; 02 - 00
JSR write_byte
LDA STATE_IS816 ; 03 - whether has xce / is 65816
JSR write_byte
LDA #$58 ; 04 - 58 == 'X'
JSR write_byte
LDA #$00 ; 05 - 00
JSR write_byte
LDA #$7e ; 06 - 7e
JSR write_byte
LDA #$00 ; 07 - 00
JSR write_byte
LDA #$00 ; 08 - 00
JSR write_byte
LDA #$80 ; 09 - 80
JSR write_byte
LDA #$00 ; 0a - 00
JSR write_byte
LDA #$fa ; 0b - fa
JSR write_byte
LDA #$7e ; 0c - 7e
JSR write_byte
LDA #$00 ; 0d - 00
JSR write_byte
LDA #$00 ; 0e - 00
JSR write_byte
LDA #$7f ; 0f - 7f
JSR write_byte
LDA #$00 ; 10 - 00
JSR write_byte
LDA #$fa ; 11 - fa
JSR write_byte
LDA #$ff ; 12 - ff
JSR write_byte
LDA #$00 ; 13 - 00
JSR write_byte
LDA #$00 ; 14 - 00
JSR write_byte
LDA #$7f ; 15 - 7f
JSR write_byte
LDA #$00 ; 16 - 00
JSR write_byte
LDA #$ff ; 17 - ff
JSR write_byte
LDA #$7f ; 18 - 7f
JSR write_byte
LDA #$00 ; 19 - 00
JSR write_byte
LDA #$ff ; 1a - ff
JSR write_byte
LDA #$ff ; 1b - ff
JSR write_byte
LDA #$ff ; 1c - ff
JMP write_byte
;; at $8399
handle_cmd_exec:
JSR load_pcr
;; restore zp
LDX #$04
@zp_restore_loop:
LDA STATE_ZP,X
STA $00,X
DEX
BPL @zp_restore_loop
LDA STATE_VAR0d
BEQ @label1
@label2:
LDX STATE_SP
TXS
LDX STATE_X
LDY STATE_Y
LDA STATE_PChi ; prepare stack for RTI
PHA
LDA STATE_PClo
PHA
LDA STATE_P
PHA
LDA #$00 ; leave kernel by clearing
STA STATE_INKERNEL ; STATE_INKERNEL
LDA STATE_A
RTI
@label1:
BRA @label2
;; at $83cc
do_rts:
RTS
;; Q: what is this?
;; at $83cd
dead_code_1:
JMP write_byte ;; 83cd 4c f0 84 JMP write_byte
LDA #$24 ;; 83d0 a9 24 LDA #$24
JSR write_byte ;; 83d2 20 f0 84 JSR write_byte
LDA #$80 ;; 83d5 a9 80 LDA #$80
JSR write_byte ;; 83d7 20 f0 84 JSR write_byte
LDA #$00 ;; 83da a9 00 LDA #$00
JMP write_byte ;; 83dc 4c f0 84 JMP write_byte
;; at $83df
complicated_write_null:
JSR @clear_carry
BCC @write_null_case
LDA #$01
JMP write_byte
@write_null_case:
LDA #$00
JMP write_byte
@clear_carry: ; weird stuff...
CLC
RTS
;; at $83f0
read_byte_and_write_null:
JSR read_byte
JSR @clear_carry
BCC @write_null_case
LDA #$01
JMP write_byte
@write_null_case:
LDA #$00
JMP write_byte
@clear_carry:
CLC
RTS
;; at $8404
;; always increment var11.
;; if break was called, call int_save_state with A=2
;; else zero var11 and call user interrupt handler
irq_handler:
INC STATE_INKERNEL
PHA
PHX
TSX
LDA $0103,X ; load P from stack
PLX
AND #$10 ; check bit 5 of P
BNE @broke
STZ STATE_INKERNEL
PLA
JMP (USR_IRQ_HANDLER_ADDRESS) ; jump to user-defined irq-handler
@broke:
LDA #$02
JMP int_save_state
;; at $841e
;; if var11 is !=0 then we just do RTI
;; but if it is == 0, we increase it, and call int_save_state with A=7
nmi_handler:
PHA
LDA STATE_INKERNEL
BNE @non_zero_case
INC STATE_INKERNEL
LDA #$07
BRA int_save_state
@non_zero_case:
PLA
RTI
;; at $842d
;; saves the state, writes the input byte, and calls main_loop_with_zp_save
int_save_state:
STA STATE_VAR13
PLA
STA STATE_A
STX STATE_X
STY STATE_Y
PLA ; pop P from stack
STA STATE_P
PLA ; pop pc lo byte
STA STATE_PClo
PLA ; pop pc hi byte
STA STATE_PChi
STZ STATE_VAR0e
STZ STATE_VAR0f
STZ STATE_VAR08
STZ STATE_VAR09
STZ STATE_Ahi
STZ STATE_Xhi
STZ STATE_Yhi
TSX
STX STATE_SP
LDA #$01
STA STATE_VAR0b
STA STATE_VAR0d
JMP @dummy_jmp
@dummy_jmp:
JSR store_pcr
LDA STATE_VAR13
JSR write_byte
JMP main_loop_with_zp_save
;; at $8476
setup_via_tide:
LDA #$00
STA VIA_TIDE_ACR
LDA #$00
STA VIA_TIDE_PCR
STA STATE_PCR
LDA #%00011000
STA VIA_TIDE_STATCTRL
LDA #%00011100
STA VIA_TIDE_DDRB
STA STATE_DDRB
LDA #$00
STA VIA_TIDE_DATA_DIR
STA STATE_DDRA
LDA VIA_TIDE_STATCTRL
PHA
AND #%11101111 ; why clear this random bit?
STA VIA_TIDE_STATCTRL
LDX #$5d
JSR sleep ; sleep for 93 = 0x5d iterations
PLA
STA VIA_TIDE_STATCTRL
;; wait for pwrenb to go low indicating the FT245RL has woken up
LDA #VIA_TIDE_STATCTRL_PWRENB
@loop1:
BIT VIA_TIDE_STATCTRL
BNE @loop1
LDA VIA_TIDE_STATCTRL
AND #%01000000 ; Q: why check this random bit?
BEQ random_rts
RTS
;; Q: what is this stuff?
;; at $84b9
dead_code_2:
LDA #$00 ;; 84b9 a9 00 LDA #$00
STA VIA_TIDE_DATA_DIR ;; 84bb 8d e3 7f STA VIA_TIDE_DATA_DIR
LDA #$02 ;; 84be a9 02 LDA #$02
BIT VIA_TIDE_STATCTRL ;; 84c0 2c e0 7f BIT VIA_TIDE_STATCTRL
BNE lbl84c8 ;; 84c3 d0 03 BNE 3 ; $84c8
LDA #$01 ;; 84c5 a9 01 LDA #$01
RTS ;; 84c7 60 RTS
lbl84c8:
LDA #$00 ;; 84c8 a9 00 LDA #$00
RTS ;; 84ca 60 RTS
;; at $84cb
read_byte:
LDA #$00 ; set PORTA (USB Data) to input
STA VIA_TIDE_DATA_DIR
LDA #VIA_TIDE_STATCTRL_RXFB ; check RXFB bit (0 if rx data available)
@wait_for_input:
BIT VIA_TIDE_STATCTRL ; EQ/Z bit set if bit cleared
BNE @wait_for_input ; wait if bit was not zero
LDA VIA_TIDE_STATCTRL
ORA #VIA_TIDE_STATCTRL_RDB
TAX
AND #($ff - VIA_TIDE_STATCTRL_RDB)
STA VIA_TIDE_STATCTRL ; Set RDB low to enable output of next byte
NOP ; wait for data?
NOP
NOP
NOP
LDA VIA_TIDE_DATA ; read next byte
STX VIA_TIDE_STATCTRL ; Set RDB high to disable output again
RTS
;; wtf?
LDA #$ee
RTS
;; at $84f0
write_byte:
LDX #$00
STX VIA_TIDE_DATA_DIR ; set PORTA (USB Data) to input
STA VIA_TIDE_DATA ; write data to the port (although not set for output yet)
LDA #VIA_TIDE_STATCTRL_TXEB
@tx_fifo_full:
BIT VIA_TIDE_STATCTRL
BNE @tx_fifo_full ; loop if bit was not zero
LDA VIA_TIDE_STATCTRL
AND #($ff - VIA_TIDE_STATCTRL_WR) ; prepare cleared WR bit
TAX
ORA #VIA_TIDE_STATCTRL_WR
STA VIA_TIDE_STATCTRL ; set WR bit to accept data
LDA #$ff
STA VIA_TIDE_DATA_DIR ; set Data port to output
NOP
NOP
STX VIA_TIDE_STATCTRL ; clear WR bit to latch in data
LDA VIA_TIDE_DATA ; read data port ? perhaps to restore A
LDX #$00
STX VIA_TIDE_DATA_DIR ; set data port to input again
RTS
random_rts:
RTS ; why extra return?
;; at $851e
sleep:
@outer_loop:
PHX
LDX #$00
@inner_loop:
DEX
BNE @inner_loop
PLX
DEX
BNE @outer_loop
RTS
;; at $8529
store_pcr:
LDA VIA_TIDE_PCR
STA STATE_PCR
RTS
;; at $8530
load_pcr:
LDA STATE_PCR
STA VIA_TIDE_PCR
RTS
;; at $8537
do_rts2:
RTS
;; at $8538
do_rts3:
RTS
RTS
RTS
RTS
.org $fffa
.address $8104 ; NMI
.address $8115 ; RST
.address $8100 ; IRQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment