This repository has been 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 pathhdc.js
2748 lines (2577 loc) · 103 KB
/
hdc.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 Implements the PCjs Hard Drive Controller (HDC) component.
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Nov-26
*
* Copyright © 2012-2014 Jeff Parsons <[email protected]>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://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 source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* 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 the
* PCjs program 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 DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var ChipSet = require("./chipset");
var Disk = require("./disk");
var State = require("./state");
}
/**
* HDC(parmsHDC)
*
* The HDC component simulates an STC-506/412 interface to an IBM-compatible fixed disk drive. The first
* such drive was a 10Mb 5.25-inch drive containing two platters and 4 heads. Data spanned 306 cylinders
* for a total of 1224 tracks, with 17 sectors/track and 512 bytes/sector.
*
* HDC supports the following component-specific properties:
*
* drives: an array of driveConfig objects, each containing 'name', 'path', 'size' and 'type' properties
* type: either 'xt' (for the PC XT Xebec controller) or 'at' (for the PC AT Western Digital controller)
*
* The 'type' parameter defaults to 'xt'. All ports for the PC XT controller are referred to as XTC ports,
* and similarly, all PC AT controller ports are referred to as ATC ports.
*
* If 'path' is empty, a scratch disk image is created; otherwise, we make a note of the path, but we will NOT
* pre-load it like we do for floppy disk images.
*
* My current plan is to read all disk data on-demand, keeping a cache of what we've read, and possibly adding
* some read-ahead as well. Any portions of the disk image that are written before being read will never be read.
*
* TRIVIA: On p.1-179 of the PC XT Technical Reference Manual (revised APR83), it reads:
*
* "WARNING: The last cylinder on the fixed disk drive is reserved for diagnostic use.
* Diagnostic write tests will destroy any data on this cylinder."
*
* Does FDISK insure that the last cylinder is reserved? I'm sure we'll eventually find out.
*
* @constructor
* @extends Component
* @param {Object} parmsHDC
*/
function HDC(parmsHDC) {
Component.call(this, "HDC", parmsHDC, HDC);
this['dmaRead'] = this.dmaRead;
this['dmaWrite'] = this.dmaWrite;
this['dmaWriteBuffer'] = this.dmaWriteBuffer;
this['dmaWriteFormat'] = this.dmaWriteFormat;
this.aDriveConfigs = [];
if (parmsHDC['drives']) {
try {
/*
* The most likely source of any exception will be right here, where we're parsing
* the JSON-encoded disk data.
*/
this.aDriveConfigs = eval("(" + parmsHDC['drives'] + ")");
/*
* Nothing more to do with aDriveConfigs now. initController() and autoMount() (if there are
* any disk image "path" properties to process) will take care of the rest.
*/
} catch (e) {
Component.error("HDC drive configuration error: " + e.message + " (" + parmsHDC['drives'] + ")");
}
}
/*
* Set fATC (AT Controller flag) according to the 'type' parameter. This in turn determines other
* defaults. For example, the default XT drive type is 3 (for a 10Mb disk drive), whereas the default
* AT drive type is 2 (for a 20Mb disk drive).
*/
this.fATC = (parmsHDC['type'] == "at");
this.iHDC = this.fATC? 1 : 0;
this.iDriveTypeDefault = this.fATC? 2 : 3;
/*
* The remainder of HDC initialization now takes place in our initBus() handler
*/
}
Component.subclass(Component, HDC);
/*
* HDC defaults, in case drive parameters weren't specified
*/
HDC.DEFAULT_DRIVE_NAME = "Hard Drive";
/*
* Each of the following DriveType entries contain (up to) 4 values:
*
* [0]: total cylinders
* [1]: total heads
* [2]: total sectors/tracks (optional; default is 17)
* [3]: total bytes/sector (optional; default is 512)
*
* verifyDrive() attempts to confirm that these values agree with the programmed drive characteristics.
*/
HDC.aDriveTypes = [
{
0x00: [306, 2],
0x01: [375, 8],
0x02: [306, 6],
0x03: [306, 4] // <= default 'xt' drive type (10Mb)
},
/*
* Sadly, drive types differ across controller models (XTC drive types don't match ATC drive types),
* so aDriveTypes must first be indexed by a controller index (this.iHDC).
*
* The following is a more complete description of the drive types supported by the MODEL_5170, where C is
* Cylinders, H is Heads, WP is Write Pre-Comp, and LZ is Landing Zone (in practice, we don't need WP or LZ).
*
* Type C H WP LZ
* ---- --- -- --- ---
* 1 306 4 128 305
* 2 615 4 300 615
* 3 615 6 300 615
* 4 940 8 512 940
* 5 940 6 512 940
* 6 615 4 no 615
* 7 462 8 256 511
* 8 733 5 no 733
* 9 900 15 no8 901
* 10 820 3 no 820
* 11 855 5 no 855
* 12 855 7 no 855
* 13 306 8 128 319
* 14 733 7 no 733
* 15 (reserved--all zeros)
*/
{
0x01: [306, 4],
0x02: [615, 4], // <= default 'at' drive type (20Mb)
0x03: [615, 6],
0x04: [940, 8],
0x05: [940, 6],
0x06: [615, 4],
0x07: [462, 8],
0x08: [733, 5],
0x09: [900,15],
0x0A: [820, 3],
0x0B: [855, 5],
0x0C: [855, 7],
0x0D: [306, 8],
0x0E: [733, 7]
}
];
/*
* ATC (AT Controller) Registers
*
* The "IBM Personal Computer AT Fixed Disk and Diskette Drive Adapter", aka the HFCOMBO card, contains what we refer
* to here as the ATC (AT Controller). Even though that card contains both Fixed Disk and Diskette Drive controllers,
* this component (HDC) still deals only with the "Fixed Disk" portion. Fortunately, the "Diskette Drive Adapter"
* portion of the card is compatible with the existing FDC component, so that component continues to be responsible
* for all diskette operations.
*
* ATC ports default to their primary addresses; secondary port addresses are 0x80 lower (eg, 0x170 instead of 0x1F0).
*
* It's important to know that the MODEL_5170 BIOS has a special relationship with the "Combo Hard File/Diskette
* (HFCOMBO) Card" (see @F000:144C). Initially, the ChipSet component intercepted reads for HFCOMBO's STATUS port
* and returned the BUSY bit clear to reduce boot time; however, it turned out that was also a prerequisite for the
* BIOS to write test patterns to the CYLLO port and set the "DUAL" bit (bit 0) of the "HFCNTRL" byte at 40:8Fh if
* those CYLLO operations succeeded (now that the HDC is "ATC-aware", those ChipSet port intercepts have been removed).
*
* Without the "DUAL" bit set, when it came time later to report the diskette drive type, the "DISK_TYPE" function
* (@F000:273D) would branch to one of two almost-identical blocks of code -- specifically, a block that disallowed
* diskette drive types >= 2 (ChipSet.CMOS.FDRIVE.DSDD) instead of >= 3 (ChipSet.CMOS.FDRIVE.DSHD).
*
* In other words, the "Fixed Disk" portion of the HFCOMBO controller has to be present and operational if the user
* wants to use high-capacity (80-track) diskettes with "Diskette Drive" portion of the controller. This may not be
* immediately obvious to anyone creating a 5170 machine configuration with the FDC component but no HDC component.
*
* TODO: Investigate what a MODEL_5170 can do, if anything, with diskettes if an "HFCOMBO card" was NOT installed
* (eg, was there Diskette-only Controller that could be installed, and if so, did it support high-capacity diskettes?)
* Also, consider making the FDC component able to detect when the HDC is missing and provide the same minimal HFCOMBO
* port intercepts that ChipSet once provided (this is not a compatibility requirement, just a usability improvement).
*/
HDC.ATC = {
DATA: { PORT: 0x1F0}, // no register (read-write)
DIAG: { // this.regError (read-only)
PORT: 0x1F1,
NO_ERROR: 0x01,
CTRL_ERROR: 0x02,
SEC_ERROR: 0x03,
ECC_ERROR: 0x04,
PROC_ERROR: 0x05
},
ERROR: { // this.regError (read-only)
PORT: 0x1F1,
NO_DAM: 0x01, // Data Address Mark (DAM) not found
NO_TRK0: 0x02, // Track 0 not detected
CMD_ABORT: 0x04, // Aborted Command
NO_CHS: 0x10, // ID field with the specified C:H:S not found
ECC_ERR: 0x40, // Data ECC Error
BAD_BLOCK: 0x80 // Bad Block Detect
},
WPREC: { PORT: 0x1F1}, // this.regWPreC (write-only)
SECCNT: { PORT: 0x1F2}, // this.regSecCnt (read-write; 0 implies a 256-sector request)
SECNUM: { PORT: 0x1F3}, // this.regSecNum (read-write)
CYLLO: { PORT: 0x1F4}, // this.regCylLo (read-write; all 8 bits are used)
CYLHI: { // this.regCylHi (read-write; only bits 0-1 are used, for a total of 10 bits, or 1024 max cylinders)
PORT: 0x1F5,
MASK: 0x03
},
DRVHD: { // this.regDrvHd (read-write)
PORT: 0x1F6,
HEAD_MASK: 0x0F, // set this to the max number of heads before issuing a SET PARAMETERS command
DRIVE_MASK: 0x10,
SET_MASK: 0xE0,
SET_BITS: 0xA0 // for whatever reason, these bits must always be set
},
STATUS: { // this.regStatus (read-only; reading clears IRQ.ATC)
PORT: 0x1F7,
BUSY: 0x80, // if this is set, no other STATUS bits are valid
READY: 0x40, // if this is set (along with the SEEK_OK bit), the drive is ready to read/write/seek again
WFAULT: 0x20, // write fault
SEEK_OK: 0x10, // seek operation complete
DATA_REQ: 0x08, // indicates that "the sector buffer requires servicing during a Read or Write command. If either bit 7 (BUSY) or this bit is active, a command is being executed. Upon receipt of any command, this bit is reset."
CORRECTED: 0x04,
INDEX: 0x02, // set once for every revolution of the disk
ERROR: 0x01 // set when the previous command ended in an error; one or more bits are set in the ERROR register (the next command to the controller resets the ERROR bit)
},
COMMAND:{ // this.regCommand (write-only)
PORT: 0x1F7,
RESTORE: 0x10, // low nibble x 500us equal stepping rate (except for 0, which corresponds to 35us) (aka RECALIBRATE)
READ_DATA: 0x20, // also supports NO_RETRIES and WITH_ECC
WRITE_DATA: 0x30, // also supports NO_RETRIES and WITH_ECC
READ_VERF: 0x40, // also supports NO_RETRIES
FORMAT_TRK: 0x50,
SEEK: 0x70, // low nibble x 500us equal stepping rate (except for 0, which corresponds to 35us)
DIAGNOSE: 0x90,
SETPARMS: 0x91,
NO_RETRIES: 0x01,
WITH_ECC: 0x02,
MASK: 0xF0
}
};
/*
* XTC (XT Controller) Registers
*/
/*
* XTC Data Register (0x320, read-write)
*
* Writes to this register are discussed below; see HDC Commands.
*
* Reads from this register after a command has been executed retrieve a "status byte",
* which must NOT be confused with the Status Register (see below). This data "status byte"
* contains only two bits of interest: XTC_DATA.STATUS_ERROR and XTC_DATA.STATUS_UNIT.
*/
HDC.XTC = {};
HDC.XTC.DATA = {};
HDC.XTC.DATA.PORT = 0x320; // port address
HDC.XTC.DATA.STATUS_OK = 0x00; // no error
HDC.XTC.DATA.STATUS_ERROR = 0x02; // error occurred during command execution
HDC.XTC.DATA.STATUS_UNIT = 0x20; // logical unit number of the drive
/*
* XTC Status Register (0x321, read-only)
*
* WARNING: The IBM Technical Reference Manual *badly* confuses the XTC_DATA "status byte" (above)
* that the controller sends following an HDC.XTC.DATA.CMD operation with the Status Register (below).
* In fact, it's so badly confused that it completely fails to document any of the Status Register
* bits below; I'm forced to guess at their meanings from the HDC BIOS listing.
*/
HDC.XTC.STATUS = {};
HDC.XTC.STATUS.PORT = 0x321; // port address
HDC.XTC.STATUS.NONE = 0x00;
HDC.XTC.STATUS.REQ = 0x01; // HDC BIOS: request bit
HDC.XTC.STATUS.IOMODE = 0x02; // HDC BIOS: mode bit (GUESS: set whenever XTC_DATA contains a response?)
HDC.XTC.STATUS.BUS = 0x04; // HDC BIOS: command/data bit (GUESS: set whenever XTC_DATA ready for request?)
HDC.XTC.STATUS.BUSY = 0x08; // HDC BIOS: busy bit
HDC.XTC.STATUS.INTERRUPT = 0x20; // HDC BIOS: interrupt bit
/*
* XTC Config Register (0x322, read-only)
*
* This register is used to read HDC card switch settings that defined the "Drive Type" for
* drives 0 and 1. SW[1],SW[2] (for drive 0) and SW[3],SW[4] (for drive 1) are set as follows:
*
* ON, ON Drive Type 0 (306 cylinders, 2 heads)
* ON, OFF Drive Type 1 (375 cylinders, 8 heads)
* OFF, ON Drive Type 2 (306 cylinders, 6 heads)
* OFF, OFF Drive Type 3 (306 cylinders, 4 heads)
*/
/*
* XTC Commands, as issued to XTC_DATA
*
* Commands are multi-byte sequences sent to XTC_DATA, starting with a XTC_DATA.CMD byte,
* and followed by 5 more bytes, for a total of 6 bytes, which collectively are called a
* Device Control Block (DCB). Not all commands use all 6 bytes, but all 6 bytes must be present;
* unused bytes are simply ignored.
*
* XTC_DATA.CMD (3-bit class code, 5-bit operation code)
* XTC_DATA.HEAD (1-bit drive number, 5-bit head number)
* XTC_DATA.CLSEC (upper bits of 10-bit cylinder number, 6-bit sector number)
* XTC_DATA.CH (lower bits of 10-bit cylinder number)
* XTC_DATA.COUNT (8-bit interleave or block count)
* XTC_DATA.CTRL (8-bit control field)
*
* One command, HDC.XTC.DATA.CMD.INIT_DRIVE, must include 8 additional bytes following the DCB:
*
* maximum number of cylinders (high)
* maximum number of cylinders (low)
* maximum number of heads
* start reduced write current cylinder (high)
* start reduced write current cylinder (low)
* start write precompensation cylinder (high)
* start write precompensation cylinder (low)
* maximum ECC data burst length
*
* Note that the 3 word values above are stored in "big-endian" format (high byte followed by low byte),
* rather than the more typical "little-endian" format (low byte followed by high byte).
*/
HDC.XTC.DATA.CMD = {
TEST_READY: 0x00, // Test Drive Ready
RECALIBRATE: 0x01, // Recalibrate
REQUEST_SENSE: 0x03, // Request Sense Status
FORMAT_DRIVE: 0x04, // Format Drive
READ_VERF: 0x05, // Read Verify
FORMAT_TRK: 0x06, // Format Track
FORMAT_BAD: 0x07, // Format Bad Track
READ_DATA: 0x08, // Read
WRITE_DATA: 0x0A, // Write
SEEK: 0x0B, // Seek
INIT_DRIVE: 0x0C, // Initialize Drive Characteristics
READ_ECC_BURST: 0x0D, // Read ECC Burst Error Length
READ_BUFFER: 0x0E, // Read Data from Sector Buffer
WRITE_BUFFER: 0x0F, // Write Data to Sector Buffer
RAM_DIAGNOSTIC: 0xE0, // RAM Diagnostic
DRV_DIAGNOSTIC: 0xE3, // HDC BIOS: CHK_DRV_CMD
CTL_DIAGNOSTIC: 0xE4, // HDC BIOS: CNTLR_DIAG_CMD
READ_LONG: 0xE5, // HDC BIOS: RD_LONG_CMD
WRITE_LONG: 0xE6 // HDC BIOS: WR_LONG_CMD
};
/*
* HDC error conditions, as returned in byte 0 of the (4) bytes returned by the Request Sense Status command
*/
HDC.XTC.DATA.ERR = {
NONE: 0x00,
NO_INDEX: 0x01, // no index signal detected
SEEK_INCOMPLETE:0x02, // no seek-complete signal
WRITE_FAULT: 0x03,
NOT_READY: 0x04, // after the controller selected the drive, the drive did not respond with a ready signal
NO_TRACK: 0x06, // after stepping the max number of cylinders, the controller did not receive the track 00 signal from the drive
STILL_SEEKING: 0x08,
ECC_ID_ERROR: 0x10,
ECC_DATA_ERROR: 0x11,
NO_ADDR_MARK: 0x12,
NO_SECTOR: 0x14,
BAD_SEEK: 0x15, // seek error: the cylinder and/or head address did not compare with the expected target address
ECC_CORRECTABLE:0x18, // correctable data error
BAD_TRACK: 0x19,
BAD_CMD: 0x20,
BAD_DISK_ADDR: 0x21,
RAM: 0x30,
CHECKSUM: 0x31,
POLYNOMIAL: 0x32,
MASK: 0x3F
};
HDC.XTC.DATA.SENSE = {
ADDR_VALID: 0x80
};
/*
* HDC Command Sequences
*
* Unlike the FDC, all the HDC commands have fixed-length command request sequences (well, OK, except for
* HDC.XTC.DATA.CMD.INIT_DRIVE) and fixed-length response sequences (well, OK, except for HDC.XTC.DATA.CMD.REQUEST_SENSE),
* so a table of byte-lengths isn't much use, but having names for all the commands is still handy for debugging.
*/
if (DEBUG) {
HDC.aATCCommands = {
0x10: "Restore (Recalibrate)",
0x20: "Read",
0x30: "Write",
0x40: "Read Verify",
0x50: "Format Track",
0x70: "Seek",
0x90: "Diagnose",
0x91: "Set Parameters"
};
HDC.aXTCCommands = {
0x00: "Test Drive Ready",
0x01: "Recalibrate",
0x03: "Request Sense Status",
0x04: "Format Drive",
0x05: "Read Verify",
0x06: "Format Track",
0x07: "Format Bad Track",
0x08: "Read",
0x0A: "Write",
0x0B: "Seek",
0x0C: "Initialize Drive Characteristics",
0x0D: "Read ECC Burst Error Length",
0x0E: "Read Data from Sector Buffer",
0x0F: "Write Data to Sector Buffer",
0xE0: "RAM Diagnostic",
0xE3: "Drive Diagnostic",
0xE4: "Controller Diagnostic",
0xE5: "Read Long",
0xE6: "Write Long"
};
}
/*
* HDC BIOS interrupts, functions, and other parameters
*
* When the HDC BIOS overwrites the ROM BIOS INT 0x13 address, it saves the original INT 0x13 address
* in the INT 0x40 vector.
*/
HDC.BIOS = {
INT_DISK: 0x13,
INT_DISKETTE: 0x40
};
/*
* NOTE: These are useful values for reference, but they're not actually used for anything at the moment.
*/
HDC.BIOS.DISK_CMD = {
RESET: 0x00,
GET_STATUS: 0x01,
READ_SECTORS: 0x02,
WRITE_SECTORS: 0x03,
VERIFY_SECTORS: 0x04,
FORMAT_TRK: 0x05,
FORMAT_BAD: 0x06,
FORMAT_DRIVE: 0x07,
GET_DRIVEPARMS: 0x08,
SET_DRIVEPARMS: 0x09,
READ_LONG: 0x0A,
WRITE_LONG: 0x0B,
SEEK: 0x0C,
ALT_RESET: 0x0D,
READ_BUFFER: 0x0E,
WRITE_BUFFER: 0x0F,
TEST_READY: 0x10,
RECALIBRATE: 0x11,
RAM_DIAGNOSTIC: 0x12,
DRV_DIAGNOSTIC: 0x13,
CTL_DIAGNOSTIC: 0x14
};
/**
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
*
* @this {HDC}
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listDisks")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
HDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
{
/*
* This is reserved for future use; for now, hard disk images can be specified during initialization only (no "hot-swapping")
*/
return false;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {HDC}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
HDC.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.cmp = cmp;
/*
* We need access to the ChipSet component, because we need to communicate with
* the PIC and DMA controller.
*/
this.chipset = cmp.getComponentByType("ChipSet");
bus.addPortInputTable(this, this.fATC? HDC.aATCPortInput : HDC.aXTCPortInput);
bus.addPortOutputTable(this, this.fATC? HDC.aATCPortOutput : HDC.aXTCPortOutput);
if (DEBUGGER) {
cpu.addIntNotify(HDC.BIOS.INT_DISK, this, this.intBIOSDisk);
cpu.addIntNotify(HDC.BIOS.INT_DISKETTE, this, this.intBIOSDiskette);
}
/*
* The following code used to be performed in the HDC constructor, but now we need to wait for information
* about the Computer to be available (eg, getMachineID() and getUserID()) before we start loading and/or
* connecting to disk images.
*
* If we didn't need auto-mount support, we could defer controller initialization until we received a powerUp()
* notification, at which point reset() would call initController(), or restore() would restore the controller;
* in that case, all we'd need to do here is call setReady().
*/
this.reset();
if (!this.autoMount()) this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {HDC}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.initController();
if (this.cmp.fReload) {
/*
* If the computer's fReload flag is set, we're required to toss all currently
* loaded disks and remount all disks specified in the auto-mount configuration.
*/
this.autoMount(true);
}
} else {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {HDC}
* @param {boolean} fSave
* @param {boolean} [fShutdown]
* @return {Object|boolean}
*/
HDC.prototype.powerDown = function(fSave, fShutdown)
{
return fSave && this.save? this.save() : true;
};
/**
* getMachineID()
*
* @return {string}
*/
HDC.prototype.getMachineID = function()
{
return this.cmp? this.cmp.getMachineID() : "";
};
/**
* getUserID()
*
* @return {string}
*/
HDC.prototype.getUserID = function()
{
return this.cmp? this.cmp.getUserID() : "";
};
/**
* reset()
*
* @this {HDC}
*/
HDC.prototype.reset = function()
{
/*
* TODO: The controller is also initialized by the constructor, to assist with auto-mount support,
* so think about whether we can skip powerUp initialization.
*/
this.initController(null, true);
};
/**
* save()
*
* This implements save support for the HDC component.
*
* @this {HDC}
* @return {Object}
*/
HDC.prototype.save = function()
{
var state = new State(this);
state.set(0, this.saveController());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the HDC component.
*
* @this {HDC}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.restore = function(data)
{
return this.initController(data[0]);
};
/**
* initController(data, fReset)
*
* @this {HDC}
* @param {Array} [data]
* @param {boolean} [fReset] true if a machine reset (not just a controller reset)
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.initController = function(data, fReset)
{
var i = 0;
var fSuccess = true;
/*
* At this point, it's worth calling into question my decision to NOT split the HDC component into separate XTC
* and ATC components, given all the differences, and given that I'm about to write some "if (ATC) else (XTC) ..."
* code. And all I can say in my defense is, yes, it's definitely worth calling that into question.
*
* However, there's also some common code, mostly in the area of disk management rather than controller management,
* and if the components were split, then I'd have to create a third component for that common code (although again,
* disk management probably belongs in its own component anyway).
*
* However, let's not forget that since my overall plan is to have only one PCjs "binary", everything's going to end
* up in the same bucket anyway, so let's not be too obsessive about organizational details. As long as the number
* of these conditionals is small and they're not performance-critical, this seems much ado about nothing.
*/
if (this.fATC) {
/*
* Since there's no way (and never will be a way) for an HDC to change its "personality" (from 'xt' to 'at'
* or vice versa), we're under no obligation to use the same number of registers, or save/restore format, etc,
* as the original XT controller.
*/
if (data == null) data = [0, 0, 0, 0, 0, 0, 0, 0, HDC.ATC.STATUS.READY, 0];
this.regError = data[i++];
this.regWPreC = data[i++];
this.regSecCnt = data[i++];
this.regSecNum = data[i++];
this.regCylLo = data[i++];
this.regCylHi = data[i++];
this.regDrvHd = data[i++];
this.regStatus = data[i++];
this.regCommand = data[i++];
/*
* Additional state is maintained by the Drive object (eg, abSector, ibSector)
*/
} else {
if (data == null) data = [0, HDC.XTC.STATUS.NONE, new Array(14), 0, 0];
this.regConfig = data[i++];
this.regStatus = data[i++];
this.regDataArray = data[i++]; // there can be up to 14 command bytes (6 for normal commands, plus 8 more for HDC.XTC.DATA.CMD.INIT_DRIVE)
this.regDataIndex = data[i++]; // used to control the next data byte to be received
this.regDataTotal = data[i++]; // used to control the next data byte to be sent (internally, we use regDataIndex to read data bytes, up to this total)
this.regReset = data[i++];
this.regPulse = data[i++];
this.regPattern = data[i++];
/*
* Initialize iDriveAllowFail only if it's never been initialized, otherwise its entire purpose will be defeated.
* See the related HACK in intBIOSDisk() for more details.
*/
var iDriveAllowFail = data[i++];
if (iDriveAllowFail !== undefined) {
this.iDriveAllowFail = iDriveAllowFail;
} else {
if (this.iDriveAllowFail === undefined) this.iDriveAllowFail = -1;
}
}
if (this.aDrives === undefined) {
this.aDrives = new Array(this.aDriveConfigs.length);
}
var dataDrives = data[i];
if (dataDrives === undefined) dataDrives = [];
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
if (this.aDrives[iDrive] === undefined) {
this.aDrives[iDrive] = {};
}
var drive = this.aDrives[iDrive];
var driveConfig = this.aDriveConfigs[iDrive];
if (!this.initDrive(iDrive, drive, driveConfig, dataDrives[iDrive], fReset)) {
fSuccess = false;
}
/*
* XTC only: the original STC-506/412 controller had two pairs of DIP switches to indicate a drive
* type (0, 1, 2 or 3) for drives 0 and 1. Those switch settings are recorded in regConfig, now that
* drive.type has been validated by initDrive().
*/
if (this.regConfig != null && iDrive <= 1) {
this.regConfig |= (drive.type & 0x3) << ((1 - iDrive) << 1);
}
}
if (DEBUG) this.messageDebugger("HDC initialized for " + this.aDrives.length + " drive(s)");
return fSuccess;
};
/**
* saveController()
*
* @this {HDC}
* @return {Array}
*/
HDC.prototype.saveController = function()
{
var i = 0;
var data = [];
if (this.fATC) {
data[i++] = this.regError;
data[i++] = this.regWPreC;
data[i++] = this.regSecCnt;
data[i++] = this.regSecNum;
data[i++] = this.regCylLo;
data[i++] = this.regCylHi;
data[i++] = this.regDrvHd;
data[i++] = this.regStatus;
data[i++] = this.regCommand;
} else {
data[i++] = this.regConfig;
data[i++] = this.regStatus;
data[i++] = this.regDataArray;
data[i++] = this.regDataIndex;
data[i++] = this.regDataTotal;
data[i++] = this.regReset;
data[i++] = this.regPulse;
data[i++] = this.regPattern;
data[i++] = this.iDriveAllowFail;
}
data[i] = this.saveDrives();
return data;
};
/**
* initDrive(iDrive, drive, driveConfig, data, fReset)
*
* TODO: Consider a separate Drive class that both FDC and HDC can use, since there's a lot of commonality
* between the drive objects created by both controllers. This will clean up overall drive management and allow
* us to factor out some common Drive methods (eg, advanceSector()).
*
* @this {HDC}
* @param {number} iDrive
* @param {Object} drive
* @param {Object} driveConfig (contains one or more of the following properties: 'name', 'path', 'size', 'type')
* @param {Array} [data]
* @param {boolean} [fReset] true if a machine reset (not just a controller reset)
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.initDrive = function(iDrive, drive, driveConfig, data, fReset)
{
var i = 0;
var fSuccess = true;
if (data === undefined) data = [HDC.XTC.DATA.ERR.NONE, 0, false, new Array(8)];
drive.iDrive = iDrive;
/*
* errorCode could be an HDC global, but in order to insulate HDC state from the operation of various functions
* that operate on drive objects (eg, readByte and writeByte), I've made it a per-drive variable. This choice may
* be contrary to how the actual hardware works, but I prefer this approach, as long as it doesn't expose any
* incompatibilities that any software actually cares about.
*/
drive.errorCode = data[i++];
drive.senseCode = data[i++];
drive.fRemovable = data[i++];
drive.abDriveParms = data[i++]; // captures drive parameters programmed via HDC.XTC.DATA.CMD.INIT_DRIVE
/*
* TODO: Make abSector a DWORD array rather than a BYTE array (we could even allocate a Memory block for it);
* alternatively, eliminate the buffer entirely and re-establish a reference to the appropriate Disk sector object.
*/
drive.abSector = data[i++];
/*
* The next group of properties are set by various HDC command sequences.
*/
drive.bHead = data[i++];
drive.nHeads = data[i++];
drive.wCylinder = data[i++];
drive.bSector = data[i++];
drive.bSectorEnd = data[i++]; // aka EOT
drive.nBytes = data[i++];
drive.bSectorBias = (this.fATC? 0: 1);
drive.name = driveConfig['name'];
if (drive.name === undefined) drive.name = HDC.DEFAULT_DRIVE_NAME;
drive.path = driveConfig['path'];
/*
* If no 'mode' is specified, we fall back to the original behavior, which is to completely preload
* any specific disk image, or create an empty (purely local) disk image.
*/
drive.mode = driveConfig['mode'] || (drive.path? DiskAPI.MODE.PRELOAD : DiskAPI.MODE.LOCAL);
/*
* On-demand I/O of raw disk images is supported only if there's a valid user ID; fall back to an empty
* local disk image if there's not.
*/
if (drive.mode == DiskAPI.MODE.DEMANDRO || drive.mode == DiskAPI.MODE.DEMANDRW) {
if (!this.getUserID()) drive.mode = DiskAPI.MODE.LOCAL;
}
drive.type = driveConfig['type'];
if (drive.type === undefined || HDC.aDriveTypes[this.iHDC][drive.type] === undefined) drive.type = this.iDriveTypeDefault;
var driveType = HDC.aDriveTypes[this.iHDC][drive.type];
drive.nSectors = driveType[2] || 17; // sectors/track
drive.cbSector = driveType[3] || 512; // bytes/sector (default is 512 if unspecified in the table)
/*
* On a full machine reset, pass the current drive type to setCMOSDriveType() (a no-op on pre-CMOS machines)
*/
if (fReset && this.chipset) {
this.chipset.setCMOSDriveType(iDrive, drive.type);
}
/*
* The next group of properties are set by user requests to load/unload disk images.
*
* NOTE: I now avoid reinitializing drive.disk in order to retain any previously mounted disk across resets.
*/
if (drive.disk === undefined) {
drive.disk = null;
this.notice("Type " + drive.type + " \"" + drive.name + "\" is fixed disk " + iDrive, true);
}
/*
* With the advent of save/restore, we need to verify every drive at initialization, not just whenever
* drive characteristics are initialized. Thus, if we've restored a sensible set of drive characteristics,
* then verifyDrive will create an empty disk if none has been provided, insuring we are ready for
* disk.restore().
*/
this.verifyDrive(drive);
/*
* The next group of properties are managed by worker functions (eg, doRead()) to maintain state across DMA requests.
*/
drive.ibSector = data[i++]; // location of the next byte to be accessed in the above sector
drive.sector = null; // initialized to null by worker, and then set to the next sector satisfying the request
if (drive.disk) {
var deltas = data[i];
if (deltas !== undefined && drive.disk.restore(deltas) < 0) {
fSuccess = false;
}
if (fSuccess && drive.ibSector !== undefined) {
drive.sector = drive.disk.seek(drive.wCylinder, drive.bHead, drive.bSector + drive.bSectorBias);
}
}
return fSuccess;
};
/**
* saveDrives()
*
* @this {HDC}
* @return {Array}
*/
HDC.prototype.saveDrives = function()
{
var i = 0;
var data = [];
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
data[i++] = this.saveDrive(this.aDrives[iDrive]);
}
return data;
};
/**
* saveDrive(drive)
*
* @this {HDC}
* @return {Array}
*/
HDC.prototype.saveDrive = function(drive)
{
var i = 0;
var data = [];
data[i++] = drive.errorCode;
data[i++] = drive.senseCode;
data[i++] = drive.fRemovable;
data[i++] = drive.abDriveParms;
data[i++] = drive.abSector;
data[i++] = drive.bHead;
data[i++] = drive.nHeads;
data[i++] = drive.wCylinder;
data[i++] = drive.bSector;
data[i++] = drive.bSectorEnd;
data[i++] = drive.nBytes;
data[i++] = drive.ibSector;
data[i] = drive.disk? drive.disk.save() : null;
return data;
};
/**
* copyDrive(iDrive)
*
* @this {HDC}
* @param {number} iDrive
* @return {Object|undefined} (undefined if the requested drive does not exist)
*/
HDC.prototype.copyDrive = function(iDrive)
{
var driveNew;
var driveOld = this.aDrives[iDrive];
if (driveOld !== undefined) {
driveNew = {};
for (var p in driveOld) {
driveNew[p] = driveOld[p];
}
}
return driveNew;
};
/**
* verifyDrive(drive, type)
*
* If no disk image is attached, create an empty disk with the specified drive characteristics.
* Normally, we'd rely on the drive characteristics programmed via the HDC.XTC.DATA.CMD.INIT_DRIVE
* command, but if an explicit drive type is specified, then we use the characteristics (geometry)
* associated with that type.
*
* @this {HDC}
* @param {Object} drive
* @param {number} [type] to create a disk of the specified type, if no disk exists yet
*/
HDC.prototype.verifyDrive = function(drive, type)
{
if (drive) {
var nHeads = 0, nCylinders = 0;
if (type == null) {
/*
* If the caller wants us to use the programmed drive parameters, we use those,
* but if there aren't any drive parameters (yet), then use default parameters based
* on drive.type.
*
* We used to do the last step ONLY if there was no drive.path -- otherwise, we'd waste
* time creating an empty disk if autoMount() was going to load an image from drive.path;
* but hopefully the Disk component is smarter now.
*/
nHeads = drive.abDriveParms[2];
if (nHeads) {
nCylinders = (drive.abDriveParms[0] << 8) | drive.abDriveParms[1];
} else {
type = drive.type;
}
}
if (type != null && !nHeads) {
nHeads = HDC.aDriveTypes[this.iHDC][type][1];
nCylinders = HDC.aDriveTypes[this.iHDC][type][0];
}
if (nHeads) {