-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathW65c816SXB-Custom-ROM.s
1640 lines (1330 loc) · 54.3 KB
/
W65c816SXB-Custom-ROM.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
; Alicie, 2022, based on original work by "Keith".
;
; Customized W65C02SXB ROM image, with the following goals:
;
; 1) Move all ROM code to the last flash page, leaving the rest
; of the flash available for use by the user application.
; 2) Reduce RAM usage.
; 3) Eliminate dead code in the monitor.
; 4) Support automatically executing a user's image in FLASH.
; 5) Allow automatic code execution without requiring the USB
; debugger port be enumerated (allows for power-only).
;
; If the first three bytes of the user's application area in flash,
; $8000-$8002, are "WDC", then the user's application is automatically
; executed by jumping to address $8003. Upon executing the user's code,
; the following conditions apply:
;
; * The CPU is in '02' emulation mode.
; * Interrupts are disabled.
; * Decimal mode is cleared.
; * All register contents are indeterminate, including SP.
; * The raw vectors are initialized to their default values.
; * The USB VIA is initialized for use with the debugger.
; * If the CPU had been running prior to being reset, then:
; * The CPU context is saved in the save area in work RAM.
; * The shadow vectors are left as they were prior to the reset.
;
; Assuming that the user application does not overwrite the default NMI
; raw vector, then the NMI button can be used to break the user's app
; and enter the debugging monitor.
.setcpu "65816"
; Most of the time, the monitor uses 8-bit A and XY registers, even in
; 65816 native mode. So to keep it simple, we'll only use .A16 or .I16
; when necessary, and we'll only use .A8 and .I8 in the places needed
; to restore the assembler back to 8-bit mode. So we'll not use a .A8
; or a .I8 every time there is a SEP -- only when it is actually needed.
; Zero-page variables that are temporarily used when executing commands
; from the debugger. When the monitor is entered, their values are saved
; into the ZP save area in work RAM (RAM_ZP_SAVE), and restored before
; control is returned to the user's program, so their use by the debugger
; and monitor is transparent to the user.
; Imports. These are all exported by the linker.
.import __HW_IO_ADDR__
.import __HW_IO_SIZE__
.import __VIA_USB_ADDR__
.import __HW_BRK_ADDR__
.import __WORK_RAM_LOAD__
.import __SHADOW_VECTORS_LOAD__
.import __SHADOW_VECTORS_SIZE__
.import __USR_ROM_START__
.import __MONITOR_HEADER_LOAD__
.import __CPU_VECTORS_LOAD__
; Export some useful symbols so they are shown in the map file.
.export RAM_A_SAVE
.export RAM_X_SAVE
.export RAM_Y_SAVE
.export RAM_PC_SAVE
.export RAM_DP_SAVE
.export RAM_SP_SAVE
.export RAM_P_SAVE
.export RAM_E_SAVE
.export RAM_PB_SAVE
.export RAM_DB_SAVE
.export RAM_CPU_TYPE
.export RAM_IN_MONITOR
.export RAM_ZP_SAVE
.export IRQ_02_ENTRY_VECTOR
.export NMI_02_ENTRY_VECTOR
.export BRK_816_ENTRY_VECTOR
.export NMI_816_ENTRY_VECTOR
.export SHADOW_VEC_COP_816
.export SHADOW_VEC_ABORT_816
.export SHADOW_VEC_IRQ_816
.export SHADOW_VEC_COP_02
.export SHADOW_VEC_ABORT_02
.export SHADOW_VEC_IRQ_02
.export Pointer_Table
.export RESET_entry
; Symbol definitions.
BOARD_ID := $58
.zeropage
; Varibales used by the debugger. They are copied and restored to work-
; RAM, so their usage is transparent to the user's application.
DBG_VAR_00: .res 1
DBG_VAR_01: .res 1
DBG_VAR_02: .res 1
DBG_VAR_03: .res 1
DBG_VAR_04: .res 1
.segment "WORK_RAM"
; Save-area in work-RAM for A.
RAM_A_SAVE: .res 2
; Save-area in work-RAM for X.
RAM_X_SAVE: .res 2
; Save-area in work-RAM for Y.
RAM_Y_SAVE: .res 2
; Save-area in work-RAM for PC.
RAM_PC_SAVE: .res 2
; Save-area in work-RAM for DP.
RAM_DP_SAVE: .res 2
; Save-area in work-RAM for the stack pointer.
RAM_SP_SAVE: .res 2
; Save-area in work-RAM for the P register.
RAM_P_SAVE: .res 1
; Save-area in work-RAM for the emulation bit. This can be thought of
; as an extenstion bit to P. If E=1, the CPU is in emulation mode.
RAM_E_SAVE: .res 1
; Save-area in work-RAM for the PB register.
RAM_PB_SAVE: .res 1
; Save-area in work-RAM for the DB register.
RAM_DB_SAVE: .res 1
; The auto-detected CPU type (0=65C02, 1=65816).
RAM_CPU_TYPE: .res 1
; 1 if the the monitor is currently being executed, 0 if user code.
RAM_IN_MONITOR: .res 2
; Why control has returned to the ROM monitor:
; 2: A BRK instruction was executed.
; 7: An NMI was generated.
RAM_ENTER_MONITOR_REASON : .res 1
; Five-byte area where $00-$04 are saved.
RAM_ZP_SAVE: .res 5
; Flag variable at $7E19 of unknown use. It only ever gets set to 0.
RAM_VAR_7E19: .res 1
; Save-area in work-RAM for the system VIA's registers.
RAM_PCR_SAVE: .res 1
.segment "RAW_VECTORS"
; Vectors in RAM which are called directly from the vectors in FLASH.
; These vectors are typically not overridden by the user, since they
; perform critical system and debugger operations.
IRQ_02_ENTRY_VECTOR: .res 2
NMI_02_ENTRY_VECTOR: .res 2
BRK_816_ENTRY_VECTOR: .res 2
NMI_816_ENTRY_VECTOR: .res 2
.segment "SHADOW_VECTORS"
; Although there are several unused vectors here, they should probably be
; left alone, since the debugger might expect the shadow vectors to be
; laid out in a certain way and at certain offsets.
; Shadow vectors for 816 mode, which the user may set to hook the vector.
SHADOW_VEC_COP_816: .res 2
SHADOW_VEC_BRK_816: .res 2 ; unused
SHADOW_VEC_ABORT_816: .res 2
SHADOW_VEC_NMI_816: .res 2 ; unused
SHADOW_VEC_RSVD_816: .res 2 ; unused
SHADOW_VEC_IRQ_816: .res 2
; Shadow vectors for the 65816 running in '02 emulation mode.
SHADOW_VEC_RSVD1_02: .res 2 ; unused
SHADOW_VEC_RSVD2_02: .res 2 ; unused
SHADOW_VEC_COP_02: .res 2
SHADOW_VEC_RSVD3_02: .res 2 ; unused
SHADOW_VEC_ABORT_02: .res 2
; Shadow vectors for all 65xx processors.
SHADOW_VEC_NMI_02: .res 2 ; unused
SHADOW_VEC_RESET_02: .res 2 ; unused
SHADOW_VEC_IRQ_02: .res 2
.segment "VIA_USB"
; IO for the VIA which is used for the USB debugger interface.
; Unused registers are commented-out.
SYSTEM_VIA_IOB := __VIA_USB_ADDR__ + $00 ; Port B IO register
SYSTEM_VIA_IOA := __VIA_USB_ADDR__ + $01 ; Port A IO register
SYSTEM_VIA_DDRB := __VIA_USB_ADDR__ + $02 ; Port B data direction register
SYSTEM_VIA_DDRA := __VIA_USB_ADDR__ + $03 ; Port A data direction register
;SYSTEM_VIA_T1C_L := __VIA_USB_ADDR__ + $04 ; Timer 1 counter/latches, low-order
;SYSTEM_VIA_T1C_H := __VIA_USB_ADDR__ + $05 ; Timer 1 high-order counter
;SYSTEM_VIA_T1L_L := __VIA_USB_ADDR__ + $06 ; Timer 1 low-order latches
;SYSTEM_VIA_T1L_H := __VIA_USB_ADDR__ + $07 ; Timer 1 high-order latches
;SYSTEM_VIA_T2C_L := __VIA_USB_ADDR__ + $08 ; Timer 2 counter/latches, lower-order
;SYSTEM_VIA_T2C_H := __VIA_USB_ADDR__ + $09 ; Timer 2 high-order counter
;SYSTEM_VIA_SR := __VIA_USB_ADDR__ + $0A ; Shift register
SYSTEM_VIA_ACR := __VIA_USB_ADDR__ + $0B ; Auxilliary control register
SYSTEM_VIA_PCR := __VIA_USB_ADDR__ + $0C ; Peripheral control register
;SYSTEM_VIA_IFR := __VIA_USB_ADDR__ + $0D ; Interrupt flag register
;SYSTEM_VIA_IER := __VIA_USB_ADDR__ + $0E ; Interrupt enable register
;SYSTEM_VIA_ORA_IRA := __VIA_USB_ADDR__ + $0F ; Port A IO register, but no handshake
; The monitor should begin with a header which includes a signature and version.
.segment "MONITOR_HEADER"
; A Western Design Center mark at the beginning of the header. This is also used
; when checking to see whether to auto-exec a user's application in ROM.
WDC_Signature:
.byte "WDC"
.BYTE $FF
Signature_String:
.byte $82,"$",$01,$FF
Monitor_Version_String:
.asciiz "WDC65c816SXB Custom ROM, v1.1, ts=", .sprintf("%d", .time)
.segment "POINTER_TABLE"
; A table of important pointers. This is located at a fixed address $80 bytes
; into the monitor code, and is 128 bytes long in total. Not sure if the debugger
; uses this table for any reason, but user applications could use it, for example,
; to read and write to the USB FIFO. There is room for more pointers at the end.
Pointer_Table:
; Used entries in the pointer table.
.addr Signature_String
.addr Initialize_System
.addr Is_VIA_USB_RX_Data_Avail
.addr Sys_VIA_USB_Char_RX
.addr Sys_VIA_USB_Char_TX
.addr RAM_IN_MONITOR
.addr Monitor_Version_String
.addr IRQ_02_ENTRY_VECTOR
.addr IRQ_02_Entry_Vector_Default
.addr NMI_02_Entry_Vector_Default
.addr BRK_816_Entry_Vector_Default
.addr NMI_816_Entry_Vector_Default
.addr Wait_For_USB_FIFO_Ready
; This is the start of the actual monitor code.
.segment "MON_CODE"
; Called directly from FLASH vector on IRQ in emulation mode. IRQ and BRK
; are shared in this mode, so jump to monitor code which checks for BRK.
IRQ_02_entry:
jmp (IRQ_02_ENTRY_VECTOR)
; Called directly from FLASH vector on NMI in emulation mode. Jump to monitor
; code to break into the debugger.
NMI_02_entry:
jmp (NMI_02_ENTRY_VECTOR)
; Called directly from FLASH vector on BRK in 816 mode. Jump to monitor
; code to break into the debugger.
BRK_816_entry:
jmp (BRK_816_ENTRY_VECTOR)
; Called directly from FLASH vector on NMI in 816 mode. Jump to monitor
; code to break into the debugger.
NMI_816_entry:
jmp (NMI_816_ENTRY_VECTOR)
; Called directly from FLASH vector on COP in emulation mode. Call the
; user's handler through the shadow vector.
COP_02_entry:
jmp (SHADOW_VEC_COP_02)
; Called directly from FLASH vector on ABORT in emulation mode. Call the
; user's handler through the shadow vector.
ABORT_02_entry:
jmp (SHADOW_VEC_ABORT_02)
; Called directly from FLASH vector on IRQ in 816 mode.
; BRK and IRQ are separate in 816 mode, so no need to run any monitor code.
; Simply invoke the user's IRQ handler through the shadow vector.
IRQ_816_entry:
jmp (SHADOW_VEC_IRQ_816)
; Called directly from FLASH vector on ABORT in 816 mode.
ABORT_816_entry:
jmp (SHADOW_VEC_ABORT_816)
; Called directly from FLASH vector on COP in 816 mode.
COP_816_entry:
jmp (SHADOW_VEC_COP_816)
; Does nothing forever. Called directly from several reserved vectors, and
; is also the default handler for all shadow vectors.
Infinite_Loop:
jsr Do_Nothing_Subroutine
BRA Infinite_Loop
; Called directly from FLASH vector on RESET. Always executed in emulation mode.
RESET_entry:
; Save the CPU context, init vectors, and switch into emulation mode.
JSR Initialize_Upon_Reset
; Execute the user's application if the signature is present.
jsr Check_AutoExec_Usr_App
; If there is no user application, wait for the USB FIFO to be ready.
jsr Wait_For_USB_FIFO_Ready
; Save a copy of ZP memory, and sync up with the debugger.
jmp Save_ZP_Sync_With_Debugger
; Checks the beginning of flash for "WDC", and jumps to the following instruction
; if present. This code must be called in emulation mode.
Check_AutoExec_Usr_App:
; Check to see if the user's area in ROM starts with "WDC".
ldx #$02
@loop:
lda WDC_Signature,x
cmp __USR_ROM_START__,x
bne @done
dex
bpl @loop
; We're no longer in the ROM monitor since we're going to execute user
; code. This is critical to allow NMI to break into the debug monitor.
lda #$00
STA RAM_IN_MONITOR
; The signature matches, so jump to the user's application.
jmp __USR_ROM_START__ + 3
@done:
rts
; Called in emulation mode when the CPU is reset.
Initialize_Upon_Reset:
; Push processor status on stack. The stack is not initialized yet,
; but Continue_System_Init expects P and A to be pushed on the stack,
; and we know the stack will be somewhere in page 1, so there will be
; RAM there to support it.
PHP
; Set A to 8-bit mode. But why? We know we're in emulation mode here.
SEP #$20
; Push A, load A with 1, and continue with initialization.
pha
lda #$01
BRA Continue_System_Init
; Called to initialize the system. The only place this is explicitly called
; is from Initialize_Upon_Reset, which is executed in emulation mode when the
; CPU is reset. But, there is a pointer to this function in the data pointer
; table, so perhaps the debugger calls it to reset the system. As such, this
; function ensures it works correctly even if called from native mode.
Initialize_System:
; Push processor status on stack.
PHP
; Set A to 8-bit mode and push it.
SEP #$20
PHA
; If called from an actual RESET, the CPU will be in emulation mode. If called
; through the data pointer table, the CPU may be in native mode with 8-bit A.
; Upon return, the CPU will be in emulation mode.
Continue_System_Init:
; Disable interrupts and clear the decimal flag.
SEI
CLD
; Zero a variable whose use is currently unknown.
STZ RAM_VAR_7E19
; Switch to native mode.
clc
XCE
; Set XY to 16-bit mode and save them into the RAM save area.
REP #$10
.I16
stx RAM_X_SAVE
STY RAM_Y_SAVE
; Set A and XY to 8-bit mode. Assembler is 8-bit for A already.
SEP #$30
.I8
; Pull the prior value of A (8-bits) into X, and P into Y.
plx
PLY
; Save the processor status byte into the RAM save area.
STY RAM_P_SAVE
; Push 8-bit A back onto the stack.
PHA
; Move the prior 8-bit value of A from X back into A.
TXA
; Set A to 16-bit mode.
REP #$20
.A16
; Save A (all 16 bits) into the RAM save area.
STA f:RAM_A_SAVE
; Save DP (16 bits) into the RAM save area.
TDC
STA f:RAM_DP_SAVE
TSC
; Save the 16-bit stack pointer into the RAM save area.
CLC
ADC #$03
STA f:RAM_SP_SAVE
; Set A and XY to 8-bit mode. Assembler for XY is already 8-bit.
SEP #$30
.A8
; Save the 8-bit program bank into the RAM save area.
PHK
pla
STA f:RAM_PB_SAVE
; Save the 8-bit data bank into the RAM save area.
PHB
PLA
STA f:RAM_DB_SAVE
; Save the emulation mode flag into the RAM save area. Note it gets
; saved as "1", which means the CPU will be in emulation mode once
; the context is restored and the user's code is executed.
lda #$01
STA f:RAM_E_SAVE
; CPU detection code. Determine if this CPU is a 65C02 or 65816.
; Try to switch into emulation mode.
SEC
XCE ; This is a NOP on a 65C02.
; Try to switch into native mode.
CLC
XCE ; This is a NOP on a 65C02. C will be set on a 65816.
; X = 0 if the CPU is a 65C02.
LDX #$00
; If carry is clear, the CPU is 65C02, not 65816.
BCC @skip_inx
; If the CPU is a 65816, carry will be set, and X will be 1.
INX
@skip_inx:
XCE ; Switch into emulation mode. This is a NOP on a 65C02.
STX RAM_CPU_TYPE
; Pull the prior value of A, pushed before the call to
; Continue_System_Init. If this was called from a physical
; RESET, $01 is pushed. Otherwise, whatever was in A is pushed.
PLA
BEQ @skip_shadow_vec_init
; a RESET has occurred, so initialize all the shadow vectors.
; Initialize all shadow vectors to point to an infinite loop.
; Done in two passes. This pass sets the LSB of the vector.
lda #<Infinite_Loop
ldx #$1C
@lsb_loop:
sta __SHADOW_VECTORS_LOAD__-2,x
dex
dex
BNE @lsb_loop
; Finish initializing the shadow registers by writing the MSB.
lda #>Infinite_Loop
ldx #$1C
@msb_loop:
sta __SHADOW_VECTORS_LOAD__-1,x
dex
dex
BNE @msb_loop
@skip_shadow_vec_init:
; We're now in the debugging monitor.
LDA #$01
STA RAM_IN_MONITOR
; Store 0 in the location after RAM_IN_MONITOR. Not sure why.
dec
STA RAM_IN_MONITOR+1
; Set a pointer to the default BRK handler when in native mode.
lda #<BRK_816_Entry_Vector_Default
sta BRK_816_ENTRY_VECTOR
lda #>BRK_816_Entry_Vector_Default
STA BRK_816_ENTRY_VECTOR+1
; Set a pointer to the default NMI handler when in native mode.
LDA #<NMI_816_Entry_Vector_Default
STA NMI_816_ENTRY_VECTOR
LDA #>NMI_816_Entry_Vector_Default
STA NMI_816_ENTRY_VECTOR+1
; Set a pointer to the default IRQ entry vector.
lda #<IRQ_02_Entry_Vector_Default
sta IRQ_02_ENTRY_VECTOR
lda #>IRQ_02_Entry_Vector_Default
STA IRQ_02_ENTRY_VECTOR+1
; Set a pointer to the default NMI entry vector.
lda #<NMI_02_Entry_Vector_Default
sta NMI_02_ENTRY_VECTOR
lda #>NMI_02_Entry_Vector_Default
STA NMI_02_ENTRY_VECTOR+1
jsr Initialize_System_VIA
rts
; Saves the first 5 bytes of zero-page memory, and syncs with the debugger,
; allowing it to control the device.
; Upon entry, the CPU will always be in emulation mode.
Save_ZP_Sync_With_Debugger:
; Copy the first 5 bytes of ZP to the work area in RAM.
ldx #$04
@copy_loop:
lda a:DBG_VAR_00,x
sta RAM_ZP_SAVE,x
dex
BPL @copy_loop
@begin_debugger_sync:
; Read the first snc char from the debugger.
JSR Sys_VIA_USB_Char_RX
@check_for_0x55:
; If the first sync char is not $55, restart the sync process.
cmp #$55
BNE @begin_debugger_sync
; Read the second cync char from the debugger.
JSR Sys_VIA_USB_Char_RX
; If the second sync char is not $AA, check for %55.
CMP #$AA
BNE @check_for_0x55
; Send a $CC to the debugger.
lda #$CC
JSR Sys_VIA_USB_Char_TX
; Read the command byte from the debugger.
JSR Sys_VIA_USB_Char_RX
; See if the command is within the supported range. If not,
; restart the debugger sync sequence.
sec
sbc #$00
cmp #$0A
BCS @begin_debugger_sync
; Set up a simulated function call to jump to the handler
; for the selected debugger command. Multiple the command
; by two since each function pointer is two bytes.
asl
TAX
; Push the address-1 to return to once the command is complete.
; We'll return to .begin_debugger_sync:.
lda #>(@begin_debugger_sync-1)
pha
lda #<(@begin_debugger_sync-1)
PHA
; Push the jump table address and issue the RTS to call the handler.
lda Cmd_Jump_Table+1,x
pha
lda Cmd_Jump_Table,x
pha
rts
; Jump table for debugger commands.
Cmd_Jump_Table:
.WORD Dbg_Cmd_0_Send_Zero - 1
.WORD Dbg_Cmd_1_Seq_Test - 1
.WORD Dbg_Cmd_2_Write_Mem - 1
.WORD Dbg_Cmd_3_Read_Mem - 1
.WORD Dbg_Cmd_4_Sys_Info - 1
.WORD Dbg_Cmd_5_Exec - 1
.WORD Dbg_Cmd_6_No_Op - 1
.WORD Dbg_Cmd_7_No_Op - 1
.WORD Dbg_Cmd_8_BRK - 1
.WORD Dbg_Cmd_9_Read_Byte_and_BRK - 1
; Debugger command 0. Simply sends $00 to the debugger.
Dbg_Cmd_0_Send_Zero:
LDA #$00
jmp Sys_VIA_USB_Char_TX
; Debugger command 1: some kind of sequence test. Debugger sends a 16-bit
; count of the bytes to test. The debugger must then send an incrementing
; sequence of bytes, starting at $00 to the monitor. The monitor sends
; back the next expected byte. If the monitor receives a byte it does not
; expect, it enters an infinite loop.
Dbg_Cmd_1_Seq_Test:
; DBG_VAR_02 is the next value in the sequence. Start with $00.
lda #$00
STA a:DBG_VAR_02
; Read the LSB of the byte count.
JSR Sys_VIA_USB_Char_RX
sta a:DBG_VAR_00
; Read the MSB of the byte count.
jsr Sys_VIA_USB_Char_RX
STA a:DBG_VAR_01
; If the byte count is 0, we're done.
ora a:DBG_VAR_00
BEQ @done
; Set up the count's MSB to be 1-based.
LDA a:DBG_VAR_00
beq @read_next_byte
INC a:DBG_VAR_01
@read_next_byte:
; Read the next byte in the sequence from the debugger.
JSR Sys_VIA_USB_Char_RX
; See if the received byte matches the next one in the sequence.
CMP a:DBG_VAR_02
BEQ @continue
; If the sequence is not correct, enter an infinite loop.
@infinite_loop:
jsr Do_Nothing_Subroutine
BRA @infinite_loop
@continue:
; Send the next byte in the sequence to the debugger.
inc
JSR Sys_VIA_USB_Char_TX
; Save the next byte in the sequence.
INC a:DBG_VAR_02
; Decrement the byte count.
dec a:DBG_VAR_00
bne @read_next_byte
dec a:DBG_VAR_01
BNE @read_next_byte
; All done with the command.
@done: rts
; Debugger command 2: write memory. The debugger sends a 24-bit starting
; address, then a 16-bit count, and then the data to write. Memory writes
; to the first 5 bytes of memory are automatically redirected to the save
; area in work-RAM for these memory locations.
Dbg_Cmd_2_Write_Mem:
; Read the 24-bit starting address, LSB-first.
jsr Sys_VIA_USB_Char_RX
sta a:DBG_VAR_00 ; 16-bit addr LSB
jsr Sys_VIA_USB_Char_RX
sta a:DBG_VAR_01 ; 16-bit addr MSB
jsr Sys_VIA_USB_Char_RX
STA a:DBG_VAR_02 ; bank
; Branch if bank is > $00.
lda #$00
cmp a:DBG_VAR_02
BCC @read_byte_count
; At this point, bank is $00. Branch if page (16-bit MSB) is > $80.
lda #$80
cmp a:DBG_VAR_01
BCC @read_byte_count
; At this point, bank=0, page <= $80. Branch if addr LSB > $00.
lda #$00
cmp a:DBG_VAR_00
BCC @read_byte_count
; At this point, bank=0, page <= $80, addr_LSB=0, which is the
; first byte of any page in RAM, plus the first page of FLASH,
; in bank 0. Now branch if bank <= 1, which is always the case,
; since bank is $00 at this point. Why are they doing all this?
lda #$01
cmp a:DBG_VAR_02
BCS @read_byte_count
; This code stores $01 in RAM_VAR_7E19, but this code is apparently
; never executed. This means RAM_VAR_7E19 will always be $00.
lda #$01
sta RAM_VAR_7E19
BRA @next_addr
@read_byte_count:
; Read the 16-bit byte count from the debugger, LSB-first.
JSR Sys_VIA_USB_Char_RX
sta a:DBG_VAR_03
jsr Sys_VIA_USB_Char_RX
STA a:DBG_VAR_04
; If the tranfer count is 0, cleanup and return.
ora a:DBG_VAR_03
BEQ @cleanup
; Set the MSB of the transfer count to be 1-based.
ldy #$00
lda a:DBG_VAR_03
beq @transfer_byte
INC a:DBG_VAR_04
@transfer_byte:
; If the address is not in the first bank and first page, then
; transfer the memory contents from the actual memory location.
lda a:DBG_VAR_01
ora a:DBG_VAR_02
BNE @transfer_byte_from_real_addr
; Now see if the address is within the first five bytes of RAM.
; This address is used for debugger command parameters.
lda a:DBG_VAR_00
cmp #$05
BCS @transfer_byte_from_real_addr
; The address is within the 5-byte debugger area in the zero-
; page. First, read the byte from the debugger to write.
tay
JSR Sys_VIA_USB_Char_RX
; Now save the byte into the save area in work-RAM.
STA RAM_ZP_SAVE,y
; Move to the next byte to transfer.
ldy #$00
BEQ @next_addr
@transfer_byte_from_real_addr:
; Read the byte to write from the debugger.
JSR Sys_VIA_USB_Char_RX
; See if we're running on a 65C02 or a 65816.
ldx RAM_CPU_TYPE
BNE @cpu_is_65816
; For the 65C02, ignore the bank, and only use the 16-bit addr.
sta (DBG_VAR_00),y
BEQ @next_addr
@cpu_is_65816:
; Use the ZP-indirect-long mode to write to the full 24-bit addr.
STA [0]
@next_addr:
; Move to the next address.
INC a:DBG_VAR_00
bne @dec_count
inc a:DBG_VAR_01
bne @dec_count
INC a:DBG_VAR_02
@dec_count:
; Decrement the transfer count, and see if we're done.
DEC a:DBG_VAR_03
bne @transfer_byte
dec a:DBG_VAR_04
BNE @transfer_byte
@cleanup:
; Clear RAM_VAR_7E19 for some unknown reason. But it would never
; get set in the first place, so this code seems unnecessary.
LDA RAM_VAR_7E19
beq @done
STZ RAM_VAR_7E19
@done: rts
; Debugger command 3: read memory. The debugger sends a 24-bit starting
; address, then a 16-bit count. and then the monitor sends the request
; memory contents to the debugger. Memory reads to the first 5 bytes of
; memory are automatically redirected to the save area in work-RAM for
; these memory locations.
Dbg_Cmd_3_Read_Mem:
; Read the 24-bit starting address, LSB-first.
jsr Sys_VIA_USB_Char_RX
sta a:DBG_VAR_00 ; 16-bit LSB
jsr Sys_VIA_USB_Char_RX
sta a:DBG_VAR_01 ; Page
jsr Sys_VIA_USB_Char_RX
STA a:DBG_VAR_02 ; Bank
; Read the 16-bit byte count, LSB-first.
jsr Sys_VIA_USB_Char_RX
sta a:DBG_VAR_03 ; Count LSB
jsr Sys_VIA_USB_Char_RX
STA a:DBG_VAR_04 ; Count MSB
; If the byte count is 0, then we're done.
ora a:DBG_VAR_03
BEQ @done
; Set up the count MSB to be 1-based.
ldy #$00
lda a:DBG_VAR_03
beq @transfer_byte
INC a:DBG_VAR_04
@transfer_byte:
; If the address is not in the first bank and first page, then
; transfer the memory contents from the actual memory location.
lda a:DBG_VAR_01
ora a:DBG_VAR_02
BNE @transfer_byte_from_real_addr
; Now see if the address is within the first five bytes of RAM.
; This address is used for debugger command parameters.
lda a:DBG_VAR_00
cmp #$05
BCS @transfer_byte_from_real_addr
; The address is within the 5-byte debugger area in the zero-page,
; so send the contents of the save area in RAM to the debugger.
tay
LDA RAM_ZP_SAVE,y
; Move to the next byte to transfer.
ldy #$00
BEQ @next_addr
@transfer_byte_from_real_addr:
; See if we're running on a 65C02 or a 65816.
lda RAM_CPU_TYPE
BNE @cpu_is_65816
; For the 65C02, ignore the bank, and only use the 16-bit addr.
lda (DBG_VAR_00),y
BRA @next_addr
@cpu_is_65816:
; Use the ZP-indirect-long mode to read from the full 24-bit addr.
LDA [0]
@next_addr:
; Send the byte read from memory to the debugger.
JSR Sys_VIA_USB_Char_TX
; Move to the next address.
inc a:DBG_VAR_00
bne @dec_count
inc a:DBG_VAR_01
bne @dec_count
INC a:DBG_VAR_02
@dec_count:
; Decrement the transfer count, and see if we're done.
DEC a:DBG_VAR_03
bne @transfer_byte
dec a:DBG_VAR_04
BNE @transfer_byte
@done: rts
; Debugger command 4. Get System Info.
Dbg_Cmd_4_Sys_Info:
; Send the start of the monitor work-RAM. This same address is also sent
; later, for some reason. Not sure if these represent two different things
; which happen to have the same address, but each one seems to be used. If
; this copy is changed to something invalid, the debugger has issues, so it
; seems to be used by the debugger, and not ignored.
lda #<__WORK_RAM_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #>__WORK_RAM_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #^__WORK_RAM_LOAD__
JSR Sys_VIA_USB_Char_TX
; Auto-detected CPU type.
lda RAM_CPU_TYPE
JSR Sys_VIA_USB_Char_TX
; Board ID.
lda #BOARD_ID
JSR Sys_VIA_USB_Char_TX
; Send the work-RAM start address again. Not sure why, but this copy is
; the value that is displayed in the "Target Connection Information"
; screen on the debugger, so this copy is used, and not ignored.
lda #<__WORK_RAM_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #>__WORK_RAM_LOAD__
jsr Sys_VIA_USB_Char_TX
LDA #^__WORK_RAM_LOAD__
jsr Sys_VIA_USB_Char_TX
; Send the monitor starting address.
lda #<__MONITOR_HEADER_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #>__MONITOR_HEADER_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #^__MONITOR_HEADER_LOAD__
JSR Sys_VIA_USB_Char_TX
; Shadow vector start (24-bit address, LSB-first).
lda #<__SHADOW_VECTORS_LOAD__
jsr Sys_VIA_USB_Char_TX
LDA #>__SHADOW_VECTORS_LOAD__
JSR Sys_VIA_USB_Char_TX
lda #^__SHADOW_VECTORS_LOAD__
JSR Sys_VIA_USB_Char_TX
; Shadow vector end address + 1.
lda #<(__SHADOW_VECTORS_LOAD__ + __SHADOW_VECTORS_SIZE__)
jsr Sys_VIA_USB_Char_TX
lda #>(__SHADOW_VECTORS_LOAD__ + __SHADOW_VECTORS_SIZE__)
jsr Sys_VIA_USB_Char_TX
LDA #^(__SHADOW_VECTORS_LOAD__ + __SHADOW_VECTORS_SIZE__)
jsr Sys_VIA_USB_Char_TX
; Send the hardware vector address.
lda #<__CPU_VECTORS_LOAD__
jsr Sys_VIA_USB_Char_TX
lda #>__CPU_VECTORS_LOAD__
jsr Sys_VIA_USB_Char_TX
LDA #^__CPU_VECTORS_LOAD__
jsr Sys_VIA_USB_Char_TX
; Send the hardware base IO address.
lda #<__HW_IO_ADDR__
jsr Sys_VIA_USB_Char_TX
lda #>__HW_IO_ADDR__
jsr Sys_VIA_USB_Char_TX
LDA #^__HW_IO_ADDR__
jsr Sys_VIA_USB_Char_TX
; Not sure, but assume this is the last byte of the hardware IO area.
lda #<(__HW_IO_ADDR__ + __HW_IO_SIZE__ - 1)
jsr Sys_VIA_USB_Char_TX
lda #>(__HW_IO_ADDR__ + __HW_IO_SIZE__ - 1)
jsr Sys_VIA_USB_Char_TX
LDA #^(__HW_IO_ADDR__ + __HW_IO_SIZE__ - 1)
jsr Sys_VIA_USB_Char_TX
; Send the hardware breakpoint address.
lda #<__HW_BRK_ADDR__
jsr Sys_VIA_USB_Char_TX
lda #>__HW_BRK_ADDR__
jsr Sys_VIA_USB_Char_TX
lda #^__HW_BRK_ADDR__
jmp Sys_VIA_USB_Char_TX
; Debugger command 5. Restore the CPU context and execute from the saved context.
Dbg_Cmd_5_Exec:
; Restore the user's state of the VIA's PCR register.
JSR Restore_VIA_PCR_State
; Restore the 5 zero-page variables from the copies in work-RAM.
LDX #$04
@copy_zp_var:
lda RAM_ZP_SAVE,x
sta a:DBG_VAR_00,x
dex
BPL @copy_zp_var
; See if the CPU was running in emulation mode.
lda RAM_E_SAVE
BEQ @was_in_native_mode
; The CPU was in emulation mode. Restore the CPU context. Start off
; by restoring the stack and the index registers.
ldx RAM_SP_SAVE
txs
ldx RAM_X_SAVE
LDY RAM_Y_SAVE
; See what CPU type we're running.
lda RAM_CPU_TYPE
BNE @cpu_is_65816
; CPU is 65C02. The below code pushes the saved direct-page contents
; onto the stack. Not sure why, since in emulation mode, these will
; always both be 0.
lda RAM_DP_SAVE+1
pha
lda RAM_DP_SAVE
PHA
; Pull the direct-page register from the stack. But, since this is
; running on a 65C02, this will actually be a no-op. Seems like the