-
Notifications
You must be signed in to change notification settings - Fork 0
/
V1.01_ROM_disassembly_C000.asm
2669 lines (2477 loc) · 136 KB
/
V1.01_ROM_disassembly_C000.asm
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
org 0xc000
PUBLIC _main
; Interrupt Vector Table (?)
; This looks like one
_main:
jp $c030 ;[c000] reset vector
jp $c027 ;[c003] reset SIO interrupts
jp $c027 ;[c006] reset SIO interrupts
jp $c45e ;[c009] putchar()
jp $c027 ;[c00c] reset SIO interrupts
jp $c027 ;[c00f] reset SIO interrupts
jp $c027 ;[c012] reset SIO interrupts
jp $c027 ;[c015] reset SIO interrupts
jp fdc_rwfs_c19d ;[c018] floppy disk software driver
jp putstr ;[c01b] print null-terminated string
jp prhex ;[c01e] print a byte as hex
jp $cde2 ;[c021]
jp $cdf4 ;[c024]
; disable SIO interrupts
label_c027:
di ;[c027] disable interrupts
ld a,$10 ;[c028] load A with $10 (reset SIO interrupts)
out ($b1),a ;[c02a] write SIO control of channel A
out ($b3),a ;[c02c] write SIO control of channel B
jr label_c040 ;[c02e]
; main entrypoint, after reset
; setup uPD8255 (GPIO IC)
; - set PORTA as output (not connected ?)
; - set PORTB as output (bank switching ?)
; - set PORTC as input (both high and low nibble) (bit 1 = used by something unknown, bit 2 = floppy related)
; - set all ports to Mode 0 (latched outputs, not-latched inputs)
ld a,$89 ;[c030] load configuration in A
out ($83),a ;[c032] write configuration
di ;[c034] disable interrupts
ld a,$10 ;[c035]
out ($81),a ;[c037] PORTB = 0x10 (bank 4 in)
; delay, measured oscilloscope 208 ms
ld h,$7d ;[c039] hl = $7d
label_c03b:
dec hl ;[c03b]
ld a,h ;[c03c]
or l ;[c03d] check for hl == 0
jr nz,label_c03b ;[c03e]
label_c040:
ld sp,$0080 ;[c040] setup stack pointer
call $c0c2 ;[c043] initialize IO peripherals
call $c6af ;[c046] initialize display management variables
call $c14e ;[c049] bios_copy_to_ram()
ld a,$12 ;[c04c] load A with $12 (keyboard initialization)
out ($b2),a ;[c04e] send keyboard configuration
label_c050:
in a,($b3) ;[c050]
bit 0,a ;[c052]
jr z,label_c050 ;[c054]
in a,($b2) ;[c056] read A from keyboard
out ($da),a ;[c058] sound speaker beep
ld c,$56 ;[c05a] hardcoded 'V' for splash screen
call $c45e ;[c05c] putchar()
ld hl,$cffc ;[c05f] "Splash screen" string pointer
ld b,$04 ;[c062]
label_c064:
ld c,(hl) ;[c064] for (b = 4; b > 0; --b) {
inc hl ;[c065]
call $c45e ;[c066] putchar()
djnz label_c064 ;[c069] } loop
; Boot procedure: execute a routine associated to a keypress
; - if BOOT, execute bios_bootkey
; - if F15, execute bios_boot_from_8000
bios_waitkey:
call kbd_getchar ;[c06b] kbd_getchar()
ld a,b ;[c06e]
cp $4d ;[c06f]
jr z,bios_bootkey ;[c071] jump if kbd_getchar() == $4D (BOOT key)
cp $5c ;[c073]
jr nz,bios_waitkey ;[c075] repeat if kbd_getchar() != $5C (F15)
; Boot trampoline executed when F15 key is pressed
; Check signature: trampoline is identified by a magic $3C
bios_boot_from_8000:
ld a,($8000) ;[c077] a = (*$8000)
cpl ;[c07a] a = ~a
ld ($8000),a ;[c07b] write back...
ld a,($8000) ;[c07e] ...and read again
cp $c3 ;[c081] check if equal to $C3 (which is absolute JMP opcode)
jr nz,label_c027 ;[c083] if not, reset computer...
jp $8000 ;[c085] ...else, execute trampoline at $8000
; Boot trampoline executed when BOOT key is pressed
bios_bootkey:
ld de,$0000 ;[c088] d = track = 0; e = sector = 0
ld bc,$4000 ;[c08b] b = cmd = read ($40); c = drive = 0
ld hl,$0080 ;[c08e] load in $0080
ld a,$01 ;[c091] formatting mode, seems to be 384 bytes per sector
call fdc_rwfs_c19d ;[c093] invoke reading
cp $ff ;[c096] check for error...
jr nz,bios_bootdisk ;[c098] ...if ok, go on with loading
out ($da),a ;[c09a] ... else, beep and try again
jr bios_bootkey ;[c09c]
; if disk has been correctly copied into RAM, execute it
bios_bootdisk:
ld a,$06 ;[c09e] load A with $06
out ($b2),a ;[c0a0] send to keyboard
out ($da),a ;[c0a2] sound speaker beep
jp $0080 ;[c0a4] execute fresh code from ram
; SUBROUTINE C0A7: kbd_getchar()
; Read from keyboard
; Returns in (B,C) = (first,second)
; |-----------------------|
; BC | 0xxx xxxx | 1xxx xxxx |
; |-----------------------|
; When no keyboard is connected, Sanco loops in this loop forever
kbd_getchar:
in a,($b3) ;[c0a7] read SIO control register of channel B (keyboard)
bit 0,a ;[c0a9] test LSB (character available)
jr z,kbd_getchar ;[c0ab] if no char available, loop
; a char is available from keyboard
in a,($b2) ;[c0ad] read A from keyboard
ld b,a ;[c0af]
bit 7,a ;[c0b0]
jr nz,kbd_getchar ;[c0b2] if A >= 128, discard char and read next one
label_c0b4:
in a,($b3) ;[c0b4] read SIO control register of channel B (keyboard)
bit 0,a ;[c0b6]
jr z,label_c0b4 ;[c0b8] if no char available, loop
in a,($b2) ;[c0ba] read A from keyboard
ld c,a ;[c0bc]
bit 7,a ;[c0bd]
jr z,label_c0b4 ;[c0bf] if A < 128, discard char and read next one
ret ;[c0c1] (B,C) now holds (first,second) char read from keyboard
; SUBROUTINE C0C2
im 2 ;[c0c2] set interrupt mode 2
call $c0d1 ;[c0c4] initialize timer ($e0)
call fdc_init ;[c0c7] initialize floppy drive controller ($c0)
call $c108 ;[c0ca] setup SIO ($b0 peripheral)
call $c88d ;[c0cd] setup CRTC ($a0) and do some stuff with bank switch? ($80)
ret ;[c0d0]
; SUBROUTINE C0D1; initialize timer (base ioaddr(0xE0))
ld hl,$c0f1 ;[c0d1] ; initialization table base address
label_c0d4:
ld a,(hl) ;[c0d4]
inc hl ;[c0d5]
cp $ff ;[c0d6] check for initialization table end
jr z,label_c0e1 ;[c0d8] if table is ended, go on
ld c,a ;[c0da]
ld a,(hl) ;[c0db]
inc hl ;[c0dc]
out (c),a ;[c0dd]
jr label_c0d4 ;[c0df] loop table
label_c0e1:
in a,($d6) ;[c0e1] read index ("$06")
and $07 ;[c0e3] clamp in interval [0;7]
ld d,$00 ;[c0e5]
ld e,a ;[c0e7]
add hl,de ;[c0e8] hl += "$06"
ld a,(hl) ;[c0e9] timer 1: time constant ("$02")
out ($e1),a ;[c0ea]
ld a,$41 ;[c0ec] timer 1: select counter mode
out ($e1),a ;[c0ee]
ret ;[c0f0]
; STATIC DATA for C0D1
; timer 2: configuration
BYTE $e2
BYTE $05
; timer 2: time constant
BYTE $e2
BYTE $10
; timer 2: select counter mode
BYTE $e2
BYTE $41
; timer 3: configuration
BYTE $e3
BYTE $05
; timer 3: time constant
BYTE $e3
BYTE $01
; timer 3: select counter mode
BYTE $e3
BYTE $41
; timer 1: configuration
BYTE $e1
BYTE $05
; terminator
BYTE $ff
; probably another table
; len = 8, deduced from what happens at C0E3
; this could be some prescaler for timer1
BYTE $ae ;[c100]
BYTE $40 ;[c101]
BYTE $20 ;[c102]
BYTE $10 ;[c103]
BYTE $08 ;[c104]
BYTE $04 ;[c105]
BYTE $02 ;[c106]
BYTE $01 ;[c107]
; SIO configuration routine
; Channel A runs at 153.6 kHz / 16 = 9600 bps (user RS/232)
; Channel B runs at 19.2 kHz / 16 = 1200 bps (keyboard)
; $b0 : channel A, data register
; $b1 : channel A, control register
; $b2 : channel B, data register
; $b3 : channel B, control register
ld hl,$c134 ;[c108]
; loop around $c134 table, writing to $b1 (channel A)
label_c10b:
ld a,(hl) ;[c10b] load index of internal SIO register
inc hl ;[c10c] fetch next data
cp $ff ;[c10d] check table end
jr z,label_c119 ;[c10f] if table end, configure channel B
out ($b1),a ;[c111] index internal SIO register
ld a,(hl) ;[c113] load desired internal SIO register value
out ($b1),a ;[c114] write desidered internal SIO register value
inc hl ;[c116] fetch next data
jr label_c10b ;[c117] loop
; loop around $c141 table, writing to $b3 (channel B)
; same as channel A
label_c119:
ld a,(hl) ;[c119]
cp $ff ;[c11a]
jr z,label_c127 ;[c11c]
out ($b3),a ;[c11e]
inc hl ;[c120]
ld a,(hl) ;[c121]
out ($b3),a ;[c122]
inc hl ;[c124]
jr label_c119 ;[c125]
; Each SIO channel has a 3 word RX FIFO: flush them.
label_c127:
in a,($b0) ;[c127] read channel A data register (and discard)
in a,($b2) ;[c129] read channel B data register (and discard)
in a,($b0) ;[c12b]
in a,($b2) ;[c12d]
in a,($b0) ;[c12f]
in a,($b2) ;[c131]
ret ;[c133]
; Configuration table for SIO ChA?
sio_chA_cfg_base:
BYTE $00 ;[c134] register 0
BYTE $10 ;[c135] reset peripheral interrupts
BYTE $00 ;[c136]
BYTE $10 ;[c137]
BYTE $04 ;[c138] register 4
BYTE $44 ;[c139] 1 stop bit, /16 clock divider
BYTE $01 ;[c13a] register 1
BYTE $00 ;[c13b] disable all interrupts
BYTE $03 ;[c13c] register 3
BYTE $c1 ;[c13d] rx 8 bit word, synch character load inhibit
BYTE $05 ;[c13e] register 5
BYTE $ea ;[c13f] enable tx, RTS, DTR, tx 8 bit word
BYTE $ff ;[c140] end of table
; Configuration table for SIO ChB?
; Note: channel configuration is identical as above
sio_chB_cfg_base:
BYTE $00 ;[c141]
BYTE $10 ;[c142]
BYTE $00 ;[c143]
BYTE $10 ;[c144]
BYTE $04 ;[c145]
BYTE $44 ;[c146]
BYTE $01 ;[c147]
BYTE $00 ;[c148]
BYTE $03 ;[c149]
BYTE $c1 ;[c14a]
BYTE $05 ;[c14b]
BYTE $ea ;[c14c]
BYTE $ff ;[c14d]
; SUBROUTINE C14E; bios_copy_to_ram()
; Loads a piece of code in RAM, then executes it
ld hl,$c165 ;[c14e]
ld de,$0010 ;[c151]
ld bc,$000f ;[c154]
; LDIR: memcpy(de: dst, hl: src, bc: size)
ldir ;[c157] copy code from [$c165:$c174] to [$0010:$000f]
ld hl,$0000 ;[c159]
ld de,$0000 ;[c15c]
ld bc,$0000 ;[c15f]
jp $0010 ;[c162] jump to just loaded code
; This $c165/$0010 routine copies whole RAM content, from [$0000:$ffff],
; into the same location. This recomputes the parity bit of all memory locations.
; Parity is held in DRAM chip located in cell K9.
; this code is executed from [$0010:$000e]
; arguments:
; - hl: src
; - de: dst
; - bc: size
in a,($81) ;[c165]/[0010]
set 0,a ;[c167]/[0012]
out ($81),a ;[c169]/[0014] PORTB |= 0x01 (bank 0 in)
ldir ;[c16b]/[0016] perform the memcpy
res 0,a ;[c16d]/[0018]
out ($81),a ;[c16f]/[001a] PORTB &= ~0x01 (bank 0 out)
out ($de),a ;[c171]/[001c] and copy same value in $de
ret ;[c173]/[001e]
; prhex(a) - prints the hex value in a
; This function is a "giro di labbrate" (i.e. spaghetti code, very hard to explain line by line)
prhex:
push af ;[c174] save a
rrca ;[c175]
rrca ;[c176]
rrca ;[c177]
rrca ;[c178]
and $0f ;[c179] take most significant nibble
call prmsn ;[c17b] convert to ASCII digit and print it (0-9A-F)
pop af ;[c17e] restore a
and $0f ;[c17f] take least significant nibble
prmsn:
call tohex ;[c181] convert to ASCII digit
jp $c45e ;[c184] print it (0-9A-F)
; Interesting part of code that converts least significant nibble in a
; to an ASCII digit
tohex:
add $90 ;[c187]
daa ;[c189] decimal adjust
adc $40 ;[c18a]
daa ;[c18c] decimal adjust
ld c,a ;[c18d] move a in c for putchar(c)
ret ;[c18e]
; putstr: prints a null-terminated string
; String is automatically passed when calling this routine, and must be located immediately after the call. Example:
; call putstr
; BYTE "Hello, world!\x00"
; ;;; rest of the code...
putstr:
ex (sp),hl ;[c18f] load the argument from the stack in hl
label_c190:
ld a,(hl) ;[c190]
inc hl ;[c191] a = *(hl++)
or a ;[c192]
jr z,label_c19b ;[c193] return if a == '\0'
ld c,a ;[c195]
call $c45e ;[c196] putchar()
jr label_c190 ;[c199] repeat again
label_c19b:
ex (sp),hl ;[c19b] reload altered hl onto the stack
ret ;[c19c]
; FDC Read Write Format Seek routine.
; Arguments:
; - a: bytes per sector "shift factor", bps = 0x80 << a
; - b: operation command, see switch in this routine
; - c: drive number (0-3) + HD flag
; - d: track number
; - e: sector number
; - hl: read/write buffer address
fdc_rwfs_c19d:
push bc ;[c19d]
push de ;[c19e]
push hl ;[c19f]
ld ($ffb8),a ;[c1a0] bytes per sector
ld a,$0a ;[c1a3]
ld ($ffbf),a ;[c1a5] value used in error checking routines
ld ($ffb9),bc ;[c1a8] *$ffb9 = drive no. + HD flag, *$ffba = operation command
ld ($ffbb),de ;[c1ac] *$ffbb = sector number, *$ffbc = track number
ld ($ffbd),hl ;[c1b0] base address for read/write buffer
call fdc_initialize_drive_c423 ;[c1b3] move head to track 0 if never done before on current drive
ld a,($ffba) ;[c1b6] load command byte
and $f0 ;[c1b9] take only the most significant nibble
jp z,fdc_sw_track0 ;[c1bb] case $00: move to track 0 (home)
cp $40 ;[c1be]
jp z,fdc_sw_read_data ;[c1c0] case $40: read sector in hl buffer
cp $80 ;[c1c3]
jp z,fdc_sw_write_data ;[c1c5] case $80: write sector in hl buffer
cp $20 ;[c1c8]
jp z,fdc_sw_seek ;[c1ca] case $20: move head to desired track
cp $f0 ;[c1cd]
jp z,fdc_sw_format ;[c1cf] case $f0: format desired track
ld a,$ff ;[c1d2]
jp fdc_sw_default ;[c1d4] default: return -1
fdc_sw_write_data:
call fdc_write_data_c1f4 ;[c1d7]
jr fdc_sw_default ;[c1da]
fdc_sw_read_data:
call fdc_read_data_c24a ;[c1dc]
jr fdc_sw_default ;[c1df]
fdc_sw_seek:
call fdc_seek_c3a9 ;[c1e1]
jr fdc_sw_default ;[c1e4]
fdc_sw_track0:
call fdc_track0_c391 ;[c1e6]
jr fdc_sw_default ;[c1e9]
fdc_sw_format:
call fdc_format_c2e3 ;[c1eb]
jr fdc_sw_default ;[c1ee]
fdc_sw_default:
pop hl ;[c1f0]
pop de ;[c1f1]
pop bc ;[c1f2]
ret ;[c1f3]
; FDC write data routine
; Writes data from *$ffbd to desired track/sector
; Arguments are stored in $ffb8-$ffbf, as explained in caller fdc_rwfs_c19d
fdc_write_data_c1f4:
call fdc_seek_c3a9 ;[c1f4] move to desired track
fdc_write_retry_c1f7:
call fdc_compute_bps_c2b7 ;[c1f7] compute byte per sectors, result in de
push de ;[c1fa]
call fdc_wait_busy ;[c1fb]
ld c,$c5 ;[c1fe] load "write data" command with MT and MF flags set
ld a,($ffb8) ;[c200]
or a ;[c203]
jr nz,label_c208 ;[c204] if *ffb8 == 0 (128 bytes per sector)...
res 6,c ;[c206] ...clear MF flag (FM mode)
label_c208:
call fdc_send_cmd ;[c208] send the "write data" command with desired MF flag
di ;[c20b] disable interrupts
call fdc_send_rw_args_c34e ;[c20c] send "write data" arguments (common with "read data" arguments)
pop de ;[c20f]
ld c,$c1 ;[c210] prepare IO address in c
ld b,e ;[c212] load number of bytes to write (LSB)
ld hl,($ffbd) ;[c213] load base address of writing buffer
; Buffer writing loop
label_c216:
in a,($82) ;[c216] read PORTC
bit 2,a ;[c218]
jr z,label_c216 ;[c21a]
in a,($c0) ;[c21c] read FDC main status register
bit 5,a ;[c21e] check if still in execution phase...
jr z,label_c229 ;[c220] ...if not, end writing
outi ;[c222] write data from buffer to FDC: IO(c) = *(hl++); b--;
jr nz,label_c216 ;[c224]
dec d ;[c226] bytes per sector is usually 512, must use a double byte counter
jr nz,label_c216 ;[c227] write ends when d = 0 and b = 0
label_c229:
out ($dc),a ;[c229]
ei ;[c22b] enable interrupts again
call fdc_rw_status_c3f4 ;[c22c] command response, put it in $ffc0-$ffc6
ld a,($ffc0) ;[c22f] fetch status (ST0)
and $c0 ;[c232] mask Interrupt Code bits (as in fdc_sis routine)...
cp $40 ;[c234]
jr nz,label_c248 ;[c236] ... and return if IC != 01 (!= "readfail")
call fdc_err_check_c2a0 ;[c238] after-write error checking (common with "read data")
ld a,($ffbf) ;[c23b] keep a retry counter to avoid infinite loops
dec a ;[c23e] decrement number of remaining retry
ld ($ffbf),a ;[c23f]
jp nz,fdc_write_retry_c1f7 ;[c242] after 256 retry give up and...
ld a,$ff ;[c245]
ret ;[c247] ... return -1
label_c248:
xor a ;[c248]
ret ;[c249] return 0
; FDC read data routine
; Read data from desired track/sector to *$ffbd
; Arguments are stored in $ffb8-$ffbf, as explained in caller fdc_rwfs_c19d
fdc_read_data_c24a:
call fdc_seek_c3a9 ;[c24a] move to desired track
fdc_read_retry_c24d:
call fdc_compute_bps_c2b7 ;[c24d] compute byte per sectors, result in de
push de ;[c250]
call fdc_wait_busy ;[c251]
ld c,$c6 ;[c254] load "read data" command with MT and MF flags set
ld a,($ffb8) ;[c256]
or a ;[c259]
jr nz,label_c25e ;[c25a] if *ffb8 == 0 (128 bytes per sector)...
res 6,c ;[c25c] ...clear MF flag (FM mode)
label_c25e:
call fdc_send_cmd ;[c25e] send the "read data" command
di ;[c261] disable interrupts
call fdc_send_rw_args_c34e ;[c262] send "read data" arguments (common with "write data" arguments)
pop de ;[c265]
ld c,$c1 ;[c266] prepare IO address in c
ld b,e ;[c268] load number of bytes to write (LSB)
ld hl,($ffbd) ;[c269] load base address of reading buffer
label_c26c:
in a,($82) ;[c26c] read PORTC
bit 2,a ;[c26e]
jr z,label_c26c ;[c270]
in a,($c0) ;[c272] read FDC main status register
bit 5,a ;[c274] check if still in execution phase...
jr z,label_c27f ;[c276] ...if not, end reading
ini ;[c278] read data from FDC to *hl, hl++, b--
jr nz,label_c26c ;[c27a]
dec d ;[c27c] bytes per sector is usually 512, must use a double byte counter
jr nz,label_c26c ;[c27d] read ends when d = 0 and b = 0
label_c27f:
out ($dc),a ;[c27f]
ei ;[c281] enable interrupts again
call fdc_rw_status_c3f4 ;[c282] command response, put it in $ffc0-$ffc6
ld a,($ffc0) ;[c285] fetch status (ST0)
and $c0 ;[c288] mask Interrupt Code bits (as in fdc_sis routine)...
cp $40 ;[c28a]
jr nz,label_c29e ;[c28c]... and return if IC != 01 (!= "readfail")
call fdc_err_check_c2a0 ;[c28e] after-write error checking (common with "read data")
ld a,($ffbf) ;[c291] keep a retry counter to avoid infinite loops
dec a ;[c294] decrement number of remaining retry
ld ($ffbf),a ;[c295]
jp nz,fdc_read_retry_c24d ;[c298] after 256 retry give up and...
ld a,$ff ;[c29b]
ret ;[c29d] ... return -1
label_c29e:
xor a ;[c29e]
ret ;[c29f] return 0
; TODO this is a FDC routine related to error checking after read/write
fdc_err_check_c2a0:
ld a,($ffc2) ;[c2a0] read 2nd status register (ST1)
bit 4,a ;[c2a3] check OverRun bit (OR)
jr z,label_c2ab ;[c2a5] if not set, return, else...
call fdc_track0_c391 ;[c2a7] ...reset head position...
ret ;[c2aa] ...and return for retry
label_c2ab:
ld a,($ffc1) ;[c2ab] read 3rd status register (ST2)
bit 0,a ;[c2ae] check Missing Address Mark in Data Field (MD) bit
jr z,label_c2b6 ;[c2b0] if not set, return, else...
call fdc_track0_c391 ;[c2b2] ...reset head position...
ret ;[c2b5] ...and return for retry
label_c2b6:
ret ;[c2b6] return and just retry
; Compute number of bytes per sector
; Arguments:
; - $ffb8: bytes per sector "shift factor". If = 0, FM encoding is used. If != 0, MFM encoding is used
; - $ffba: lower nibble, manually add some more bps (TODO)
; - $ffbb: sector number (only bit 7 is considered)
; Return:
; - de: bytes per sector
fdc_compute_bps_c2b7:
ld e,$00 ;[c2b7] e = 0
ld a,($ffb8) ;[c2b9] load bytes per sector
cp $03 ;[c2bc]
jr nz,label_c2d4 ;[c2be] if 1024 bytes per sector (*$ffb8 == 3)...
ld d,$04 ;[c2c0] ... d = 4
ld a,($ffbb) ;[c2c2] load sector number
bit 7,a ;[c2c5]
jr z,label_c2e2 ;[c2c7] if MSb is not set, return
ld a,($ffba) ;[c2c9] load (lower nibble of) operation command
and $0f ;[c2cc]
rlca ;[c2ce]
rlca ;[c2cf]
add d ;[c2d0]
ld d,a ;[c2d1] d = ((*$ffba & 0xf) + 1) * 4
jr label_c2e2 ;[c2d2] return, d as above, e = 0
label_c2d4: ; if bytes per sector != 1024...
or a ;[c2d4]
jr nz,label_c2d9 ;[c2d5] if bytes per sector != 128 (*$ffb8 = 0)...
ld e,$80 ;[c2d7] e = 128
label_c2d9:
ld a,($ffba) ;[c2d9] load (lower nibble of) operation command
and $0f ;[c2dc]
ld d,$01 ;[c2de]
add d ;[c2e0]
ld d,a ;[c2e1] d = (*$ffba & 0xf) + 1
label_c2e2:
ret ;[c2e2]
; Format floppy disk
; Arguments are stored in $ffb8-$ffbf, as explained in caller fdc_rwfs_c19d
fdc_format_c2e3:
call fdc_seek_c3a9 ;[c2e3] move to desired track
cp $ff ;[c2e6] if not able to locate track...
ret z ;[c2e8] ...return
ld b,$14 ;[c2e9] b default value is 20
ld a,($ffb8) ;[c2eb] a is bytes per sector
cp $03 ;[c2ee]
jr z,label_c2f4 ;[c2f0] if less than 1024 bytes per sector...
ld b,$40 ;[c2f2] b = 64
label_c2f4:
push bc ;[c2f4]
call fdc_wait_busy ;[c2f5]
ld c,$4d ;[c2f8]
call fdc_send_cmd ;[c2fa] send "write id" command
ld bc,($ffb9) ;[c2fd] 1st argument: drive number (c <= *$ffb9)
call fdc_send_cmd ;[c301]
ld a,($ffb8) ;[c304] 2nd argument: bytes per sector "factor"
ld c,a ;[c307]
call fdc_send_cmd ;[c308]
ld c,$05 ;[c30b] default sectors/track number, 5
ld a,($ffb8) ;[c30d] same as before, load formatting encoding in a
cp $03 ;[c310]
jr z,label_c316 ;[c312]
ld c,$10 ;[c314] if *$ffb8 != 3, sectors/track = 16
label_c316:
call fdc_send_cmd ;[c316] 3rd argument: sectors/track number
ld c,$28 ;[c319] gap length is 40
call fdc_send_cmd ;[c31b] 4rd argument: gap3 length
di ;[c31e] disable interrupts
ld c,$e5 ;[c31f]
call fdc_send_cmd ;[c321] 5th argument: filler byte value
pop bc ;[c324] reload bytes per sector in b
ld c,$c1 ;[c325]
ld hl,($ffbd) ;[c327]
label_c32a:
in a,($82) ;[c32a] read PORTC
bit 2,a ;[c32c]
jr z,label_c32a ;[c32e]
in a,($c0) ;[c330] read main status register
bit 5,a ;[c332] check if still in execution phase...
jr z,label_c33a ;[c334] ...if not, end formatting
outi ;[c336] TODO why do I have to write something if i'm formatting?
jr nz,label_c32a ;[c338]
label_c33a:
out ($dc),a ;[c33a]
ei ;[c33c]
call fdc_rw_status_c3f4 ;[c33d] command response, put it in $ffc0-$ffc6
ld a,($ffc0) ;[c340] fetch status (ST0)
and $c0 ;[c343] mask Interrupt Code bits (as in fdc_sis routine)...
cp $40 ;[c345]
jr nz,label_c34c ;[c347] ... and return if IC != 01 (!= "readfail")
ld a,$ff ;[c349]
ret ;[c34b] return -1
label_c34c:
xor a ;[c34c]
ret ;[c34d] return 0
; FDC utility function: send arguments for read or write data commands
fdc_send_rw_args_c34e:
ld bc,($ffb9) ;[c34e] 1st argument: load drive number
call fdc_send_cmd ;[c352]
ld de,($ffbb) ;[c355] 2nd argument: track number
ld c,d ;[c359] track is in d <= *$ffbc
call fdc_send_cmd ;[c35a]
ld bc,($ffb9) ;[c35d] ffb9 contains HD flag too (physical head number)
ld a,c ;[c361]
and $04 ;[c362] extract bit 2 (HD)
rrca ;[c364]
rrca ;[c365] Move in bit 0 position
ld c,a ;[c366]
call fdc_send_cmd ;[c367] 3rd argument: head number (0/1)
res 7,e ;[c36a] reset bit 7 in e (sector number register loaded before)
ld c,e ;[c36c]
inc c ;[c36d] hypothesys: sector number - 1 is stored in $ffbb
call fdc_send_cmd ;[c36e] 4th argument: sector number to write
ld a,($ffb8) ;[c371]
ld c,a ;[c374]
call fdc_send_cmd ;[c375] 5th argument: bytes per sector "factor"
ld c,$05 ;[c378] default value for EOT = 5
ld a,($ffb8) ;[c37a] load bytes per sector "factor"
cp $03 ;[c37d]
jr z,label_c383 ;[c37f] if less than 1024 bytes per sector...
ld c,$10 ;[c381] override EOT with c = 16
label_c383:
call fdc_send_cmd ;[c383] 6th argument: EOT - final sector of a track
ld c,$28 ;[c386]
call fdc_send_cmd ;[c388] 7th argument: GPL - gap length fixed to 0x28
ld c,$ff ;[c38b]
call fdc_send_cmd ;[c38d] 8th argument: DTL - data length, should be invalid if 5th argument is != 0
ret ;[c390]
; This routine seems to move the floppy head to track 0, then waits for the operation execution
fdc_track0_c391:
call fdc_wait_busy ;[c391]
ld c,$07 ;[c394] Recalibrate command
call fdc_send_cmd ;[c396] Send command to FDC
ld bc,($ffb9) ;[c399] Load drive number?
res 2,c ;[c39d] For some reason, clear bit 2. Recalibrate argument must be 0b000000xx, where xx = drive number in [0,3]
call fdc_send_cmd ;[c39f] Send command to FDC
call fdc_sis_c3d2 ;[c3a2] send SIS to check if head movement was correctly completed
jr z,fdc_track0_c391 ;[c3a5] check if return is "readfail" (Z = 0) then retry, else...
xor a ;[c3a7] ... return 0
ret ;[c3a8]
; FDC: sends the seek command and move head upon desired track
; Arguments:
; - $ffbb: new track number
; - $ffb9: drive number
fdc_seek_c3a9:
ld de,($ffbb) ;[c3a9] load track number
ld a,d ;[c3ad] track number is in d <= *$ffbc
or a ;[c3ae] check if requested track is 0...
jp z,fdc_track0_c391 ;[c3af] if track = 0, skip this and call appropriate routine
call fdc_wait_busy ;[c3b2]
ld c,$0f ;[c3b5] send "seek" command
call fdc_send_cmd ;[c3b7]
ld bc,($ffb9) ;[c3ba] 1st arg: load and send *$ffb9 = drive number + HD flag
call fdc_send_cmd ;[c3be]
ld c,d ;[c3c1] 2nd arg: send NCN (new cylinder number) = desired track
call fdc_send_cmd ;[c3c2]
call fdc_sis_c3d2 ;[c3c5] sends SIS to check if head movement was correctly completed
jr nz,label_c3d0 ;[c3c8] if fdc_sis returns "OK" (Z != 0), return, else...
call fdc_track0_c391 ;[c3ca] ...move head to track 0...
jp fdc_seek_c3a9 ;[c3cd] ...and try again
label_c3d0:
xor a ;[c3d0] On success, a=0 and return
ret ;[c3d1]
; FDC utility function: send "Sense Interrupt Status" command and read the two bytes (ST0, PCN)
; Return:
; - Z flag from the comparison (ST0 & 0xC0) == 0x40.
; ST0[7:6] is Interrupt Code, and is:
; - 00 if previous operation was successful (OK);
; - 01 if previous operation was not successful (readfail);
; - other cases are treated as successful.
fdc_sis_c3d2:
in a,($82) ;[c3d2] read PORTC
bit 2,a ;[c3d4]
jp z,fdc_sis_c3d2 ;[c3d6] busy wait until PORTC[2] != 0
call fdc_wait_busy ;[c3d9]
call fdc_wait_rqm_wr ;[c3dc] wait until FDC is ready for write request
ld a,$08 ;[c3df] send "Sense Interrupt Status" command
out ($c1),a ;[c3e1]
call fdc_wait_rqm_rd ;[c3e3] wait for data ready from FDC
in a,($c1) ;[c3e6] read status byte (ST0)
ld b,a ;[c3e8]
call fdc_wait_rqm_rd ;[c3e9]
in a,($c1) ;[c3ec] read present cylinder number (PCN), aka current track
ld a,b ;[c3ee] discard PCN
and $c0 ;[c3ef]
cp $40 ;[c3f1] perform (ST0 & 0xC0) == 0x40
ret ;[c3f3] return is in Z flag
; FDC utility function: read response after read/write/format execution.
; A 7 byte response is given, read it all in $ffc0-$ffc6
fdc_rw_status_c3f4:
ld hl,$ffc0 ;[c3f4] buffer pointer
ld b,$07 ;[c3f7] data length, answer is 7 byte long
ld c,$c1 ;[c3f9] IO address
label_c3fb:
call fdc_wait_rqm_rd ;[c3fb] wait until FDC is ready to send data
ini ;[c3fe] read from IO in *hl, hl++, b--
jr nz,label_c3fb ;[c400] end if b = 0
ret ;[c402]
; SUBROUTINE C403 ; wait for ioaddr(0xc0) to become "0b10xxxxxx"
; FDC utility function: read main status register and wait for RQM = 1 and DIO = 0.
; RQM = request from master, RQM = 1 means FDC is ready for communication with the CPU
; DIO = data input/output, DIO = 0 means transfer from CPU to FDC
fdc_wait_rqm_wr:
in a,($c0) ;[c403]
rlca ;[c405]
jr nc,fdc_wait_rqm_wr ;[c406] while (bit7 == 0), try again
rlca ;[c408]
jr c,fdc_wait_rqm_wr ;[c409] while (bit7 == 1) && (bit6 == 1), try again
ret ;[c40b]
; SUBROUTINE C40C ; wait for ioaddr(0xc0) to become "0b11xxxxxx"
; FDC utility function: read main status register and wait for RQM = 1 and DIO = 1
; RQM = request from master, RQM = 1 means FDC is ready for communication with the CPU
; DIO = data input/output, DIO = 1 means transfer from FDC to CPU
fdc_wait_rqm_rd:
in a,($c0) ;[c40c]
rlca ;[c40e]
jr nc,fdc_wait_rqm_rd ;[c40f] while (bit7 == 0), try again
rlca ;[c411]
jr nc,fdc_wait_rqm_rd ;[c412] while (bit7 == 1) && (bit6 == 0), try again
ret ;[c414]
; SUBROUTINE C415
; FDC utility function: send a command byte to the FDC
; Arguments:
; - c: the command byte
fdc_send_cmd:
call fdc_wait_rqm_wr ;[c415] wait until FDC is ready to receive data
ld a,c ;[c418]
out ($c1),a ;[c419] actually send the comamnd
ret ;[c41b]
; SUBROUTINE C41C ; while( ioaddr(0xc0).4 == 1 ), wait
; FDC utility function: wait until the FDC is no more busy.
; $C0 may be the main status register, where bit 4 is the CB (active high busy) flag.
fdc_wait_busy:
in a,($c0) ;[c41c]
bit 4,a ;[c41e]
jr nz,fdc_wait_busy ;[c420]
ret ;[c422]
; FDC utility routine: IDEA reset head position at least one time per drive since the computer was turned on
; Arguments:
; - c: drive number from rwfs routine
fdc_initialize_drive_c423:
ld b,$01 ;[c423]
ld a,c ;[c425]
and $03 ;[c426] mask drive number only
or a ;[c428]
jr z,label_c430 ;[c429] if drive is != 0 (not drive A)...
label_c42b:
rlc b ;[c42b] at the end of the cycle...
dec a ;[c42d] ... b = 1 << (drive number)
jr nz,label_c42b ;[c42e]
label_c430:
ld a,($ffc7) ;[c430] idea: *ffc7 = mask of the accessed drives
ld c,a ;[c433]
and b ;[c434]
ret nz ;[c435] return if rwfs is trying to access an already "initialized" drive
ld a,c ;[c436]
or b ;[c437] else, mark this drive as initialized...
ld ($ffc7),a ;[c438] ...store this information in ram...
call fdc_track0_c391 ;[c43b] ...and perform initialization (aka moving head to track 0)
ret ;[c43e]
; SUBROUTINE C43F
; FDC initialization.
; Configure the FDC IC with:
; - SRT = 6 (Step Rate Time = 6ms)
; - HUT = F (Head Unload Time = 240ms)
; - HLT = A (Head Load Time = 22ms)
; - ND = 1 (DMA mode disabled)
fdc_init:
push bc ;[c43f]
push hl ;[c440]
ld hl,fdc_cfg_base ;[c441] prepare HL to address FDC configuration table
call fdc_wait_busy ;[c444]
ld c,$03 ;[c447] send "specify" command
call fdc_send_cmd ;[c449]
ld c,(hl) ;[c44c] load first "specify" argument from table
inc hl ;[c44d]
call fdc_send_cmd ;[c44e] send SRT | HUT
ld c,(hl) ;[c451] load second "specify" argument from table
call fdc_send_cmd ;[c452] send HLT | ND
xor a ;[c455]
ld ($ffc7),a ;[c456] *$ffc7 = 0
pop hl ;[c459]
pop bc ;[c45a]
ret ;[c45b]
; STATIC DATA for C43F
fdc_cfg_base:
BYTE $6f ;[c45c] SRT << 4 | HUT
BYTE $1b ;[c45d] HLT << 1 | ND
; SUBROUTINE C45E: bios_putchar()
; Input
; C: character to be printed
bios_putchar_c45e:
push af ;[c45e] save all registers
push bc ;[c45f]
push de ;[c460]
push hl ;[c461]
push ix ;[c462]
push iy ;[c464]
call $c69a ;[c466] bios_load_ix_iy()
; if escaping a char from previous call, then jump to second-stage escape handler
ld a,($ffd8) ;[c469]
or a ;[c46c]
jp nz,$c9e3 ;[c46d]
; check if should override character switch behaviour
ld a,($ffcc) ;[c470]
cp $ff ;[c473] if *$ffcc is == $ff...
jp z,$c6a3 ;[c475] save_index_restore_registers_and_ret()
or a ;[c478] if *$ffcc is != 0...
jp nz,label_c4be ;[c479] print c even if is not a printable char
; Character switch: handle not printable characters
ld a,c ;[c47c]
cp $1b ;[c47d] jump if ESC
jr z,label_c4b6 ;[c47f]
cp $20 ;[c481] jump if printable (>=' ')
jp nc,label_c4be ;[c483]
cp $0d ;[c486] jump if CR
jp z,label_c524 ;[c488]
cp $0a ;[c48b] jump if LF
jp z,label_c532 ;[c48d]
cp $0b ;[c490] jump if TAB
jp z,label_c558 ;[c492]
cp $0c ;[c495] jump if NewPage
jp z,label_c56f ;[c497]
cp $08 ;[c49a] jump if Backspace
jp z,label_c59b ;[c49c]
cp $1e ;[c49f] jump if RS
jp z,label_c5db ;[c4a1]
cp $1a ;[c4a4] jump if SUB
jp z,label_c5ee ;[c4a6]
cp $07 ;[c4a9] jump if BEL
call z,label_c5f4 ;[c4ab]
cp $00 ;[c4ae] jump if NUL
jp z,label_c6a3 ;[c4b0] save_index_restore_registers_and_ret()
jp label_c4be ;[c4b3] jump if any other not printable character
; Handle escape (first stage): remember that a character is being escaped, for next putchar()
label_c4b6:
ld a,$01 ;[c4b6]
ld ($ffd8),a ;[c4b8] "ecaped_char" = 1
jp label_c6a3 ;[c4bb] save_index_restore_registers_and_ret()
; Handle all, but special characters
label_c4be:
push iy ;[c4be]
pop hl ;[c4c0] hl <- iy (copy of cursor position)
call $c715 ;[c4c1] display_cursor_to_video_mem_ptr()
ld (hl),c ;[c4c4] put character in video memory
call $c795 ;[c4c5] bank_video_attribute_memory()
ld a,($ffd1) ;[c4c8]
ld b,a ;[c4cb]
ld a,($ffd2) ;[c4cc]
and (hl) ;[c4cf] a = *(0xffd2) & character in video memory
or b ;[c4d0] a |= *(0xffd1)
ld (hl),a ;[c4d1] write again in video memory
call $c79e ;[c4d2] bank_video_char_memory()
call $c5f8 ;[c4d5] increment cursor column position
jr c,label_c4e0 ;[c4d8] if posx > "max column width", must do something else before the end
call $c613 ;[c4da] increments hl counter and update cursor position in CRTC
jp label_c6a3 ;[c4dd] putchar epilogue: save_index_restore_registers_and_ret()
label_c4e0:
ld a,($ffcb) ;[c4e0] load "current_row"
ld b,a ;[c4e3]
ld a,($ffcd) ;[c4e4] read "number of rows" of display
cp b ;[c4e7]
jr z,label_c501 ;[c4e8] jump if posy reached last row
inc b ;[c4ea] increment cursor posy?
ld a,b ;[c4eb]
ld ($ffcb),a ;[c4ec] save "current_row"
ld a,($ffc9) ;[c4ef]
or a ;[c4f2]
jr nz,label_c4fb ;[c4f3] jump if *$ffc9 != 0
call $c613 ;[c4f5] increments hl counter and update cursor position in CRTC
jp label_c6a3 ;[c4f8] putchar epilogue: save_index_restore_registers_and_ret()
; SUBROUTINE 0xC4FB; called by c4e0 if *$ffc9 == 0
label_c4fb:
call $c620 ;[c4fb]
jp label_c6a3 ;[c4fe] putchar epilogue: save_index_restore_registers_and_ret()
; SUBROUTINE C501
; Perform display frame scroll
label_c501:
ld a,($ffc9) ;[c501]
or a ;[c504]
jr nz,label_c510 ;[c505]
call $c613 ;[c507]
call $c62e ;[c50a]
jp label_c6a3 ;[c50d] putchar epilogue: save_index_restore_registers_and_ret()
label_c510:
ld a,($ffcd) ;[c510] read "number of rows" of display
ld b,a ;[c513]
ld a,($ffd0) ;[c514]
ld c,a ;[c517]
call $c6f1 ;[c518] display_add_row_column()
call $c71c ;[c51b] crtc_update_cursor_position()
call $c62e ;[c51e]
jp label_c6a3 ;[c521] save_index_restore_registers_and_ret()
; print CR ($0d) Carriage Return
label_c524:
ld a,($ffd0) ;[c524]
ld ($ffca),a ;[c527] copy content $FFCA = $FFD0
ld c,a ;[c52a] C = var$FFD0
ld a,($ffcb) ;[c52b]
ld b,a ;[c52e] B = "current_row"
jp label_c5e5 ;[c52f]
; print LF ($0a) Line Feed
label_c532:
ld a,($ffcb) ;[c532]
ld b,a ;[c535] B = "current_row"
ld a,($ffcd) ;[c536] read "number of rows" of display
cp b ;[c539]
jr z,label_c54b ;[c53a] if "current_row" != "number of rows" {
inc b ;[c53c]
ld a,b ;[c53d]
ld ($ffcb),a ;[c53e] "current_row" ++
label_c541:
push iy ;[c541]
pop hl ;[c543]
ld de,$0050 ;[c544]
add hl,de ;[c547] IY += 80 (next row, in linear position)
jp label_c5e8 ;[c548] always the same save, cleanup and return stuff
; }
label_c54b:
call $c62e ;[c54b]
ld a,($ffcb) ;[c54e] read "current_row"
ld b,a ;[c551]
ld a,($ffca) ;[c552] read "current_column"
ld c,a ;[c555]
jr label_c541 ;[c556]
; print TAB ($09)
label_c558:
ld a,($ffcb) ;[c558] read "current_row"
ld b,a ;[c55b]
ld a,($ffce) ;[c55c] read current column? what
cp b ;[c55f] if A == B
jp z,label_c6a3 ;[c560] save_index_restore_registers_and_ret()
dec b ;[c563]
ld a,b ;[c564]
ld ($ffcb),a ;[c565] "current_row" --