This repository was archived by the owner on Dec 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy pathdisk.js
1274 lines (1217 loc) · 55 KB
/
disk.js
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
/**
* @fileoverview This file implements the C1Pjs DiskController component.
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @copyright © 2012-2020 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (typeof module !== "undefined") {
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
* @class Drive
* @property {number} iType
* @property {number} nTracks
* @property {boolean} fProtected
* @property {number} nIndexPulse
* @property {number} iTrackSelect
* @property {number} iTrackOffset
* @property {Array} aTracks
*/
/**
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
* which would force us to declare all class properties in the constructor, as well as prevent
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
*
* @unrestricted
*/
class C1PDiskController extends Component {
/**
* C1PDiskController(parmsDC)
*
* The C1PDiskController component has no component-specific parameters.
*
* This component is being built to supplement a C1P (aka SuperBoard II) Model 600
* single-board computer with the addition of a 610 Accessory Board, which included:
*
* MC6820 PIA (Peripheral Interface Adapter at $C000-$C003, decoded at $C000-$C00F)
* MC6850 ACIA (Asynchronous Communications Interface Adapter at $C010-$C011, decoded at $C010-$C01F)
*
* From "OSI C1P Technical Report" p.4 regarding the 610 Accessory Board:
*
* "This board holds up to 24K of additional RAM memory, a dual mini-floppy disk controller,
* a BUS expansion facility to Model 620 BUS adapter, and switching circuitry to route the
* 600 board's serial interface to both the modem and printer as well as an audio cassette.
* Thus, a fully expanded Challenger lP system can have BASIC-in-ROM, 32K of RAM memory,
* dual mini-floppies, cassette, printer, modem, and full BUS expansion capability to the OSI
* 48 line BUS through which over 40 accessories can be added (A/D, D/A, voice, I/O, more memory,
* etc.)."
*
* On p.20, the Report says that the 610 Accessory Board contains:
*
* - Up to 24K of RAM
* - Dual mini-floppy controller
* - Real Time Clock (although elsewhere the Report says this is disabled by default)
* - Expansion interface to a model 620 BUS adapter
*
* On p.21, the Report also says:
*
* "The dual mini-floppy interface is designed after Ohio Scientific's extremely popular
* and successful 470 floppy disk controller. This floppy disk controller and encoding
* technique has been field proven for several years in thousands of floppy disks and is
* believed to be one of the most reliable floppy disk configurations in existence. Although
* the Challenger lP product line is new, it has the advantage of the experience of a company
* which has been building high performance microcomputers for several years."
*
* From "PEEK 65" Vol.2 No.3 March 1981, p.9:
*
* "The 470 board wired as a floppy disk controller contains two different interfaces:
* a PIA and an ACIA. The PIA A and B ports are used in control circuits: raise and lower
* the head, detect drive ready, detect sector hole, clear error faults, etc. The ACIA is
* the interface over which the data actually travels. Typical operation is to drop the head,
* reset the ACIA, wait for the index hole to come around, activate the read or write circuit,
* then read or write characters through the ACIA."
*
* 470 Board Addressing
* --------------------
*
* Address Read Write
* ------- ---- -----
* C000 PIA: PA0 thru PA7 PIA: PA0 thru PA7 or DDA0 thru DDA7
* C001 PIA: Port A Ctrl PIA: Port A Control
* C002 PIA: PB0 thru PB7 PIA: PB0 thru PB7 or DDB0 thru DDB7
* C003 PIA: Port B Ctrl PIA: Port B Control
* C010 ACIA: Status Reg. ACIA: Control Register
* C011 ACIA: Data Path ACIA: Data Path
* C020 Clear Real Time Clock Clear Real Time Clock
* (Reset) ($FF returned) (Reset) (Data Ignored)
*
* PIA Data Register A Layout:
*
* PA7 PA6 PA5 PA4 PA3 PA2 PA1 PA0
* --- --- --- ---- --- --- --- ----
* IHD | SD2 | WP | RDY2 | SHD | FD | TZD | RDY1
* (In) (Out) (In) (In) (In) (In) (In) (In)
*
* PIA Data Register B Layout:
*
* PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
* --- --- --- --- --- --- --- ---
* HLD | LCS | SD1 | FR | ST | STI | EE | WE
* (Out) (Out) (Out) (Out) (Out) (Out) (Out) (Out)
*
* PIA Data Register A Lines PIA Data Register B Lines
* ------------------------- -------------------------
* IHD - Index Hole Detect HLD - Head Load
* SD2 - Select Drive 2 (Drive B) LCS - Low Current Select
* WP - Write Protected SD1 - Select Drive 1
* RDY2- Drive 2 Ready FR - Fault Reset
* SHD - Sector Hole Detect ST - Step
* FD - Fault Detected STI - Step In
* TZD - Track Zero Detected EE - Enable Erase
* RDY1- Drive 1 Ready WE - Write Enable
*
* NOTE: The PIA bit assignments above agree with those described, albeit somewhat less clearly,
* in http://www.osiweb.org/osiweb/misc/osi-hardware.txt, under "Model 475 Floppy disk system with
* 470 Controller board".
*
* There is apparently significant overlap with another OSI board: the Model 505 CPU Board
* used in C4P/MF systems. According to http://www.osiweb.org/osiweb/misc/osi-hardware.txt, it
* contained:
*
* CPU board w/ ROM, ACIA, Floppy Disk I/O, Real Time Clock
* ROM $FDxx, $FExx, $FFxx
* Floppy disk interface: 6820 PIA at $C000, 6850 ACIA at $C010 [Original says "6850 PIA"]
* ACIA 6850 at $FC00 for RS-232 serial I/O. Baud jumpers for 75,150,300,600,1200,2400,4800,9600
* Disk PIA $C0xx CB1 connected to 400mSEC (2.5/sec) clock divided from system clock (RTC)
* Home security - PIA $F700-F703
*
* Disk Formats (from http://osi.marks-lab.com/files/winOSI/old-source-V1.2/Disk_io.cpp):
*
* 5.25" disk, 40 tracks, 8 sectors/track, 256 bytes/sector, 11 bits/byte (8E1) = 80K/disk.
*
* NOTE: 8E1 refers to "8 data bits, even parity, 1 stop bit," plus an implied start bit.
*
* OSI uses 8E1 to give a max unformatted capacity of 2272 bytes per track (see below).
* However other bit encodings (8N1) could give up to 2500 bytes/track.
*
* NOTE: 8N1 refers to "8 data bits, no parity, 1 stop bit," plus an implied start bit.
*
* The standard speed for 5.25" drives is 300rpm. Thus one rotation of the disk is 200ms.
* Stated baud-rate is 125k or 125000 bits/sec and one serial byte is 11 bits (1 start,
* 8 data, 1 parity, 1 stop). So the theoretical absolute maximum storage per track is
* (125000 x 0.2) / 11 = 2272 bytes or 8.8 pages.
*
* OS-65D loses a bit more because it doesn't write until 10ms after the index pulse, so
* (125000 x 0.19) / 11 = 2159 bytes or 8.4 pages and this doesn't even allow for the length
* of the index pulse (a few milliseconds?) and the speed variation between drives.
*
* 8" disk, 77 tracks, 12 sectors/track, 256 bytes/sector, 11 bits/byte (8E1) = 231K/disk.
* OSI uses 8E1 to give a max unformatted capacity of 3772 bytes/track (see below).
* However other bit encodings (8N1) could give up to 3900 bytes/track.
*
* The standard speed for 8" drives is 360rpm. Thus one rotation of the disk is 166.6ms.
* Stated baud rate is 250K or 250000 bits/sec and one serial byte is 11 bits (1 start,
* 8 data, 1 parity, 1 stop). So the theoretical absolute maximum storage per track is
* (250000 x 0.166 ) / 11 = 3772 or 14.7 pages.
*
* OS-65D loses a bit more because it doesn't write until 10 mS after the index pulse, so
* (250000 x 0.156) / 11 = 3545 bytes or 13.8 pages and this doesn't even allow for the length
* of the index pulse (a few milliseconds?) and the speed variation between drives.
*
* Track 0 Format
* --------------
* (10ms delay after index hole)
* 0,1 load address of the track in hi,lo form
* 2 page count of how much data is written on track 0.
* 3+ sector data
*
* Track N Format (N > 0)
* ----------------------
* (10ms delay after index hole)
* 0,1 2-byte start code $43, $57
* 2 BCD track number
* 3 track type code (always $58)
* 4+ sector data
*
* Sector Format (5.25" disks)
* ---------------------------
* There can be any mixture of various length sectors. The total page count can not
* exceed 8 pages (8*256) if more than one sector is on a track. Each sector is written
* in the following format:
*
* previous sector length (4 if none before) times 800 microseconds of delay
* sector start code $76
* sector number in binary
* sector length (#pages) in binary
* sector data
* (end of sector mark? $47, $53? MDS)
*
* Directory Format
* ----------------
* 2 sectors (1 & 2) on track 12 hold the directory information.
* Each entry requires 8 bytes. There are a total of 64 entries. The entries are
* formatted as follows:
*
* 0-5 ASCII 6 character filename
* 6 BCD first track of file
* 7 BCD Last track of file
*
* So far, all the 5.25" disk images I've seen are 92160 bytes, regardless whether they have an
* .IMG or .65D extension. If we divide that total by 40 (tracks/disk), we get 2304 (bytes/track).
* Divide 2304 by 256 (bytes/page) and we get 9 pages/track. Presumably a fixed 9 pages was chosen
* to yield a consistent track size across the entire image, while also allowing room for all the
* metadata that's typically present on a track as well. As explained above, the upper limit
* on data per track (both sector data and metadata) is 8.8 pages in theory, or 8.4 pages in practice.
*
* @this {C1PDiskController}
* @param {Object} parmsDC
*/
constructor(parmsDC)
{
super("C1PDiskController", parmsDC);
this.flags.powered = false;
/*
* Our DiskController simulates the combination of an MC6820 PIA and an MC6850 ACIA.
* This image of an OSI 470 Controller Board (http://osi.marks-lab.com/boards/images/OSI470.jpg)
* shows that the chips actually used were MC68B21P and MC68B50P.
*
* We start with definitions for the MC6820 PIA.
*/
this.PORT_PDA = 0; // PIA Peripheral Data Register A
this.PORT_DDA = 0; // PIA Data Direction Register A (DDA shares the same register offset as PDA)
this.PORT_CRA = 1; // PIA Control Register A
this.PORT_PDB = 2; // PIA Peripheral Data Register B
this.PORT_DDB = 2; // PIA Data Direction Register B (DDB shares the same register offset as PDB)
this.PORT_CRB = 3; // PIA Control Register B
this.CR_IRQ1 = 0x80; // IRQ1
this.CR_IRQ2 = 0x40; // IRQ2
// this.CR_C2_OUT = 0x20; // C2 is designated an output
// this.CR_C2_CTRL = 0x18; // C2 Control (00 and 10 mask IRQ2, 01 and 11 pass IRQ2 through to the CPU)
this.CR_PD_SEL = 0x04; // set to select PD (PDA or PDB), clear to select DD (DDA or DDB)
// this.CR_C1_CTRL = 0x03; // C1 Control (00 and 10 mask IRQ1, 01 and 11 pass IRQ1 through to the CPU)
/*
* The PDA bits have the following hard-wired connections in the OSI Floppy Disk Controller.
* Each line marked INPUT should have its corresponding Data Direction bit clear (0), and each line
* marked OUTPUT should have its Data Direction bit set (1); however, we do not currently verify that
* the Data Direction bits are actually initialized to match these specs (and in fact, in the case
* of PDA_SD2, they may not be).
*/
this.PDA_RDY1 = 0x01; // INPUT: 0 = Drive 1 Ready
this.PDA_TZD = 0x02; // INPUT: 0 = Track Zero Detected
this.PDA_FD = 0x04; // INPUT: 0 = Fault Detected
this.PDA_SHD = 0x08; // INPUT: 0 = Sector Hole Detect
this.PDA_RDY2 = 0x10; // INPUT: 0 = Drive 2 Ready
this.PDA_WP = 0x20; // INPUT: 0 = Write Protected
this.PDA_SD2 = 0x40; // OUTPUT: 0 = Select Drive 2 (Drive B)
this.PDA_IHD = 0x80; // INPUT: 0 = Index Hole Detect
// this.PDB_WE = 0x01; // OUTPUT: 0 = Write Enable
// this.PDB_EE = 0x02; // OUTPUT: 0 = Erase Enable (set to 1)
this.PDB_STI = 0x04; // OUTPUT: 0 = Step In (away from track 0)
this.PDB_ST = 0x08; // OUTPUT: 0 = Step (on 1-to-0 transition)
// this.PDB_FR = 0x10; // OUTPUT: 0 = Fault Reset (set to 1)
this.PDB_SD1 = 0x20; // OUTPUT: 0 = Select Drive 1
// this.PDB_LCS = 0x40; // OUTPUT: 0 = Low Current Select (set to 1)
// this.PDB_HLD = 0x80; // OUTPUT: 0 = Head Load (head on disk)
/*
* Next, definitions for the MC6850 ACIA.
*
* For reference, here are all the possible CTRL_WSEL (Word Select) values:
*
* 000 0x00 7 bits, even parity, 2 stop bits
* 001 0x04 7 bits, odd parity, 2 stop bits
* 010 0x08 7 bits, even parity, 1 stop bit
* 011 0x0C 7 bits, odd parity, 1 stop bit
* 100 0x10 8 bits, 2 stop bits
* 101 0x14 8 bits, 1 stop bit
* 110 0x18 8 bits, even parity, 1 stop bit
* 111 0x1C 8 bits, odd parity, 1 stop bit
*
* And here are all the possible CTRL_TCTL (Transmit Control) values:
*
* 00 0x00 RTS=Low, Transmitting Interrupt Disabled
* 01 0x20 RTS=Low, Transmitting Interrupt Enabled
* 10 0x40 RTS=High, Transmitting Interrupt Disabled
* 11 0x60 RTS=Low, Transmits a Break level on the Transmit Data Output; Transmitting Interrupt Disabled
*/
this.PORT_CTRL = 0x10; // ACIA Control Register (WRITE-only)
this.PORT_STAT = 0x10; // ACIA Status Register (READ-only)
this.PORT_DATA = 0x11; // ACIA Data Register (Transmit Data Register on WRITE, Receive Data Register on READ)
this.CTRL_CDIV = 0x03; // Counter Divide (CR1,CR0) [OSI sets both, performing a "Master Reset", then immediately clears both, for a divide ratio of 1]
// this.CTRL_WSEL = 0x1C; // Word Select (CR4,CR3,CR2), determining word length, parity and stop bits [OSI selects 0x18 for "8 bits, even parity, 1 stop bit"]
// this.CTRL_TCTL = 0x60; // Transmit Control (CR6,CR5) [OSI selects 0x40 for "RTS=High, Transmitting Interrupt Disabled"]
// this.CTRL_RINT = 0x80; // Receive Interrupt Enable (CR7) [OSI selects 0x00 for interrupts disabled]
this.STAT_RDRF = 0x01; // Receive Data Register Full
this.STAT_TDRE = 0x02; // Transmit Data Register Empty
this.STAT_DCD = 0x04; // Data Carrier Detect
this.STAT_CTS = 0x08; // Clear To Send
// this.STAT_FE = 0x10; // Framing Error (ie, the received character is improperly framed by a start and a stop bit and is detected by the absence of the first stop bit)
// this.STAT_OVRN = 0x20; // Receiver Overrun (ie, one or more characters in the data stream were lost due to not being read from the Receive Data Register in time)
// this.STAT_PE = 0x40; // Parity Error (ie, the number of highs (ones) in the character does not agree with the preselected odd or even parity)
// this.STAT_IRQ = 0x80; // Interrupt Request (ie, state of the IRQ output; cleared by a read operation to the Receive Data Register or a write operation to the Transmit Data Register)
/*
* Last but not least, some internal state definitions and hard-coded assumptions
*/
this.DRIVETYPE_5INCH = 0;
// this.DRIVETYPE_8INCH = 1;
this.MAXTRACKS_5INCH = 40;
// this.MAXTRACKS_8INCH = 77;
/*
* Some random OS-65D notes
*
* Version 3.3 Initialization Code
* -------------------------------
*
* The following code (where X is 0x00):
*
* 2217 8E 01 F4 STX $F401
* 221A 8E 00 F4 STX $F400
* 221D 8E 03 F4 STX $F403
*
* is intended to reset a Printer PIA located at 0xF400.
*
* It then takes a detour to "SET KEYBOARD SOUND GENERATOR TO LOWEST FREQUENCY (192.753 HZ)"
* with X set to 0xFF; the sound generator is supposed to be turned off a bit later, presumably
* at the same time it sets "64 char/line" mode -- well, that's what v3.2 did anyway.
*
* 2220 CA DEX
* 2221 8E 01 DF STX $DF01
*
* While X is still 0xFF, it continues initializing the Printer PIA:
*
* 2224 8E 02 F4 STX $F402
*
* Then the code fiddles a bit with a mystery serial port (perhaps the "Model 430B Cassette & Analog I/O"
* interface?)
*
* 2227 AD 06 FB LDA $FB06
* 222A 8E 05 FB STX $FB05
*
* And then it's back to more Printer PIA initialization:
*
* 222D A9 04 LDA #$04
* 222F 8D 01 F4 STA $F401
* 2232 8D 03 F4 STA $F403
*
* Then it does some disk resetting (with A still 0x04 and Y set to 0x00):
*
* 2235 8C 01 C0 STY $C001
* 2238 A0 40 LDY #$40 ;'@'
* 223A 8C 00 C0 STY $C000
* 223D 8D 01 C0 STA $C001
*
* This code supposedly selects DRIVE 1:
*
* 2240 A9 01 LDA #$01
* 2242 20 C6 29 JSR $29C6
*
* Then it "resets" and "sets" the TERMINAL ACIA. Note that the C1P serial port is addressed
* at 0xF000-0xF0FF, and the C1P has ROM mapped to 0xF800-0xFFFF, so we know nothing of the serial
* port mentioned above at 0xFBxx, nor this terminal ACIA port at 0xFCxx.
*
* 2245 A9 03 LDA #$03
* 2247 8D 00 FC STA $FC00
* 224A A0 11 LDY #$11
* 224C 8C 00 FC STY $FC00
*
* Next, there's some code to "SET CA-10X 16 WAY SERIAL BOARD" at 0xCF00-0xCF1F; again, something
* we know nothing about:
*
* 224F A2 1E LDX #$1E
* 2251 9D 00 CF STA $CF00,X
* 2254 98 TYA
* 2255 9D 00 CF STA $CF00,X
* 2258 A9 03 LDA #$03
* 225A CA DEX
* 225B CA DEX
* 225C 10 F3 BPL $2251
*
* Then it clears 8 pages of video memory (ie, it simply ASSUMES that this is a Model 540 video board
* with 2K of video memory):
*
* 225E A2 08 LDX #$08
* 2260 A9 D0 LDA #$D0
* 2262 85 FF STA $FF
* 2264 A0 00 LDY #$00
* 2266 84 FE STY $FE
* 2268 A9 20 LDA #$20 ;' '
* 226A 91 FE STA ($FE),Y
* 226C C8 INY
* 226D D0 FB BNE $226A
* 226F E6 FF INC $FF
* 2271 CA DEX
* 2272 D0 F6 BNE $226A
*
* Then it performs a memory test, starting with a high page of 0xBF, and stores the highest page of
* available RAM at 0x2300:
*
* 2276 A0 BF LDY #$BF
* 2278 20 EC 22 JSR $22EC
* 227B F0 03 BEQ $2280
* 227D 88 DEY
* 227E D0 F8 BNE $2278
* 2280 8C 00 23 STY $2300
*
* Now it checks for "SERIAL OR VIDEO (EITHER 65-A OR 65-V PROM)" (the byte at 0xFE01 on a C1P is 0x28,
* so X will be 2, implying "VIDEO"):
*
* 2283 A2 01 LDX #$01
* 2285 AD 01 FE LDA $FE01
* 2288 F0 01 BEQ $228B
* 228A E8 INX
* 228B 8E C6 2A STX $2AC6
*
* Finally, there's some code that's a little different from v3.2; in 3.2, it would set X to 0x01
* and then store X at 0xDE00, effectively forcing the video board into "64 char/line" mode -- which was
* originally EXACTLY what I was looking for in the video emulation component. But v3.3 doesn't do that.
* Here's what it does instead:
*
* 228F A2 00 LDX #$00
* 2291 8E 80 DC STX $DC80
*
* So, what's supposed to be at 0xDC80?
*/
this.reset(true);
}
/**
* @this {C1PDiskController}
* @param {boolean|undefined} [fPowerOn] is true for the initial reset only
*/
reset(fPowerOn)
{
this.resetRegs();
this.iDriveSelect = -1;
if (fPowerOn) {
this.aDrives = [];
this.resetDrive(0, this.DRIVETYPE_5INCH, this.MAXTRACKS_5INCH);
}
}
/**
* @this {C1PDiskController}
*/
resetRegs()
{
this.regDDA = {
bits: this.PDA_SD2, // clear all DDA bits, indicating that all PDA bits represent INPUT lines (well, except for PDA_SD2)
read: function() {},
update: function(controller) {
return function(b) {
if (b !== undefined) this.bits = b;
if (!(controller.regCRA.bits & controller.CR_PD_SEL)) {
controller.writePort(controller.PORT_DDA, this);
}
};
}(this)
};
this.regPDA = {
bits: 0xff,
read: function() {
this.update();
},
update: function(controller) {
return function(b) {
this.bits = controller.updatePDA(b);
if (controller.regCRA.bits & controller.CR_PD_SEL) {
controller.writePort(controller.PORT_PDA, this);
}
};
}(this)
};
this.regCRA = {
bits: 0,
read: function() {},
update: function(controller) {
return function(b) {
/*
* Most bits written to CRA should be left as-is (the CPU should read back what it wrote);
* bits 7 and 6 (IRQ1 and IRQ2) are exceptions, since those are tied to peripheral "Control Lines"
* C1 and C2, which can in theory generate an interrupt depending on how the C1_CTRL and C2_CTRL bits
* in CRA are set. However, assuming there's no need to simulate interrupts for this particular
* controller hardware, all we'll do is simply insure those two bits are always off.
*/
if (b !== undefined) this.bits = (b & ~(controller.CR_IRQ1 | controller.CR_IRQ2));
controller.writePort(controller.PORT_CRA, this);
/*
* Since a CRA write may have also changed which register (PDA or DDA) is enabled via the corresponding
* PDA port, we simply ask ask both to update (only the one that's enabled will write itself to memory).
*/
controller.regPDA.update();
controller.regDDA.update();
};
}(this)
};
this.regDDB = {
bits: 0xff, // set all DDB bits, indicating that all PDB bits represent OUTPUT lines
read: function() {},
update: function(controller) {
return function(b) {
if (b !== undefined) this.bits = b;
if (!(controller.regCRB.bits & controller.CR_PD_SEL)) {
controller.writePort(controller.PORT_DDB, this);
}
};
}(this)
};
this.regPDB = {
bits: 0xff,
read: function() {},
update: function(controller) {
return function(b) {
this.bits = controller.updatePDB(b);
if (controller.regCRB.bits & controller.CR_PD_SEL) {
controller.writePort(controller.PORT_PDB, this);
}
};
}(this)
};
this.regCRB = {
bits: 0,
read: function() {},
update: function(controller) {
return function(b) {
/*
* Most bits written to CRB should be left as-is (the CPU should read back what it wrote);
* bits 7 and 6 (IRQ1 and IRQ2) are exceptions, since those are tied to peripheral "Control Lines"
* C1 and C2, which can in theory generate an interrupt depending on how the C1_CTRL and C2_CTRL bits
* in CRB are set. However, assuming there's no need to simulate interrupts for this particular
* controller hardware, all we'll do is simply insure those two bits are always off.
*/
if (b !== undefined) this.bits = (b & ~(controller.CR_IRQ1 | controller.CR_IRQ2));
controller.writePort(controller.PORT_CRB, this);
/*
* Since a CRB write may have also changed which register (PDB or DDB) is enabled via the corresponding
* PDB port, we simply ask ask both to update (only the one that's enabled will write itself to memory).
*/
controller.regPDB.update();
controller.regDDB.update();
};
}(this)
};
this.regCTRL = {
bits: 0,
read: function() {},
update: function(controller) {
return function(b) {
if (b !== undefined) {
if ((b & controller.CTRL_CDIV) == controller.CTRL_CDIV) {
/*
* Setting both CTRL_CDIV bits (CR0 and CR1) constitutes a "Master Reset" of the ACIA
*/
controller.regSTAT.bits = (controller.STAT_TDRE | controller.STAT_DCD | controller.STAT_CTS);
}
this.bits = b;
}
// regCTRL isn't readable; instead, we ensure regSTAT is rewritten in its place
controller.regSTAT.update();
};
}(this)
};
this.regSTAT = {
bits: (this.STAT_TDRE | this.STAT_DCD | this.STAT_CTS),
read: function() {},
update: function(controller) {
return function(b) {
this.bits = controller.updateSTAT(b);
controller.writePort(controller.PORT_STAT, this);
};
}(this)
};
this.regDATA = {
bits: 0,
read: function(controller) {
return function() {
controller.advanceDriveData();
};
}(this),
update: function(controller) {
return function(b) {
if (b !== undefined) this.bits = b;
controller.writePort(controller.PORT_DATA, this);
};
}(this)
};
this.regUnknown = {
bits: 0,
read: function() {},
update: function(controller) {
return function(b) {};
}(this)
};
if (DEBUG) {
this.regDDA.sName = "DDA",
this.regDDA.aBitIDs = {0x80:"DD7",0x40:"DD6",0x20:"DD5",0x10:"DD4",0x08:"DD3",0x04:"DD2",0x02:"DD1",0x01:"DD0"}; // jshint ignore:line
this.regPDA.sName = "PDA";
this.regPDA.aBitIDs = {0x80:"IHD",0x40:"SD2",0x20:"WP",0x10:"RDY2",0x08:"SHD",0x04:"FD",0x02:"TZD",0x01:"RDY1"};
this.regCRA.sName = "CRA";
this.regCRA.aBitIDs = {0x80:"IRQ1",0x40:"IRQ2",0x20:"C2OUT",0x10:"C2:1",0x08:"C2:0",0x04:"PDS",0x02:"C1:1",0x01:"C1:0"};
this.regDDB.sName = "DDB";
this.regDDB.aBitIDs = {0x80:"DD7",0x40:"DD6",0x20:"DD5",0x10:"DD4",0x08:"DD3",0x04:"DD2",0x02:"DD1",0x01:"DD0"};
this.regPDB.sName = "PDB";
this.regPDB.aBitIDs = {0x80:"HLD",0x40:"LCS",0x20:"SD1",0x10:"FR",0x08:"ST",0x04:"STI",0x02:"EE",0x01:"WE"};
this.regCRB.sName = "CRB";
this.regCRB.aBitIDs = {0x80:"IRQ1",0x40:"IRQ2",0x20:"C2OUT",0x10:"C2:1",0x08:"C2:0",0x04:"PDS",0x02:"C1:1",0x01:"C1:0"};
this.regCTRL.sName = "CTRL";
this.regCTRL.aBitIDs = {0x80:"CR7",0x40:"CR6",0x20:"CR5",0x10:"CR4",0x08:"CR3",0x04:"CR2",0x02:"CR1",0x01:"CR0"};
this.regSTAT.sName = "STAT";
this.regDATA.sName = "DATA";
this.regUnknown.sName = "unknown";
}
}
/**
* @this {C1PDiskController}
* @param {number} iDrive
* @param {number} iDriveType
* @param {number} nMaxTracks
*/
resetDrive(iDrive, iDriveType, nMaxTracks)
{
this.aDrives[iDrive] = {
iType: iDriveType,
nTracks: nMaxTracks,
fProtected: true, // fake for now
nIndexPulse: 20, // nIndex (20 is initial index pulse)
iTrackSelect: 0, // nTrack
iTrackOffset: -1, // nSector
/*
* Our disk data consists of an array of tracks, where each track is an array of sectors;
* as long as aTracks.length == 0 (empty array), the drive is not considered "loaded" with a disk.
*/
aTracks: []
};
}
/**
* @this {C1PDiskController}
* @param {string} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listDisk")
* @param {HTMLElement} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
setBinding(sHTMLType, sBinding, control, sValue)
{
switch(sBinding) {
case "listDisk":
this.bindings[sBinding] = control;
return true;
case "loadDisk":
this.bindings[sBinding] = control;
control.onclick = function(controller) {
return function() {
if (controller.bindings["listDisk"]) {
var sFilePath = controller.bindings["listDisk"].value;
var sFileURL = sFilePath;
/*
* If the selected disk image has a ".json" extension, then we assume it's a pre-converted
* JSON-encoded disk image, so we load it as-is; otherwise, we ask our server-side disk image
* converter to return the corresponding JSON-encoded data, in compact form (ie, minimal whitespace,
* no ASCII data comments, etc).
*/
if (sFilePath.substr(sFilePath.length-5) != ".json") {
/*
* TODO: This code was using a deprecated parameter (compact=1); make sure things still work.
*
* TODO: Convert this code to use the new shared Disk API definitions and weblib functions; eg:
*
* sDiskURL = Web.getHostOrigin() + DumpAPI.ENDPOINT + "?" + DumpAPI.QUERY.DISK + "=" + sDiskPath;
*/
sFileURL = "http://" + window.location.host + "/api/v1/dump?disk=" + sFilePath;
}
controller.println("loading " + Str.getBaseName(sFilePath) + "...");
Web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
controller.loadDisk(sURL, sResponse, nErrorCode);
});
}
};
}(this);
return true;
default:
break;
}
return false;
}
/**
* @this {C1PDiskController}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
setBuffer(abMemory, start, end, cpu)
{
this.abMem = abMemory;
this.addrController = start;
// this.addrControllerLimit = end + 1;
if ((this.cpu = cpu)) {
cpu.addReadNotify(start, end, this, this.getByte);
cpu.addWriteNotify(start, end, this, this.setByte);
}
this.setReady();
}
/**
* @this {C1PDiskController}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*
* We need We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
*/
setPower(fOn, cmp)
{
if (fOn && !this.flags.powered) {
this.flags.powered = true;
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
}
}
/**
* @this {C1PDiskController}
* @param {string} sDiskName
* @param {string} sDiskData
* @param {number} nErrorCode (response from server if anything other than 200)
*
* NOTE: Although I've expanded the JSON disk-image format to support multiple heads (ie, platters or disk surfaces),
* this controller implementation currently supports only single-head drives, and therefore only single-sided images.
* So, if the image contains more than one entry in head data array, all we use is the first entry; data for any remaining
* heads is discarded.
*
* WARNING: The disk-image format should match that used by PCjs, where the image is an array of cylinders, each of which
* is an array of heads. That's also more typical, because it maintains the original data's physical locality.
*/
loadDisk(sDiskName, sDiskData, nErrorCode)
{
if (nErrorCode) {
this.println("disk load error (" + nErrorCode + ")");
return;
}
var aHeads = [];
this.println("mounting " + sDiskName + "...");
try {
/*
* The most likely source of any exception will be right here, where we're parsing
* the JSON-encoded disk data.
*/
aHeads = eval("(" + sDiskData + ")"); // jshint ignore:line
if (!aHeads.length) {
this.println("no data: " + sDiskName);
return;
}
if (!aHeads[0].length) {
this.println("no tracks: " + sDiskName);
return;
}
var aTracks = aHeads[0];
if (aTracks[0]['trackNum'] === undefined) {
this.println("data error: " + aTracks[0]);
return;
}
/*
* NOTE: This should never happen, otherwise we shouldn't have initiated the load
* in the first place. Can we guarantee that and eliminate this test?
*/
if (!this.aDrives[0]) {
this.println("no available drives");
return;
}
/*
* To make disk access more efficient, we need to supplement every track object with a
* simple byte-array (trackData) containing all the data bytes for the entire track.
*/
for (var iTrack=0; iTrack < aTracks.length; iTrack++) {
var iTrackNum;
var track = aTracks[iTrack];
var sectors = track['sectors'];
/*
* WARNING: There are MANY other ways the track data could be malformed, but we'll
* start with the most egregious, and worry about the rest later.
*/
if ((iTrackNum = track['trackNum']) === undefined || sectors === undefined) {
throw new Error("track " + iTrack + " missing data");
}
/*
* WARNING: We allow out-of-order tracks, because we store each track's data according
* to its trackNum index, but just in case that wasn't intended, we're going to mention it.
*/
if (iTrackNum != iTrack) {
Component.warning("track " + iTrackNum + " out of order (expected " + iTrack + ")");
}
/*
* For each track, we start with an empty trackData array and "push" (ie, append) all the
* sector data onto it. Most of the data is already in byte form and can simply use Array.push(),
* but there is also some metadata (signatures, types, lengths, etc), for which we have assorted
* helpers below: pushBCD, pushBin, and pushSig.
*/
var trackData = [], sector, sectorData, i;
if (!iTrackNum) {
sector = sectors[0];
sectorData = sector['sectorData'];
this.pushBin(trackData, track, 'trackLoad', 2);
this.pushBin(trackData, sector, 'sectorPages');
for (i = 0; i < sectorData.length; i++) {
trackData.push(sectorData[i]);
}
}
else {
this.pushSig(trackData, track, 'trackSig');
this.pushBCD(trackData, track, 'trackNum');
this.pushBin(trackData, track, 'trackType');
for (var iSector=0; iSector < sectors.length; iSector++) {
sector = sectors[iSector];
sectorData = sector['sectorData'];
this.pushBin(trackData, sector, 'sectorSig');
this.pushBin(trackData, sector, 'sectorNum');
this.pushBin(trackData, sector, 'sectorPages');
for (i = 0; i < sectorData.length; i++) {
trackData.push(sectorData[i]);
}
this.pushSig(trackData, sector, 'sectorEndSig');
}
}
/*
* Finally, here's where we add the newly-created chunk of track data to the current track object
*/
aTracks[iTrackNum].trackData = trackData;
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_DISK)) {
this.dbg.message("track " + iTrackNum + ": " + trackData.length + " bytes");
}
}
this.aDrives[0].aTracks = aTracks;
this.println("mount of " + sDiskName + " complete");
} catch (e) {
this.println("disk data error: " + e.message);
}
}
/**
* @this {C1PDiskController}
* @param {Array.<number>} a
* @param {Object} o is the object containing the key
* @param {string} k is the key of 8-bit value to convert to BCD (ie, two 4-bit BCD digits) and push
*/
pushBCD(a, o, k)
{
var n = o[k];
if (n === undefined) {
throw new Error("missing bcd value: " + k);
}
var bcd = (Math.floor(n / 10) << 4) | (n % 10);
a.push(bcd);
}
/**
* @this {C1PDiskController}
* @param {Array.<number>} a
* @param {Object} o is the object containing the key
* @param {string} k is the key of the value
* @param {number} [cb] is the number of bytes to push (only 1 or 2 is supported, and the default is 1)
*/
pushBin(a, o, k, cb)
{
var n = o[k];
if (n === undefined) {
throw new Error("missing binary value: " + k);
}
if (cb == 2) {
a.push((n >> 8) & 0xff);
}
a.push(n & 0xff);
}
/**
* @this {C1PDiskController}
* @param {Array.<number>} a
* @param {Object} o is the object containing the key
* @param {string} k is the key of the signature string to push
*/
pushSig(a, o, k)
{
var s = o[k];
if (s === undefined) {
throw new Error("missing signature: " + k);
}
for (var i=0; i < s.length; i++) {
a.push(s.charCodeAt(i));
}
}
/**
* @this {C1PDiskController}
* @param {number} port address (0x0000-0x00FF) relative to addrController (0xC000)
* @param {boolean} fWrite is true if port write, false if port read
* @return {Object} reg will always be a valid register object, but it may be the "unknown" register if we don't recognize the port.
*/
getReg(port, fWrite)
{
var reg;
port &= 0x3F;
/*
* Now that we've masked the full port range of 0x00-0xFF down to 0x00-0x3F, we further mask the
* PIA port range (0x00-0x0F) to 0x00-0x03, and the ACIA port range (0x10-0x1F) to 0x10-0x11.
* The rest of the masked range (0x20-0x3F) is unmapped, so we map it to our global unknown register.
*/
if (port < 0x10)
port &= 0x03;
else if (port < 0x20)
port &= 0x11;
switch(port) {
case this.PORT_PDA:
reg = (this.regCRA.bits & this.CR_PD_SEL)? this.regPDA : this.regDDA;
break;
case this.PORT_CRA:
reg = this.regCRA;
break;
case this.PORT_PDB:
reg = (this.regCRB.bits & this.CR_PD_SEL)? this.regPDB : this.regDDB;
break;
case this.PORT_CRB:
reg = this.regCRB;
break;
case this.PORT_CTRL:
reg = (fWrite? this.regCTRL : this.regSTAT);
break;
case this.PORT_DATA:
reg = this.regDATA;
break;
default:
reg = this.regUnknown;
break;
}
return reg;
}
/**
* @this {C1PDiskController}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
*/
getByte(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
*/
if (addrFrom !== undefined) {
var port = addr - this.addrController;
var reg = this.getReg(port, false);
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_DISK, false, reg.sName);
reg.read();
}
}
/**
* @this {C1PDiskController}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
*/
setByte(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
*/
if (addrFrom !== undefined) {