-
Notifications
You must be signed in to change notification settings - Fork 2
/
81-232.s
2107 lines (1940 loc) · 62.4 KB
/
81-232.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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Analysis of the Kaypro II ROM
;
; Based on 81-232.rom
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; NOTES:
;
; The code if similar to the 81-149c ROM with the following
; differences:
; - The welcome message says "Kaypro" instead of "Kaypro II"
; - Support for double sided doble density disks (DSDD)
; - The code goes 144 bytes beyong the 2KB limit and needs a 4KB
; ROM. All the remaining spce is filled with FF.
; - The PIO-2A bit 6 is configured as output.
;
; To support the DSDD disks, a new disk parameter block is added.
; This block is not copied to upper RAM as the SSSD and SSDD blocks
; were. Instead, the upper RAM copy of SSDD is replaced by the disk
; parameter block for DSDD when needed. Also, the previously unused
; system bit 2 is used to select single side or double side mode.
;
; On the ROM 81.149c, the current track of both drives is stored
; on two variables. Also, the density detected for the current disk
; on each drive is stored as an aditional 16th byte of the disk
; parameter header. On this ROM, there is a need to store also if
; the disk is double sided. Current track, density and sides are now
; stored per drive in the variables disk_active_info_drive_a and
; disk_active_info_drive_b. It is copied back an forth to
; disk_active_info as A: or B: is selected.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; CONSTANTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; I/O Ports
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
io_00_serial_baud_rate: EQU 0x00
io_04_serial_data: EQU 0x04
io_05_keyboard_data: EQU 0x05
io_06_serial_control: EQU 0x06
io_07_keyboard_control: EQU 0x07
io_08_parallel_data: EQU 0x08
io_09_parallel_control: EQU 0x09
io_0b_parallel_b_control: EQU 0x0b
io_0c_keyboad_baud_rate: EQU 0x0c
io_10_fdc_status: EQU 0x10 ; as IN it is a get status
io_10_fdc_command: EQU 0x10 ; as OUT it is a command
io_11_fdc_track: EQU 0x11
io_12_fdc_sector: EQU 0x12
io_13_fdc_data: EQU 0x13
io_14_scroll_register: EQU 0x14
io_1c_system_bits: EQU 0x1c
io_1d_system_bits_control: EQU 0x1d
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; System bits
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
system_bit_drive_a: EQU 0
system_bit_drive_b: EQU 1
system_bit_side_2: EQU 2
system_bit_centronicsReady: EQU 3
system_bit_centronicsStrobe: EQU 4
system_bit_double_density_neg: EQU 5
system_bit_motors_neg: EQU 6
system_bit_bank: EQU 7
system_bit_drive_a_mask: EQU 0x01
system_bit_drive_b_mask: EQU 0x02
system_bit_side_2_mask: EQU 0x04
system_bit_centronicsReady_mask: EQU 0x08
system_bit_centronicsStrobe_mask: EQU 0x10
system_bit_double_density_neg_mask: EQU 0x20
system_bit_motors_meg_mask: EQU 0x40
system_bit_bank_mask: EQU 0x80
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Console constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
address_vram: EQU 0x3000
console_lines: EQU 24
console_columns: EQU 80
console_line_length: EQU 0x80 ; There are 80 cols, but 128 bytes reserved for each line
console_line_mask: EQU 0x7f
address_vram_end: EQU address_vram + console_lines * console_line_length -1 ; 0x3bff
address_vram_start_of_last_line: EQU address_vram_end - console_line_length + 1 ; 0x3b80
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Disk constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
logical_sector_size: EQU 128
double_density_block_size: EQU 1024
physical_sectors_per_side: EQU 10
disk_count: EQU 2 ; 0 is A: and 1 is B:
fdc_command_restore: EQU 0x00
fdc_command_read_address: EQU 0xc4
fdc_command_seek: EQU 0x10
fdc_command_read_sector: EQU 0x88
fdc_command_write_sector: EQU 0xac
fdc_command_force_interrupt: EQU 0xd0
rw_mode_single_density: EQU 1 ; We read or write 128 bytes directly to/from DMA
rw_mode_double_density: EQU 4 ; We read or write the full 512 bytes buffer
fdc_status_record_busy_bit: EQU 0
fdc_status_record_not_found_bit: EQU 4
fdc_status_read_error_bitmask: EQU 0x9c ; Not ready, record not found, crc error or lost data
fdc_status_write_error_bitmask: EQU 0xfc ; Not ready, write_protect, write fault, record not found, crc error or lost data
; RET, used to set the NMI_ISR when the ROM is disabled
RET_opcode: EQU 0xC9
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Info to load CP/M on boot.
; The first boot sector has the info about how many addtional
; sectors to read and where to store them.
; Note that boot only works with double density disks. On DD disks,
; the OS is on the first track and some more sectors after the
; directory area.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
first_sector_load_address: EQU 0xfa00
address_to_load_second_sector: EQU 0xfa02
address_to_exec_boot: EQU 0xfa04
count_of_boot_sectors_needed: EQU 0xfa06
double_density_sectors_per_track: EQU 40
double_density_sectors_for_directory: EQU 16
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Disk related variables
; Sector address is given by the DTS (Drive, Track and Sector)
; For double density the sector is divided by 4 to account for 512
; bytes sector.
; See "Uninitialized RAM data areas" in Appendix G
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; DTS with the user requested data. Uses losgical sectors of 128 bytes
drive_selected: EQU 0xfc00
track_selected: EQU 0xfc01 ; 2 bytes
sector_selected: EQU 0xfc03
; DTS as understood by the floppy disk controller. Sectors are 512 bytes
drive_in_fdc: EQU 0xfc04
track_in_fdc: EQU 0xfc05 ; 2 bytes
sector_in_fdc: EQU 0xfc07
dd_sector_selected: EQU 0xfc08 ; the double density sector is sector_selected / 4
fdc_set_flag: EQU 0xfc09 ; 'hstact'
pending_write_flag: EQU 0xfc0a ; 'hstwrt'
pending_count: EQU 0xfc0b
drive_unallocated: EQU 0xfc0c
track_unallocated: EQU 0xfc0d ; 2 bytes
sector_unallocated: EQU 0xfc0f
rw_result: EQU 0xfc10
read_needed_flag: EQU 0xfc11
read_not_needed: EQU 0
read_needed: EQU 1
; See CP/M 2.2 System alteration guide appendix G
operation_type: EQU 0xfc12 ; 'readop' in appendix G
operation_type_write: EQU 0
operation_type_read: EQU 1
; See CP/M 2.2 System alteration guide, section 12 and appendix G
rw_type: EQU 0xfc13 ; 'wrtype' in appendix G
rw_type_normal_write: EQU 0 ; write to allocated
rw_type_directory_write: EQU 1
rw_type_read_or_unallocated_write: EQU 2 ; write to unallocated
disk_DMA_address: EQU 0xfc14 ; 2 bytes
; There are 4 sector buffers. To select the buffer we get the sector modulo 4
sector_buffer_base: EQU 0xfc16
sector_buffer_0: EQU 0xfc16
sector_buffer_1: EQU 0xfc16 + logical_sector_size
sector_buffer_2: EQU 0xfc16 + logical_sector_size * 2
sector_buffer_3: EQU 0xfc16 + logical_sector_size * 3
disk_active_drive: EQU 0xfe16
; There are three bytes with the disk info: track, density, sides support
disk_active_info: EQU 0xfe17
disk_active_info_undefined: EQU 0xff
disk_active_track: EQU disk_active_info + 0
disk_density: EQU disk_active_info + 1
disk_density_double: EQU 0x00 ; FM encoding
disk_density_single: EQU 0x20 ; MFM encoding
disk_active_has_sides: EQU disk_active_info + 2
disk_active_has_sides_no: EQU 0x00
disk_active_has_sides_yes: EQU 0xff
; Copy of the disk info for the drive A: (3 bytes)
disk_active_info_drive_a: EQU 0xfe1a ; 3 bytes copy of 0xfe17, 8 and 9
; Copy of the disk info for the drive B: (3 bytes)
disk_active_info_drive_b: EQU 0xfe1d ; 3 bytes copy of 0xfe17, 8 and 9
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Console related variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
console_esc_mode: EQU 0xfe74
console_esc_mode_clear: EQU 0 ; No ESC pending
console_esc_mode_enabled: EQU 1 ; Next char is the ESC command
console_esc_mode_arg_1: EQU 2 ; Next char is the first arg of the = command
console_esc_mode_arg_2: EQU 3 ; Next char is the second arg of the = command
console_esc_equal_first_arg: EQU 0xfe75 ; First arg of the esc= command
console_cursor_position: EQU 0xfe76 ; 2 bytes
; On greek mode, the char is converted to a control char that is printed as a greek letter
console_alphabet_mask: EQU 0xfe78
console_alphabet_ascii_mask: EQU 0x7f
console_alphabet_greek_mask: EQU 0x1f
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Entry points of code relocated to upper RAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
disk_params_destination: EQU 0xfe79
disk_parameter_header_0: EQU 0xfe79
disk_parameter_header_1: EQU 0xfe8a
disk_parameter_block_single_density: EQU 0xfe9b
disk_parameter_block_double_density: EQU 0xfeaa
disk_sector_translation_table: EQU 0xfeb9
disk_parameter_block_size: EQU 15
disk_read_address_buffer: EQU 0xfecb
disk_read_address_sector: EQU 0xfecd
disk_read_address_buffer_size: EQU 6
relocation_destination: EQU 0xfed1
relocation_offset: EQU 0xfed1 - 0x03b ; relocation_destination - block_to_relocate
read_single_density_relocated: EQU 0xfee0 ; reloc_single_density + relocation_offset
move_RAM_relocated: EQU 0xfed1 ; reloc_move_RAM + relocation_offset
read_to_buffer_relocated: EQU 0xfee7 ; reloc_read_to_buffer + relocation_offset
write_from_buffer_relocated: EQU 0xfef8 ; reloc_write_from_buffer + relocation_offset
write_single_density_relocated: EQU 0xfef1 ; reloc_write_single_density + relocation_offset
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Other addresses
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CSV_0: EQU 0xfe20 ; Scrathpad for change disk check, drive 0
ALV_0: EQU 0xfe30 ; Scrathpad for BDOS disk allocation, drive 0
CSV_1: EQU 0xfe4a ; Scrathpad for change disk check, drive 1
ALV_1: EQU 0xfe5a ; Scrathpad for BDOS disk allocation, drive 1
DIRBUF: EQU 0xff6d ; Address of a 128 byte scratchpad for BDOS dir ops
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS ENTRY POINTS
;
; Description of the entry points adapted from the KayPLUS manual.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0h
; COLD: Resets entire computer system and is ALMOST like
; pressing the RESET button.
; Corresponds to CP/M BIOS function BOOT
JP EP_COLD
; INITDSK: Resets the disk input/output buffer status to empty.
; Any pending write is lost. Useful to perform a "soft" disk reset.
JP EP_INITDSK
; INITVID: Resets the video system. Video hardware is configured
; and screen is cleared
JP EP_INITVID
; INITDEV: Initializes tall I/O ports.
JP EP_INITDEV
; HOME: Sets track number to 0
; Corresponds to CP/M BIOS function HOME
JP EP_HOME
; SELDSK: Selects logical drive in register C (value of 0 through 1),
; corresponding to drives A or B). SELDSK determines what type of
; disk (density) is present in the drive.
; Corresponds to CP/M BIOS function SELDSK
JP EP_SELDSK
; SETTRK: Sets the track number to the value in register BC. No seek
; is actually performed until a disk read/write occurs.
; Corresponds to CP/M BIOS function SETTRK
JP EP_SETTRK
; SETSEC: Sets the logical sector number to the value in register C.
; Corresponds to CP/M BIOS function SETSEC
JP EP_SETSEC
; SETDMA: Specifies the DMA address where disk read/write occurs in
; memory. The address in register pair BC is used until another DMA
; address is specified.
; Corresponds to CP/M BIOS function SETDMA
JP EP_SETDMA
; READ: Reads the previously-specified logical sector from specified
; track and disk into memory at the DMA address. Note that on
; double-density disks and the hard drive, one physical sector may be
; composed of up to eight logical sectors, so a physical disk read
; may not actually occur. Returns disk status in A with zero
; indicating no error occurred and a non-zero value indicating an
; error.
; Corresponds to CP/M BIOS function READ
JP EP_READ
; WRITE: Same as above, but writes from memory to disk.
; Corresponds to CP/M BIOS function WRITE
JP EP_WRITE
; SECTRAN: Translates logical sector number to physical sector number
; Corresponds to CP/M BIOS function SECTRAN
JP EP_SECTRAN
; DISKON: Turns on the disk drive.
JP EP_DISKON
; DISKOFF: Turns off the disk drive.
JP EP_DISKOFF
; KBDSTAT: Simply returns status of keyboard queue. Returns 0FFH if
; a key is available, or 00H otherwise.
; Corresponds to CP/M BIOS function CONST
JP EP_KBDSTAT
; KBDIN: Gets character from keyboard buffer or waits for one, if
; none ready.
; Corresponds to CP/M BIOS function CONIN
JP EP_KBDIN
; KBDOUT: Sends the character in register A to the keyboard port.
JP EP_KBDOUT
; SIOSTI: Returns status of SIO-B input port. Returns 00H if no
; character is ready, or 0FFH otherwise.
JP EP_SIOSTI
; SIOIN: Gets character from SIO-B input port, or waits for one if
; none is ready.
JP EP_SIOIN
; SIOOUT: Sends character to SIO-B output port.
JP EP_SIOOUT
; LISTST: Returns the list status of the Centronics port: 00H is
; returned if the printer is busy, 0FFH if ready.
JP EP_LISTST
; LIST: Sends the character in register C to the Centronics port.
JP EP_LIST
; SERSTO: Returns status of SIO-B output port. Returns 0FFH if SIO-B
; is ready to accept a character for output, and 00H otherwise.
JP EP_SERSTO
; VIDOUT: Sends character in register C to video screen. All characters
; 20H (blank) to 7FH are directly displayed and screen scroll is done,
; if required. Characters below 20H are defined as control characters.
JP EP_VIDOUT
; DELAY: This entry point performs a "B times 10 mSec" delay. The
; 10 mSec delay is preset for 4 MHz. "B" is the value in the B-register
; and ranges from 1 to 256 decimal (0 is treated as 256).
JP EP_DELAY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INITIALIZATION AND BOOT FROM DISK
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EP_COLD:
DI
LD SP, 0xffff
LD B, 0xa ; 100ms delay
CALL EP_DELAY
; Init the system, io ports, screen and memory
CALL EP_INITDEV
CALL EP_INITVID
CALL EP_INITDSK
; Avoid the NMI entry point at 0x0066
JR EP_COLD_continue
DB 0x3D, 0xC3, 0x3B, 0x2B, 0xAF, 0x32, 0x13
nmi_isr:
; Just return from the interrupts generated
; by the floppy controller
RET
EP_COLD_continue:
; Show the wellcome message
CALL console_write_string ; console_write_string uses the zero terminated
; string after the CALL
DB 1Bh,"=", 0x20 + 0xa, 0x20 + 0x1f ; ESC code, move to line 10, column 31
DB "* KAYPRO *"
DB 1Bh,"=", 0x20 + 0xd, 0x20 + 0x14 ; ESC code, move to line 13, column 20
DB " Please place your diskette into Drive A"
DB 0x8 ; Cursor
DB 0 ; End NUL terminated string
; Read the first sector of the boot disk
LD C,0x0
CALL EP_SELDSK
LD BC,0x0
CALL EP_SETTRK
LD C,0x0
CALL EP_SETSEC
LD BC, first_sector_load_address
CALL EP_SETDMA
CALL EP_READ
DI
; Verify the result
OR A
JR NZ, error_bad_disk
; Set the DMA destination as instructed by the info
; on the first boot sector
LD BC, (address_to_load_second_sector)
LD (disk_DMA_address), BC
; Store the boot exec addres on the stack. A RET will
; use this address and start executionthere
LD BC, (address_to_exec_boot)
PUSH BC
; Prepare the loading of the rest of the sectors
LD BC, (count_of_boot_sectors_needed)
LD B,C
; Continue reading from sector 1
LD C,0x1
read_another_boot_sector:
; B has the count of sectors remaining
; C has the current sector number
PUSH BC
; Load sector C
CALL EP_SETSEC
CALL EP_READ
DI
; Verify the result
POP BC
OR A
JR NZ, error_bad_disk
; Increase by 128 the load address (logical sector size is 128 bytes)
LD HL, (disk_DMA_address)
LD DE, logical_sector_size
ADD HL,DE
LD (disk_DMA_address), HL
; Decrease the count of sectors remaining
DEC B
; If done , jump to the boot exec address previously pushed to the stack
RET Z
; Not finished, calculate the next sector and track
INC C
LD A, double_density_sectors_per_track
; Are we on the last sector of the track?
CP C
; No, continue reading sector + 1
JR NZ,read_another_boot_sector
; Yes, track 0 completed. Continue with track 1
; Skip the 16 sectors used for the directory
LD C, double_density_sectors_for_directory
PUSH BC
; Move to track 1
LD BC,0x0001
CALL EP_SETTRK
POP BC
; Loop
JR read_another_boot_sector
error_bad_disk:
; Error, write the error message and stop
CALL console_write_string
DB "\n\r\n\r\aI cannot read your diskette.",0
CALL EP_DISKOFF
wait_forever:
; Lock the CPU forever
JR wait_forever
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INIT DISK. COPY CODE AND DISK PARAMS TO UPPER RAM. RESET VARIABLES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; See CP/M 2.2 System alteration guide, section 10
; This data will be copied starting 0xfe79
disk_params:
init_disk_parameter_header_0: ; to 0xfe79
DW 0x0000 ; XLT, logical translation table
DW 0x0000, 0x0000, 0x0000 ; Scrathpad for BDOS
DW DIRBUF ; Address of additional scratchpad for BDOS,
DW disk_parameter_block_double_density ; DPB
DW CSV_0
DW ALV_0
DB 0x00 ; Used by the BIOS to store the disk density
init_disk_parameter_header_1: ; to 0xfe8a
DW 0x0000 ; XLT, logical translation table
DW 0x0000, 0x0000, 0x0000 ; Scrathpad for BDOS
DW DIRBUF ; Address of additional scratchpad for BDOS,
DW disk_parameter_block_double_density ; DPB
DW CSV_1
DW ALV_1
DB 0x00 ; Used by the BIOS to store the disk density
; Single density disk
; 18 sectors (of 128 bytes) per track
; 1024 bytes per allocation block
; 83 kb total disk space
; 40 tracks, 3 reserved
init_disk_parameter_block_single_density: ; to 0xfe9b
DW 18 ; SPT, sectors per track
DB 3 ; BSH, data alloc shift factor
DB 7 ; BLM
; As BSH=3 and BLM=7, then BLS (data alocation size) is 1024.
DB 0 ; EXM, extent mask
DW 82 ; DSM, total storage in allocation blocks - 1
DW 31 ; DRM, number of directory entries - 1
DB 0x80 ; AL0
DB 0x00 ; AL1
DW 8 ; CKS, directory check vector size
DW 3 ; OFF, number of reserved tracks
; Single density disk
; 40 sectors (128 bytes) per track
; 1024 bytes per allocation block
; 195 kb total disk space
; 40 tracks, 1 reserved
init_disk_parameter_block_double_density: ; to 0xfeaa
DW 40 ; SPT, sectors per track
DB 3 ; BSH, data alloc shift factor
DB 7 ; BLM
; As BSH=3 and BLM=7, then BLS (data alocation size) is 1024.
DB 0 ; EXM, extent mask
DW 194 ; DSM, total storage in allocation blocks - 1
DW 63 ; DRM, number of directory entries - 1
DB 0xF0 ; AL0
DB 0x00 ; AL1
DW 16 ; CKS, directory check vector size
DW 1 ; OFF, number of reserved tracks
init_sector_translation_table: ; 0xfeb9
; Only used for single density
; There is translation for 18 sectors.
DB 1, 6, 11, 16, 3, 8, 13, 18
DB 5, 10, 15, 2, 7, 12, 17, 4
DB 9, 14
disk_params_end:
init_disk_parameter_block_double_density_double_side:
DW 40 ; SPT, sectors per track
DB 4 ; BSH, data alloc shift factor
DB 15 ; BLM
; As BSH=4 and BLM=15, then BLS (data alocation size) is 2048.
DB 1 ; EXM, extent mask
DW 196 ; DSM, total storage in allocation blocks - 1
DW 63 ; DRM, number of directory entries - 1
DB 0xC0 ; AL0
DB 0x00 ; AL1
DW 16 ; CKS, directory check vector size
DW 1 ; OFF, number of reserved tracks
EP_INITDSK:
; Copy relocatable disk access to upper RAM to
; be accessible even when the ROM is swapped out
LD HL, block_to_relocate
LD DE, relocation_destination
LD BC, block_to_relocate_end - block_to_relocate ;0x87
LDIR
; Copy the disk parameters to upper RAM
LD HL, disk_params
LD DE, disk_params_destination
LD BC, disk_params_end - disk_params ;0x52
LDIR
; Init some variables
XOR A
LD (fdc_set_flag), A ; = No
LD (pending_count), A ; = 0
LD A, disk_density_double
LD (disk_density), A
LD A, disk_active_info_undefined
LD (disk_active_drive), A
LD (disk_active_info_drive_a), A
LD (disk_active_info_drive_b), A
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FLOPPY DISK ENTRY POINTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EP_SELDSK:
; C: disk number
LD A,C
LD (drive_selected), A
JP init_drive
EP_SETSEC:
; BC: sector number
LD A,C
LD (sector_selected), A
; Is the disk double density?
LD A, (disk_density)
OR A
; No, send the sector to the controller
JP NZ, fdc_set_sector
; Yes, we just store the sector number
RET
EP_SETDMA:
; BC: DMA address
LD (disk_DMA_address), BC
RET
EP_SETTRK:
; C: track number
LD (track_selected), BC
; Is the disk double density?
LD A, (disk_density)
OR A
; No, send the track to the controller
JP NZ, fdc_seek_track
; Yes, we just store the track number
RET
EP_HOME:
;Is the disk double density?
LD A, (disk_density)
OR A
; No, go to track 0 and return
JP NZ, fdc_seek_track_0
; Yes.
; Is a write pending?
LD A,(pending_write_flag)
OR A
; Yes, skip update
JP NZ, skip_buffer_discard
; No, discard the buffer
LD (fdc_set_flag),A ; = No
skip_buffer_discard:
JP fdc_seek_track_0
EP_READ:
; Is disk double density?
LD A,(disk_density)
OR A
; No, go directly to the read routine
JP NZ, read_single_density_relocated
; Yes, some preparation is needed as the calls to EP_SETSEC and
; EP_SETTRK did not send the info to the fdc for double density.
; Init variables
XOR A
LD (pending_count),A ; = 0
; Starting from here it is equal to read in Appendix G
LD A, operation_type_read
LD (operation_type), A; = operation_type_read
LD (read_needed_flag), A ; = read_needed
LD A, rw_type_read_or_unallocated_write
LD (rw_type),A
JP read_write_double_density
EP_WRITE:
; C indicates the rw_type
; Is disk double density?
LD A,(disk_density)
OR A
; No, go directly to the write routine
JP NZ, write_single_density_relocated
; Yes, some preparation is needed as the calls to EP_SETSEC and
; set_track did not send the info to the fdc on double density.
; Starting from here it is equal to read in Appendix G
XOR A
LD (operation_type), A ; = operation_type_write
LD A,C
LD (rw_type),A ; = C
CP rw_type_read_or_unallocated_write
; It's an allocated write, we can skip reset the
; unallocated params to check if a read is needed.
JP NZ, write_check_read_needed
LD A, double_density_block_size / logical_sector_size ; 8
LD (pending_count),A ; = 8
; Initialize the unallocated params
LD A, (drive_selected)
LD (drive_unallocated), A
LD HL, (track_selected)
LD (track_unallocated), HL
LD A, (sector_selected)
LD (sector_unallocated), A
write_check_read_needed:
; Do we have pending logical sectors?
LD A,(pending_count)
OR A
; No, skip
JP Z, write_with_read_needed
; Yes, there are more unallocated records remaining
DEC A
LD (pending_count),A ; pending_count-1
; Is drive requested different to the unallocated?
LD A, (drive_selected)
LD HL, drive_unallocated
CP (HL)
; Yes, the drive is different
JP NZ, write_with_read_needed
; The drives are the same
; Is track requested different to the unallocated?
LD HL, track_unallocated
CALL is_track_equal_to_track_selected
; Yes, the track is different
JP NZ, write_with_read_needed
; The tracks are the same
; Is sector requested different to the unallocated?
LD A, (sector_selected)
LD HL, sector_unallocated
CP (HL)
; Yes, the sector is different
JP NZ, write_with_read_needed
; The sectors are the same
; DTS on the unallocated variables match the requested DTS
; Advance to the next sector to check if the next write will
; be of the next sector.
INC (HL)
; Are we at the end of the track?
LD A,(HL)
CP double_density_sectors_per_track
; No
JP C, write_with_read_not_needed
; Yes, increase track and set sector to zero
LD (HL),0x0
LD HL, (track_unallocated)
INC HL
LD (track_unallocated),HL
write_with_read_not_needed:
XOR A
LD (read_needed_flag),A ; = read_not_needed
JP read_write_double_density
write_with_read_needed:
XOR A
LD (pending_count),A ; = 0
INC A
LD (read_needed_flag),A ; = read_needed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FLOPPY DISK INTERNAL IMPLEMENTATION READ AND WRITE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
read_write_double_density:
; Reset the result variable
XOR A
LD (rw_result),A ; = 0
; Translate the sector logical address to the double density
; address. As sector in DD are four times the size of sector
; in SD, we divide by 4 (or shift right twice).
; Sure? Ono some places its 8 times ???
LD A, (sector_selected)
OR A ; Clear carry
RRA ; /2
OR A ; Clear carry
RRA ; /2
LD (dd_sector_selected),A ; sector_selected / 4
; Is fcd_position set
LD HL, fdc_set_flag
LD A,(HL)
LD (HL),0x1 ; fdc_set_flag = Yes
OR A
; No, continue after updating the DTS_in_fdc variables
JP Z, rw_fdc_not_set
; Yes
; Are the fdc variables different from the selected?
LD A,(drive_selected)
LD HL, drive_in_fdc
CP (HL)
; Yes, the drive is different
JP NZ, rw_fdc_mismatch
; Is track requested different to the xx?
LD HL, track_in_fdc
CALL is_track_equal_to_track_selected
; Yes, the track is different
JP NZ, rw_fdc_mismatch
; Is sector requested equals to the xx?
LD A, (dd_sector_selected)
LD HL, sector_in_fdc
CP (HL)
; Yes, the sector is equal
JP Z, rw_fdc_set
rw_fdc_mismatch:
; Is there a pending write on the buffer
LD A, (pending_write_flag)
OR A
; Yes, write the buffer before continuing
CALL NZ, write_from_buffer_with_retries
; Now we can init the _in_fdc variables
rw_fdc_not_set:
; DTS_in_fdc = DTS_selected
LD A, (drive_selected)
LD (drive_in_fdc),A
LD HL, (track_selected)
LD (track_in_fdc),HL
LD A,(dd_sector_selected)
LD (sector_in_fdc),A
; Is a read needed
LD A,(read_needed_flag)
OR A
; Yes, read to fill the buffer
CALL NZ, read_to_buffer_with_retries
XOR A
LD (pending_write_flag),A ; = no pending write
rw_fdc_set:
; Calculate the sector buffer to use for this sector
LD A, (sector_selected)
AND 0x3 ; mod 4
LD L,A
LD H,0x0
ADD HL,HL ; *2
ADD HL,HL ; *2
ADD HL,HL ; *2
ADD HL,HL ; *2
ADD HL,HL ; *2
ADD HL,HL ; *2
ADD HL,HL ; *2. Combined *128
LD DE, sector_buffer_base
ADD HL,DE
; HL = sector_buffer_base + (sector mod 4) * logical_sector_size
LD DE,(disk_DMA_address)
LD BC,logical_sector_size
;
LD A,(operation_type)
OR A
; Yes, it's a read, skip write related coe
JR NZ, copy_block_to_or_from_buffer
LD A,0x1
LD (pending_write_flag),A ; = 1
; Reverse the block copy direction
EX DE,HL
copy_block_to_or_from_buffer:
; Copy a sector from the buffer to the DMA
CALL move_RAM_relocated
LD A, (rw_type)
CP rw_type_directory_write
LD A, (rw_result)
; Return if it is a read or a normal write
RET NZ
OR A
; Return if last read/write had an error
RET NZ
; It is a directory write. Le's not wait and
; save to disk now. (to be more reliable?)
XOR A
LD (pending_write_flag),A ; = 0
CALL write_from_buffer_with_retries
LD A,(rw_result)
RET
is_track_equal_to_track_selected:
; HL = address to a variable with the track
; Returns flag Z set if they are equal.
; This is not inline as the drive and sector comparison because
; the track is two bytes.
EX DE,HL
LD HL, track_selected
LD A,(DE) ; = track
CP (HL)
RET NZ
INC DE
INC HL
LD A,(DE)
CP (HL)
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FLOPPY DISK INTERNAL INIT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
init_drive:
; Change current drive, update the disk info and check density
; C: drive number
LD HL,0x0
LD A,C
CP disk_count
; Ignore if the drive number is out of range
RET NC
; Point HL to the disk info for disk A or B
OR A
LD HL, disk_parameter_header_0
JR Z, disk_params_skip_for_drive_a
LD HL, disk_parameter_header_1
disk_params_skip_for_drive_a:
; The current drive is the requested one?
LD A,(disk_active_drive)
CP C
; Yes, nothing to do
RET Z
; No, change the drive
; Store the new drive number
LD A,C
LD (disk_active_drive),A
OR A
; If the disk info for the drive is defined,
; restore that as the active info
PUSH HL
LD HL, disk_active_info
LD DE, disk_active_info_drive_b
JR Z, save_previous_disk_info
LD DE, disk_active_info_drive_a
save_previous_disk_info:
LD A, (DE)
; Is the active info undefined?
CP disk_active_info_undefined
; Yes, no need to save the info
JR Z, skip_save_disk_info
PUSH BC
; Save the current disk info for the drive
LD BC, 0x3
LDIR
POP BC
LD DE, disk_active_info
skip_save_disk_info:
; C is the disk number
LD A,C
OR A
; Load active info for disk a or b
LD HL, disk_active_info_drive_a
JR Z, load_previous_info
LD HL, disk_active_info_drive_b
load_previous_info:
LD A,(HL)
; Is the stored info undefined?
CP disk_active_info_undefined
; Yes, the info is not cached, we need to analyze the disk
JR Z, analyze_inserted_disk
; Restore the disk info
LD BC, 0x3
LDIR
; Copy the required disk parameter block DDSD or DDDD
LD BC, disk_parameter_block_size
LD DE, disk_parameter_block_double_density
LD HL, init_disk_parameter_block_double_density
LD A, (disk_active_has_sides)
OR A
JR Z, skip_for_single_side
LD HL, init_disk_parameter_block_double_density_double_side
skip_for_single_side:
LDIR ; Copy the parameter block
; Restore the track position
LD A, (disk_active_track)
OUT (io_11_fdc_track), A
POP HL
RET
analyze_inserted_disk:
POP HL
; Try reading as double density disk
LD A, disk_density_double
LD (disk_density), A
CALL prepare_drive
CALL EP_HOME
CALL fdc_read_address
; If it reads the track, it is a double density disk
JR Z, set_double_density_disk
; No, try reading as single density disk
LD A, disk_density_single
LD (disk_density), A
CALL prepare_drive
CALL fdc_read_address
RET NZ ; Return if it fails as single and double density disk
JR set_single_density_disk
set_double_density_disk:
; HL is disk_parameter_header
PUSH HL
PUSH DE
; Set no sector tran on the disk params $0 and $1
LD DE,0x0000
LD (HL),E
INC HL
LD (HL),D
; Set DPB for double density on the disk params $a and $b
LD DE,0x0009
ADD HL,DE
LD DE, disk_parameter_block_double_density
LD (HL),E
INC HL
LD (HL),D
; Select disk side 2, will be change to side 1 later.
IN A,(io_1c_system_bits)
OR system_bit_side_2_mask
OUT (io_1c_system_bits),A
; Try reading on the side 2
CALL fdc_read_address
LD BC, disk_parameter_block_size
LD DE, disk_parameter_block_double_density
LD HL, init_disk_parameter_block_double_density
LD A, disk_active_has_sides_no
; If it fails, side 2 is not readable we are for sure on a single side disk
JR NZ, second_side_analysis_completed
; The previous fdc_read_address has not failed: there is valid
; info on the side 2.
; We will see if the sector number to check if it is a single side disk
; reversed or if it is the side 2 of a double sided disk.