summaryrefslogtreecommitdiff
path: root/v4.0/src/DEV/VDISK/VDISK.ASM
blob: 5994111a316c548a7bb68bd877a557a783454256 (plain) (blame)
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
	PAGE	,132
	TITLE	VDISK - Virtual Disk Device Driver

;VDISK simulates a disk drive, using Random Access Memory as the storage medium.

;This program is meant to serve as an example of a device driver.  It does not
;reflect the current level of VDISK.SYS.

;(C) Copyright 1988 Microsoft
;Licensed Material - Program Property of Microsoft

;Add the following statement to CONFIG.SYS
;	DEVICE=[d:][path]VDISK.SYS bbb sss ddd [/E:m]

;   where:  bbb is the desired buffer size (in kilobytes)
;		minimum 1KB, maximum is size of available memory,
;		default is 64KB.

;		VDISK will leave at least 64KB of available memory,
;		although subsequent device drivers (other than VDISK)
;		other programs that make themselves resident, and
;		COMMAND.COM will result in less than 64KB as shown
;		by CHKDSK.

;		Must be large enough for 1 boot sector + FAT sectors
;		+ 1 directory sector + at least 1 data cluster,
;		or the device driver won't be installed.

;	    sss is the desired sector size (in bytes)
;		128, 256, or 512, default is 128.
;		Will be adjusted if number of FAT entries > 0FE0H

;	    ddd is the desired number of directory entries
;		Minimum 2, maximum 512, default 64.
;		Will be rounded upward to sector size boundary.

;	    /E may only be used if extended memory above 1 megabyte
;	    is to be used.  INT 15H functions 87H and 88H are used
;	    to read and write this extended memory.
;	    The m parameter in the /E option specifies the maximum
;	    number of sectors that the VDISK will transfer at a time.
;	    Optional values are 1,2,3,4,5,6,7 or 8 sectors, the default
;	    is 8 sectors.

;	    Brackets indicate optional operands.


; Samples:
;	DEVICE=\path\VDISK.SYS 160 512 64
;	results in a 160KB VDISK, with 512 byte sectors, 64 directory entries

;	DEVICE=VDISK.SYS Buffersize 60 Sectorsize 128 Directory entries 32
;	(since only numbers are interpreted, you may comment the line with
;	non-numeric characters)

	SUBTTL	Structure Definitions
	PAGE
;-----------------------------------------------------------------------;
;	Request Header (Common portion) 				;
;-----------------------------------------------------------------------;
RH	EQU	DS:[BX] 	;addressability to Request Header structure

RHC	STRUC			;fields common to all request types
	   DB	?		;length of Request Header (including data)
	   DB	?		;unit code (subunit)
RHC_CMD    DB	?		;command code
RHC_STA    DW	?		;status
	   DQ	?		;reserved for DOS
RHC	ENDS			;end of common portion

CMD_INPUT  EQU	4		;RHC_CMD is INPUT request

;status values for RHC_STA

STAT_DONE   EQU 01H		;function complete status (high order byte)
STAT_CMDERR EQU 8003H		;invalid command code error
STAT_CRC    EQU 8004H		;CRC error
STAT_SNF    EQU 8008H		;sector not found error
STAT_BUSY   EQU 0200H		;busy bit (9) for Removable Media call
;-----------------------------------------------------------------------;
;	Request Header for INIT command 				;
;-----------------------------------------------------------------------;
RH0	STRUC
	   DB	(TYPE RHC) DUP (?)	;common portion
RH0_NUN    DB	?		;number of units
				;set to 1 if installation succeeds,
				;set to 0 to cause installation failure
RH0_ENDO   DW	?		;offset  of ending address
RH0_ENDS   DW	?		;segment of ending address
RH0_BPBO   DW	?		;offset  of BPB array address
RH0_BPBS   DW	?		;segment of BPB array address
RH0_DRIV   DB	?		;drive code (DOS 3 only)
RH0	ENDS

RH0_BPBA   EQU	DWORD PTR RH0_BPBO	;offset/segment of BPB array address
;Note: RH0_BPBA at entry to INIT points to all after DEVICE= on CONFIG.SYS stmt

;-----------------------------------------------------------------------;
;	Request Header for MEDIA CHECK Command				;
;-----------------------------------------------------------------------;
RH1	STRUC
	   DB	(TYPE RHC) DUP (?)	;common portion
	   DB	?		;media descriptor
RH1_RET    DB	?		;return information
RH1	ENDS
;-----------------------------------------------------------------------;
;	Request Header for BUILD BPB Command				;
;-----------------------------------------------------------------------;
RH2	STRUC
	   DB	(TYPE RHC) DUP(?)	;common portion
	   DB	?		;media descriptor
	   DW	?		;offset  of transfer address
	   DW	?		;segment of transfer address
RH2_BPBO   DW	?		;offset  of BPB table address
RH2_BPBS   DW	?		;segment of BPB table address
RH2	ENDS
;-----------------------------------------------------------------------;
;	Request Header for INPUT, OUTPUT, and OUTPUT with verify	;
;-----------------------------------------------------------------------;
RH4	STRUC
	   DB	(TYPE RHC) DUP (?)	;common portion
	   DB	?		;media descriptor
RH4_DTAO   DW	?		;offset  of transfer address
RH4_DTAS   DW	?		;segment of transfer address
RH4_CNT    DW	?		;sector count
RH4_SSN    DW	?		;starting sector number
RH4	ENDS

RH4_DTAA   EQU	DWORD PTR RH4_DTAO ;offset/segment of transfer address

;-----------------------------------------------------------------------;
;	Segment Descriptor (part of Global Descriptor Table)		;
;-----------------------------------------------------------------------;
DESC	STRUC			;data segment descriptor
DESC_LMT   DW	0		;segment limit (length)
DESC_BASEL DW	0		;bits 15-0 of physical address
DESC_BASEH DB	0		;bits 23-16 of physical address
	   DB	0		;access rights byte
	   DW	0		;reserved
DESC	ENDS

	SUBTTL	Equates and Macro Definitions
	PAGE

MEM_SIZE   EQU	12H		;BIOS memory size determination INT
				;returns system size in KB in AX

EM_INT	   EQU	15H		;extended memory BIOS interrupt INT
EM_BLKMOVE EQU	87H		;block move function
EM_MEMSIZE EQU	88H		;memory size determination in KB

BOOT_INT   EQU	19H		;bootstrap DOS

DOS	   EQU	21H		;DOS request INT
DOS_PCHR   EQU	02H		;print character function
DOS_PSTR   EQU	09H		;print string function
DOS_VERS   EQU	30H		;get DOS version

TAB	   EQU	09H		;ASCII tab
LF	   EQU	0AH		;ASCII line feed
CR	   EQU	0DH		;ASCII carriage return
BEL	   EQU	07H		;ASCII bell

PARA_SIZE  EQU	16		;number of bytes in one 8088 paragraph
DIR_ENTRY_SIZE EQU 32		;number of bytes per directory entry
MAX_FATE   EQU	0FE0H		;largest number of FAT entries allowed

;default values used if parameters are omitted

DFLT_BSIZE EQU	64		;default VDISK buffer size (KB)
DFLT_SSZ   EQU	128		;default sector size
DFLT_DIRN  EQU	64		;default number of directory entries
DFLT_ESS   EQU	8		;default maximum sectors to transfer

MIN_DIRN   EQU	2		;minimum number of directory entries
MAX_DIRN   EQU	512		;maximum number of directory entries

STACK_SIZE EQU	512		;length of stack during initialization

;-----------------------------------------------------------------------;
;	MSG invokes the console message subroutine			;
;-----------------------------------------------------------------------;

MSG	MACRO	TEXT
	PUSH	DX		;;save DX across call
	MOV	DX,OFFSET TEXT	;;point to message
	CALL	SHOW_MSG	;;issue message
	POP	DX
	ENDM


	SUBTTL	Resident Data Area
	PAGE
;-----------------------------------------------------------------------;
;	Map INT 19H vector in low storage				;
;-----------------------------------------------------------------------;
INT_VEC SEGMENT AT 00H
	ORG	4*BOOT_INT
BOOT_VEC   LABEL DWORD
BOOT_VECO  DW	?		;offset
BOOT_VECS  DW	?		;segment
INT_VEC ENDS


CSEG	SEGMENT PARA PUBLIC 'CODE'
	ASSUME	CS:CSEG
;-----------------------------------------------------------------------;
;	Resident data area.						;
;									;
;	All variables and constants required after initialization	;
;	part one are defined here.					;
;-----------------------------------------------------------------------;

START	   EQU	$		;begin resident VDISK data & code

;DEVICE HEADER - must be at offset zero within device driver
	   DD	-1		;becomes pointer to next device header
	   DW	0800H		;attribute (IBM format block device)
				;supports OPEN/CLOSE/RM calls
	   DW	OFFSET STRATEGY ;pointer to device "strategy" routine
	   DW	OFFSET IRPT	;pointer to device "interrupt handler"
	   DB	1		;number of block devices
	   DB	7 DUP (?)	;7 byte filler (remainder of 8-byte name)
;END OF DEVICE HEADER

;This volume label is placed into the directory of the new VDISK
;This constant is also used to determine if a previous extended memory VDISK
;has been installed.

VOL_LABEL  DB	'VDISK      '   ;00-10 volume name (shows program level)
	   DB	28H		;11-11 attribute (volume label)
	   DT	0		;12-21 reserved
	   DW	6000H		;22-23 time=12:00 noon
	   DW	0986H		;24-25 date=12/06/84
VOL_LABEL_LEN  EQU  $-VOL_LABEL ;length of volume label

;The following field, in the first extended memory VDISK device driver,
;is the 24-bit address of the first free byte of extended memory.
;This address is not in the common offset/segment format.
;The initial value, 10 0000H, is 1 megabyte.

AVAIL_LO   DW	0		;address of first free byte of
AVAIL_HI   DB	10H		;extended memory

;The INT 19H vector is "stolen" by the first VDISK installed in extended memory.
;The original content of the interrupt vector is saved here.

INTV19	   LABEL DWORD
INTV19O    DW	?		;offset
INTV19S    DW	?		;segment


PARAS_PER_SECTOR  DW	?	;number of 16-byte paragraphs in one sector

START_BUFFER_PARA DW	?	;segment address of start of VDISK buffer
				;for extended memory, this segment address
				;is the end of the VDISK device driver.

EM_SW	DB	0		;non-zero if Extended Memory

EM_STAT DW	0		;AX from last unsuccessful extended memory I/O

START_EM_LO DW	?		;24-bit address of start of VDISK buffer
START_EM_HI DB	?		;(extended memory only)

WPARA_SIZE DW	PARA_SIZE	;number of bytes in one paragraph

MAX_CNT    DW	?		;(0FFFFH/BPB_SSZ) truncated, the maximum
				;number of sectors that can be transferred
				;without worrying about 64KB wrap

SECT_LEFT  DW	?		;sectors left to transfer

IO_SRCA    LABEL DWORD		;offset/segment of source
IO_SRCO    DW	?		;offset
IO_SRCS    DW	?		;segment

IO_TGTA    LABEL DWORD		;offset/segment of target
IO_TGTO    DW	?		;offset
IO_TGTS    DW	?		;segment

;-----------------------------------------------------------------------;
;	BIOS Parameter Block (BPB)					;
;-----------------------------------------------------------------------;
;This is where the characteristics of the virtual disk are established.
;A copy of this block is moved into the boot record of the virtual disk.
;DEBUG can be used to read sector zero of the virtual disk to examine the
;boot record copy of this block.

BPB	 LABEL	BYTE		;BIOS Parameter Block (BPB)
BPB_SSZ    DW	0		;number of bytes per disk sector
BPB_AUSZ   DB	1		;sectors per allocation unit
BPB_RES    DW	1		;number of reserved sectors (for boot record)
BPB_FATN   DB	1		;number of File Allocation Table (FAT) copies
BPB_DIRN   DW	0		;number of root directory entries
BPB_SECN   DW	1		;total number of sectors
				;computed from buffer size and sector size
				;(this includes reserved, FAT, directory,
				;and data sectors)
BPB_MCB    DB	0FEH		;media descriptor byte
BPB_FATSZ  DW	1		;number of sectors occupied by a single FAT
				;computed from BPBSSZ and BPBSECN
BPB_LEN    EQU	$-BPB		;length of BIOS parameter block

BPB_PTR    DW	BPB		;BIOS Parameter Block pointer array (1 entry)
;-----------------------------------------------------------------------;
;	Request Header (RH) address, saved here by "strategy" routine   ;
;-----------------------------------------------------------------------;
RH_PTRA    LABEL DWORD
RH_PTRO    DW	?		;offset
RH_PTRS    DW	?		;segment
;-----------------------------------------------------------------------;
;	Global Descriptor Table (GDT), used for extended memory moves	;
;-----------------------------------------------------------------------;
;Access Rights Byte (93H) is
;	P=1	(segment is mapped into physical memory)
;	E=0	(data segment descriptor)
;	D=0	(grow up segment, offsets must be <= limit)
;	W=1	(data segment may be written into)
;	DPL=0	(privilege level 0)

GDT	LABEL	BYTE		;begin global descriptor table
	DESC	<>		;dummy descriptor
	DESC	<>		;descriptor for GDT itself
SRC	DESC	<,,,93H,>	;source descriptor
TGT	DESC	<,,,93H,>	;target descriptor
	DESC	<>		;BIOS CS descriptor
	DESC	<>		;stack segment descriptor

	SUBTTL	INT 19H (boot) interrupt handler
	PAGE
;-----------------------------------------------------------------------;
;	INT 19H Interrupt Handler routine				;
;-----------------------------------------------------------------------;
;The INT 19H vector is altered by VDISK initialization to point to this
;routine within the first extended memory VDISK device driver.

;The vector points to the device driver so that subsequent VDISKs installed
;in extended memory can find the first one to determine what memory has
;already been allocated to VDISKs.

;This routine restores the original INT 19H vector's content, then jumps
;to the original routine.

;INT 19H, the "Boot" INT, is always altered when DOS is booted.

;This routine is entered with interrupts disabled.

VDISK_INT19 PROC		;INT 19H received
	PUSH	DS		;save registers we're going to alter
	PUSH	AX

	XOR	AX,AX
	MOV	DS,AX		;set DS = 0
	ASSUME	DS:INT_VEC

	MOV	AX,CS:INTV19O	;get offset of saved vector
	MOV	DS:BOOT_VECO,AX ;store offset in interrupt vector

	MOV	AX,CS:INTV19S	;get segment of saved vector
	MOV	DS:BOOT_VECS,AX ;store segment in interrupt vector

	POP	AX
	POP	DS

	JMP	CS:INTV19	;go to original interrupt routine

VDISK_INT19 ENDP

	ASSUME	DS:NOTHING

	SUBTTL	Device Strategy & interrupt entry points
	PAGE
;-----------------------------------------------------------------------;
;	Device "strategy" entry point                                   ;
;									;
;	Retain the Request Header address for use by Interrupt routine	;
;-----------------------------------------------------------------------;
STRATEGY PROC	FAR
	MOV	CS:RH_PTRO,BX	;offset
	MOV	CS:RH_PTRS,ES	;segment
	RET
STRATEGY ENDP
;-----------------------------------------------------------------------;
;	Table of command processing routine entry points		;
;-----------------------------------------------------------------------;
CMD_TABLE LABEL WORD
	   DW	OFFSET INIT_P1		; 0 - Initialization
	   DW	OFFSET MEDIA_CHECK	; 1 - Media check
	   DW	OFFSET BLD_BPB		; 2 - Build BPB
	   DW	OFFSET INPUT_IOCTL	; 3 - IOCTL input
	   DW	OFFSET INPUT		; 4 - Input
	   DW	OFFSET INPUT_NOWAIT	; 5 - Non destructive input no wait
	   DW	OFFSET INPUT_STATUS	; 6 - Input status
	   DW	OFFSET INPUT_FLUSH	; 7 - Input flush
	   DW	OFFSET OUTPUT		; 8 - Output
	   DW	OFFSET OUTPUT_VERIFY	; 9 - Output with verify
	   DW	OFFSET OUTPUT_STATUS	;10 - Output status
	   DW	OFFSET OUTPUT_FLUSH	;11 - Output flush
	   DW	OFFSET OUTPUT_IOCTL	;12 - IOCTL output
	   DW	OFFSET DEVICE_OPEN	;13 - Device OPEN
	   DW	OFFSET DEVICE_CLOSE	;14 - Device CLOSE
MAX_CMD    EQU	($-CMD_TABLE)/2 	;highest valid command follows
	   DW	OFFSET REMOVABLE_MEDIA	;15 - Removable media

;-----------------------------------------------------------------------;
;	Device "interrupt" entry point                                  ;
;-----------------------------------------------------------------------;
IRPT	PROC	FAR		;device interrupt entry point
	PUSH	DS		;save all registers Revised
	PUSH	ES
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	DI
	PUSH	SI
				;BP isn't used, so it isn't saved
	CLD			;all moves forward

	LDS	BX,CS:RH_PTRA	;get RH address passed to "strategy" into DS:BX

	MOV	AL,RH.RHC_CMD	;command code from Request Header
	CBW			;zero AH (if AL > 7FH, next compare will
				;catch that error)

	CMP	AL,MAX_CMD	;if command code is too high
	JA	IRPT_CMD_HIGH	;jump to error routine

	MOV	DI,OFFSET IRPT_CMD_EXIT ;return addr from command processor
	PUSH	DI		;push return address onto stack
				;command routine issues "RET"

	ADD	AX,AX		;double command code for table offset
	MOV	DI,AX		;put into index register for JMP

	XOR	AX,AX		;initialize return to "no error"

;At entry to command processing routine:

;	DS:BX	= Request Header address
;	CS	= VDISK code segment address
;	AX	= 0

;	top of stack is return address, IRPT_CMD_EXIT

	JMP	CS:CMD_TABLE[DI]	;call routine to handle the command


IRPT_CMD_ERROR: 		;CALLed for unsupported character mode commands

INPUT_IOCTL:			;IOCTL input
INPUT_NOWAIT:			;Non-destructive input no wait
INPUT_STATUS:			;Input status
INPUT_FLUSH:			;Input flush

OUTPUT_IOCTL:			;IOCTL output
OUTPUT_STATUS:			;Output status
OUTPUT_FLUSH:			;Output flush

	POP	AX		;pop return address off stack

IRPT_CMD_HIGH:			;JMPed to if RHC_CMD > MAX_CMD
	MOV	AX,STAT_CMDERR	;"invalid command" and error

IRPT_CMD_EXIT:			;return from command routine
				;AX = value to OR into status word
	LDS	BX,CS:RH_PTRA	;restore DS:BX as Request Header pointer
	OR	AH,STAT_DONE	;add "done" bit to status word
	MOV	RH.RHC_STA,AX	;store status into request header
	POP	SI		;restore registers
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	POP	ES
	POP	DS
	RET
IRPT	ENDP

	SUBTTL	Command Processing routines
	PAGE
;-----------------------------------------------------------------------;
;	Command Code 1 - Media Check					;
;	At entry, DS:BX point to request header, AX = 0 		;
;-----------------------------------------------------------------------;
MEDIA_CHECK PROC
	MOV	RH.RH1_RET,1	;indicate media not changed
	RET			;AX = zero, no error
MEDIA_CHECK ENDP
;-----------------------------------------------------------------------;
;	Command Code 2 - Build BPB					;
;	At entry, DS:BX point to request header, AX = 0 		;
;-----------------------------------------------------------------------;
BLD_BPB PROC
	MOV	RH.RH2_BPBO,OFFSET BPB	;return pointer to our BPB
	MOV	RH.RH2_BPBS,CS
	RET				;AX = zero, no error
BLD_BPB ENDP
;-----------------------------------------------------------------------;
;	Command Code 13 - Device Open					;
;	Command Code 14 - Device Close					;
;	Command Code 15 - Removable media				;
;	At entry, DS:BX point to request header, AX = 0 		;
;-----------------------------------------------------------------------;
REMOVABLE_MEDIA PROC
	MOV	AX,STAT_BUSY		;set status bit 9 (busy)
					;indicating non-removable media
DEVICE_OPEN:				;NOP for device open
DEVICE_CLOSE:				;NOP for device close
	RET
REMOVABLE_MEDIA ENDP			;fall thru to return
;-----------------------------------------------------------------------;
;	Command Code 4 - Input						;
;	Command Code 8 - Output 					;
;	Command Code 9 - Output with verify				;
;	At entry, DS:BX point to request header, AX = 0 		;
;-----------------------------------------------------------------------;
INOUT	PROC
INPUT:
OUTPUT:
OUTPUT_VERIFY:

;Make sure I/O is entirely within the VDISK sector boundaries

	MOV	CX,CS:BPB_SECN		;get total sector count
	MOV	AX,RH.RH4_SSN		;starting sector number
	CMP	AX,CX			;can't exceed total count
	JA	INOUT_E1		;jump if start > total

	ADD	AX,RH.RH4_CNT		;start + sector count
	CMP	AX,CX			;can't exceed total count
	JNA	INOUT_A 		;jump if start + count <= total

INOUT_E1:				;I/O not within VDISK sector boundaries
	MOV	RH.RH4_CNT,0		;set sectors transferred to zero
	MOV	AX,STAT_SNF		;indicate 'Sector not found' error
	RET				;return with error status in AX

INOUT_A:				;I/O within VDISK bounds
	MOV	AX,RH.RH4_CNT		;get sector count
	MOV	CS:SECT_LEFT,AX 	;save as sectors left to process

	CMP	CS:EM_SW,0		;extended memory mode?
	JNE	INOUT_EM		;jump to extended memory I/O code

;Compute offset and segment of VDISK buffer for starting segment in CX:SI

	MOV	AX,RH.RH4_SSN		;starting sector number
	MUL	CS:PARAS_PER_SECTOR	;* length of one sector in paragraphs
	ADD	AX,CS:START_BUFFER_PARA ;+ segment of VDISK buffer sector 0
	MOV	CX,AX			;segment address to CX
	XOR	SI,SI			;offset is zero

;Compute address of caller's Data Transfer Addr in DX:AX with smallest offset,
;so that there is no possibility of overflowing a 64KB boundary moving MAX_CNT
;sectors.

	MOV	AX,PARA_SIZE		;16
	MUL	RH.RH4_DTAS		;* segment of caller's DTA in DX,AX
	ADD	AX,RH.RH4_DTAO		;+ offset of caller's DTA
	ADC	DL,0			;carry in from addition
	DIV	CS:WPARA_SIZE		;AX is segment of caller's DTA
					;DX is smallest offset possible
					;AX:DX = DTA address

;AX:DX is caller's DTA segment:offset, CX:SI is VDISK buffer segment:offset

;If this is an OUTPUT request, exchange the source and target addresses

	CMP	RH.RHC_CMD,CMD_INPUT	;INPUT operation?
	JE	INOUT_B 		;jump if INPUT operation

	XCHG	AX,CX			;swap source and target segment
	XCHG	DX,SI			;swap source and target offset

INOUT_B:				;CX:SI is source, AX:DX is target
	MOV	CS:IO_SRCS,CX		;save source segment
	MOV	CS:IO_SRCO,SI		;save source offset
	MOV	CS:IO_TGTS,AX		;save target segment
	MOV	CS:IO_TGTO,DX		;save target offset

	JMP	SHORT INOUT_E		;AX := SECT_LEFT, test for zero
INOUT_C:				;SECT_LEFT in AX, non-zero

;  Compute number of sectors to transfer in a single move,
;  AX = minimum of (SECT_LEFT, MAX_CNT)

;  MAX_CNT is the maximum number of sectors that can be moved without
;  spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)

	MOV	CX,CS:MAX_CNT		;MAX sectors with one move
	CMP	AX,CX			;if SECT_LEFT cannot span 64KB boundary
	JBE	INOUT_D 		;then move SECT_LEFT sectors

	MOV	AX,CX			;else move MAX_CNT sectors
INOUT_D:
	SUB	CS:SECT_LEFT,AX 	;reduce number of sectors left to move

;Move AX sectors from source to target

	MUL	CS:BPB_SSZ		;sectors * sector size = byte count
					;(cannot overflow into DX)
	SHR	AX,1			;/2 = word count
	MOV	CX,AX			;word count to CX for REP MOVSW

	LDS	SI,CS:IO_SRCA		;source segment/offset to DS:SI
	LES	DI,CS:IO_TGTA		;target segment/offset to ES:DI

	REP	MOVSW			;move MOV_CNT sectors

;Update source and target paragraph addresses
;AX has number of words moved

	SHR	AX,1			;words moved / 8 = paragraphs moved
	SHR	AX,1
	SHR	AX,1

	ADD	CS:IO_SRCS,AX		;add paragraphs moved to source segment
	ADD	CS:IO_TGTS,AX		;add paragraphs moved to target segment

;Determine if more sectors need to be transferred

INOUT_E:				;do while SECT_LEFT <> zero
	MOV	AX,CS:SECT_LEFT 	;get sectors left to transfer
	OR	AX,AX			;set flags
	JNZ	INOUT_C 		;go back to transfer some sectors
	RET				;AX = zero, all sectors transferred

	SUBTTL	Extended Memory I/O routine
	PAGE
;-----------------------------------------------------------------------;
;	Extended Memory I/O routine					;
;-----------------------------------------------------------------------;
INOUT_EM:				;Extended memory I/O routine
					;change to larger stack
	MOV	SI,SS			;save old SS in SI
	MOV	DX,SP			;save old SP in DX
	CLI				;disable interrupts
	MOV	AX,CS
	MOV	SS,AX			;set SS = CS
	MOV	SP,OFFSET EM_STACK	;point to new stack
	STI				;enable interrupts
	PUSH	SI			;save old SS at top of new stack
	PUSH	DX			;save old SP on new stack

	MOV	SI,RH.RH4_DTAO		;caller's DTA offset

;Compute 24-bit address of VDISK sector in CX (hi) and SI (low)

	MOV	AX,RH.RH4_SSN		;starting sector number
	MUL	CS:BPB_SSZ		;* sector size = offset within buffer
	ADD	AX,CS:START_EM_LO	;+ base address of this VDISK buffer
	ADC	DL,CS:START_EM_HI
	MOV	CX,DX			;save high byte
	MOV	SI,AX			;save low word

;Compute 24-bit address of caller's DTA in DX (hi) and AX (low)

	MOV	AX,PARA_SIZE		;16
	MUL	RH.RH4_DTAS		;* segment of caller's DTA
	ADD	AX,RH.RH4_DTAO		;+ offset of caller's DTA
	ADC	DL,0			;carry in from addition

;Caller's DTA address is in CX,SI, VDISK buffer address is in DX,AX.

;If this is an OUTPUT request, exchange the source and target addresses

	CMP	RH.RHC_CMD,CMD_INPUT	;INPUT operation?
	JE	INOUT_EM_B		;jump if INPUT operation

	XCHG	DX,CX			;swap source and target high byte
	XCHG	AX,SI			;swap source and target low word

INOUT_EM_B:				;CX,SI is source, DX,AX is target

	MOV	SRC.DESC_BASEL,SI	;low 16 bits of source address
	MOV	SRC.DESC_BASEH,CL	;high 8 bits of source address

	MOV	TGT.DESC_BASEL,AX	;low 16 bits of target address
	MOV	TGT.DESC_BASEH,DL	;high 8 bits of target address

	JMP	SHORT INOUT_EM_E	;AX := SECT_LEFT, test for zero
INOUT_EM_C:				;SECT_LEFT in AX, non-zero

;  Compute number of sectors to transfer in a single move,
;  AX = minimum of (SECT_LEFT, MAX_CNT)

;  MAX_CNT is the maximum number of sectors that can be moved without
;  spanning a 64KB boundary (0FFFFH / Sector size, remainder truncated)

	MOV	CX,CS:MAX_CNT		;MAX sectors with one move
	CMP	AX,CX			;if SECT_LEFT cannot span 64KB boundary
	JBE	INOUT_EM_D		;then move SECT_LEFT sectors

	MOV	AX,CX			;else move MAX_CNT sectors
INOUT_EM_D:
	SUB	CS:SECT_LEFT,AX 	;reduce number of sectors left to move

;Move AX sectors from source to target

	MUL	CS:BPB_SSZ		;sectors * sector size = byte count
					;(cannot overflow into DX)
	MOV	TGT.DESC_LMT,AX 	;store segment limit (byte count)
	MOV	SRC.DESC_LMT,AX

	PUSH	AX			;preserve byte count on stack

	SHR	AX,1			;/2 = word count
	MOV	CX,AX			;word count to CX

	PUSH	CS
	POP	ES			;set ES = CS
	MOV	SI,OFFSET GDT		;ES:SI point to GDT

	MOV	AH,EM_BLKMOVE		;function is block move
	INT	EM_INT			;move an even number of words

	POP	CX			;get byte count back from stack

	OR	AH,AH			;get error code

	JNZ	INOUT_EM_XE		;jump if I/O error encountered

;Update source and target addresses

	ADD	SRC.DESC_BASEL,CX	;add bytes moved to source
	ADC	SRC.DESC_BASEH,0	;pick up any carry

	ADD	TGT.DESC_BASEL,CX	;add bytes moved to target
	ADC	TGT.DESC_BASEH,0	;pick up any carry

;Determine if more sectors need to be transferred

INOUT_EM_E:				;do while SECT_LEFT <> zero
	MOV	AX,CS:SECT_LEFT 	;get sectors left to transfer
	OR	AX,AX			;set flags
	JNZ	INOUT_EM_C		;go back to transfer some sectors

INOUT_EM_X2:				;revert to original stack
	POP	DI			;get old SP
	POP	SI			;get old SS
	CLI				;disable interrupts
	MOV	SS,SI			;restore old SS
	MOV	SP,DI			;restore old SP
	STI				;enable interrupts
	RET				;return to IRPT_EXIT

INOUT_EM_XE:				;some error with INT 15H
	MOV	CS:EM_STAT,AX		;save error status for debugging
	MOV	RH.RH4_CNT,0		;indicate no sectors transferred
	MOV	AX,STAT_CRC		;indicate CRC error
	JMP	INOUT_EM_X2		;fix stack and exit
INOUT	ENDP

	DW	40 DUP (?)		;stack for extended memory I/O
EM_STACK LABEL	WORD

	SUBTTL	Boot Record
	PAGE
;-----------------------------------------------------------------------;
;	Adjust the assembly-time instruction counter to a paragraph	;
;	boundary							;
;-----------------------------------------------------------------------;

	IF	($-START) MOD 16
	ORG	($-START) + 16 - (($-START) MOD 16)
	ENDIF

VDISK	   EQU	$			;start of virtual disk buffer
VDISKP	   EQU	($-START) / PARA_SIZE	;length of program in paragraphs
;-----------------------------------------------------------------------;
;	If this VDISK is in extended memory, this address is passed	;
;	back to DOS as the end address that is to remain resident.	;
;									;
;	It this VDISK is not in extended memory, the VDISK buffer	;
;	begins at this address, and the address passed back to DOS	;
;	as the end address that is to remain resident is this address	;
;	plus the length of the VDISK buffer.				;
;-----------------------------------------------------------------------;

BOOT_RECORD LABEL BYTE		;Format of Boot Record documented in
				;DOS Technical Reference Manual
	   DB	0,0,0		;3-byte jump to boot code (not bootable)
	   DB	'VDISK   '      ;8-byte vendor identification
BOOT_BPB LABEL	BYTE		;boot record copy of BIOS parameter block
	   DW	?		;number of bytes per disk sector
	   DB	?		;sectors per allocation unit
	   DW	?		;number of reserved sectors (for boot record)
	   DB	?		;number of File Allocation Table (FAT) copies
	   DW	?		;number of root directory entries
	   DW	?		;total number of sectors
	   DB	?		;media descriptor byte
	   DW	?		;number of sectors occupied by a single FAT
;end of boot record BIOS Parameter block

;The following three words mean nothing to VDISK, they are placed here
;to conform to the DOS standard for boot records.
	   DW	8		;sectors per track
	   DW	1		;number of heads
	   DW	0		;number of hidden sectors
;The following word is the 16-bit kilobyte address of the first byte in
;extended memory that is not occupied by a VDISK buffer
;It is placed into this location so that other users of extended memory
;may find where all the VDISKs end.

;This field may be accessed by moving the boot record of the First extended
;memory VDISK from absolute location 10 0000H.	Before assuming that the
;value below is valid, the vendor ID (constant VDISK) should be verified
;to make sure that SOME VDISK has been installed.

;For example, if two VDISKs are installed, one 320KB and one 64KB, the
;address calculations are as follows:

;Extended memory start address	= 100000H (1024KB)
;Start addr of 1st VDISK buffer = 100000H (1024KB)
;Length of 1st VDISK buffer	= 050000H ( 320KB)
;End addr of 1st VDISK buffer	= 14FFFFH
;Start addr of 2nd VDISK buffer = 150000H (1344KB)
;Length of 2nd VDISK buffer	= 010000H (  64KB)
;End addr of 2nd VDISK buffer	= 15FFFFH
;First byte after all VDISKs	= 160000H (1408KB)
;Divide by 1024 		=   0580H (1408D)

;Content of BOOT_EM		=   0580H

BOOT_EM_OFF EQU $-BOOT_RECORD	;offset from 10 0000H of the following word
BOOT_EM    DW	1024		;KB addr of first free byte of extended memory
;-----------------------------------------------------------------------;
;	Part 2 of Initialization (executed last)			;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.

;INIT_P1 is overlaid by the virtual disk buffer

;INIT_P1 is executed first, then jumps to INIT_P2.  INIT_P2 returns to caller.

;Exercise caution if extending the initialization part 2 code.
;It overlays the area immediately following the boot sector.
;If this section of code must be expanded, make sure it fits into the minimum
;sector size of 128 bytes.
;Label TEST_LENGTH must equate to a non-negative value (TEST_LENGTH >= 0).
;If this code it must be extended beyond the 128 byte length of the boot sector,
;move all of INIT_P2 before label VDISK.

;Registers at entry to INIT_P2 (set up at end of INIT_P1):
;	BL = media control byte from BPB (for FAT)
;	CX = number of FAT copies
;	DX = number of bytes in one FAT - 3
;	SI = OFFSET of Volume Label field
;	ES:DI = VDISK buffer address of first FAT sector
;	CS = DS = VDISK code segment

INIT_P2 PROC				;second part of initialization
	ASSUME	DS:CSEG 		;DS set in INIT_P1

;Initialize File Allocation Table(s) (FATs)

INIT_P2_FAT:				;set up one FAT, sector number in AX

	PUSH	CX			;save loop counter on stack
	MOV	AL,BL			;media control byte
	STOSB				;store media control byte, increment DI
	MOV	AX,0FFFFH		;bytes 2 and 3 of FAT are 0FFH
	STOSW

	MOV	CX,DX			;FAT size in bytes - 3
	XOR	AX,AX			;value to store in remainder of FAT
	REP	STOSB			;clear remainder of FAT

	POP	CX			;get loop counter off stack
	LOOP	INIT_P2_FAT		;loop for all copies of the FAT

;Put the volume label in the first directory entry

	MOV	CX,VOL_LABEL_LEN	;length of volume directory entry
	REP	MOVSB			;move volume id to directory

;Zero the remainder of the directory

	MOV	AX,DIR_ENTRY_SIZE	;length of 1 directory entry
	MUL	BPB_DIRN		;* number entries = bytes of directory
	SUB	AX,VOL_LABEL_LEN	;less length of volume label
	MOV	CX,AX			;length of rest of directory
	XOR	AX,AX
	REP	STOSB			;clear directory to nulls
	RET				;return with AX=0
INIT_P2 ENDP

PATCH_AREA DB	5 DUP ('PATCH AREA ')
TEST_LENGTH EQU 128-($-VDISK)		;if negative, boot record has too much
					;data area, move some fields below VDISK
;-----------------------------------------------------------------------;
;	All fields that must remain resident after device driver	;
;	initialization must be defined before this point.		;
;-----------------------------------------------------------------------;
	   DB	'MS DOS Version 4.00 - Virtual Disk Device Driver'
	   DB	'-------- Licensed Material ---------'
	   DB	'Program Property of Microsoft Corporation.   '
	   DB	'(C)Copyright 1988 Microsoft'
	   DB	'Thank You For Your '
	   DB	'    Support    '

MAXSEC_TRF DW	0		;maximum number of sectors to transfer when
				;in extended memory

BUFF_SIZE  DW	0		;desired VDISK buffer size in kilobytes

MIN_MEMORY_LEFT DW	64	;minimum amount of system memory (kilobytes)
				;that must remain after VDISK is installed

FIRST_EM_SW DB	?		;0FFH if this is the first device driver
				;to be installed in extended memory
				;00H if another VDISK extended memory driver
				;has been installed

FIRST_VDISK DW	?		;segment address of 1st VDISK device driver
PARA_PER_KB DW	1024/PARA_SIZE	;paragraphs in one kilobyte
C1024	   DW	1024		;bytes in one kilobyte
DIRE_SIZE  DW	DIR_ENTRY_SIZE	;bytes in one directory entry
DIR_SECTORS DW	?		;number of sectors of directory

ERR_FLAG   DB	0		;error indicators to condition messages
ERR_BSIZE  EQU	80H		;buffer size adjusted
ERR_SSZ    EQU	40H		;sector size adjusted
ERR_DIRN   EQU	20H		;number of directory entries adjusted
ERR_PASS   EQU	10H		;some adjustment made that requires
				;recomputation of values previously computed
ERR_SSZB   EQU	ERR_SSZ+ERR_PASS	;sector size altered this pass
ERR_SYSSZ  EQU	08H		;system storage too small for VDISK
ERR_SWTCH  EQU	04H		;invalid switch character
ERR_EXTSW  EQU	02H		;extender card switches don't match memory size
ERR_ESIZE  EQU	01H		;Transfer size adjusted

; additional errors added - kwc

major_version	    equ     4	;Major DOS version
minor_version	    equ     00	;Minor DOS Version

expected_version    equ     (MINOR_VERSION SHL 8)+MAJOR_VERSION

err_flag2	    db	    0
err_baddos	    equ     01h ; Invalid DOS Version

	SUBTTL	Initialization, Part one
	PAGE
;-----------------------------------------------------------------------;
;	Command Code 0 - Initialization 				;
;	At entry, DS:BX point to request header, AX = 0 		;
;-----------------------------------------------------------------------;
;Initialization is divided into two parts.
;This part, executed first, is later overlaid by the VDISK buffer.

INIT_P1 PROC			;first part of initialization
	MOV	DX,SS		;save stack segment register
	MOV	CX,SP		;save stack pointer register
	CLI			;inhibit interrupts while changing SS:SP
	MOV	AX,CS		;move CS to SS through AX
	MOV	SS,AX
	MOV	SP,OFFSET MSGEND ;end of VDISKMSG
	ADD	SP,STACK_SIZE	;+ length of our stack
	STI			;allow interrupts
	PUSH	DX		;save old SS register on new stack
	PUSH	CX		;save old SP register on new stack

	push bx 		;secure registers before DOS int
	push cx 		;secure registers before DOS int

; add version check - kwc

	mov	ah,030h
	int	21h
	pop	cx	   ;restore pointer values
	pop	bx	   ;restore pointer values
	cmp	ax,expected_version
	je	okdos

	or	cs:err_flag2,err_baddos

okdos:
	CALL	GET_PARMS	;get parameters from CONFIG.SYS line

	PUSH	CS
	POP	DS		;set DS = CS
	ASSUME	DS:CSEG

	CALL	APPLY_DEFAULTS	;supply any values not specified
	CALL	DETERMINE_START ;compute start address of VDISK buffer
	CALL	VALIDATE	;validate parameters
	CALL	COPY_BPB	;Copy BIOS Parameter Block to boot record

	CALL	VERIFY_EXTENDER ;Verify that extender card switches are right

	TEST	ERR_FLAG,ERR_EXTSW	;are switches wrong?
	JNZ	INIT_P1_A	;if so, exit with messages

	test	CS:err_flag2,err_baddos
	jnz	init_p1_a

	CMP	EM_SW,0 	;extended memory requested?
	JE	INIT_P1_A	;jump if not

	TEST	ERR_FLAG,ERR_SYSSZ	;is system too small for VDISK?
	JNZ	INIT_P1_A	;if so, don't do extended memory init

	CALL	UPDATE_AVAIL	;update AVAIL_HI and AVAIL_LO to reflect
				;addition of extended memory VDISK
	CALL	FORMAT_VDISK	;construct a boot record, FATs and
				;directory in storage immediately
				;following this device driver
	CALL	MOVE_VDISK	;move formatted boot record, FATs,
				;and directory to extended memory
	CALL	UPDATE_BOOT	;place the end address of ALL VDISKs
				;in the boot record of the first VDISK
	CMP	FIRST_EM_SW,0	;is this the first extended memory VDISK?
	JE	INIT_P1_A	;no, exit

	CALL	STEAL_INT19	;point INT 19H to this VDISK
INIT_P1_A:
	CALL	FILL_RH 	;fill in INIT request header
	CALL	WRITE_MESSAGES	;display all messages
	POP	CX		;get old SP from stack
	POP	DX		;get old SS from stack
	CLI			;disable interrupts while changing SS:SP
	MOV	SS,DX		;restore stack segment register
	MOV	SP,CX		;restore stack pointer register
	STI			;enable interrupts
;-----------------------------------------------------------------------;
;	INIT_P2 must be short enough to fit into the boot sector	;
;	(minimum size of boot sector is 128 bytes), so we set up	;
;	as many pointers as we can to help keep INIT_P2 short.		;
;									;
;	ES:DI = storage address of first FAT sector			;
;	BL = media control byte 					;
;	CX = number of FAT copies					;
;	DX = number of bytes in one FAT, less 3 			;
;	SI = offset of VOL label field					;
;-----------------------------------------------------------------------;
	MOV	ES,START_BUFFER_PARA	;start paragraph of VDISK buffer

	MOV	AX,BPB_RES		;number of reserved sectors
	MUL	BPB_SSZ 		;* sector size
	MOV	DI,AX			;ES:DI point to FAT start

	MOV	BL,BPB_MCB		;media control byte

	MOV	CL,BPB_FATN		;number of FAT copies
	XOR	CH,CH

	MOV	AX,BPB_FATSZ		;FAT size in sectors
	MUL	BPB_SSZ 		;* sector size = total FAT bytes

	SUB	AX,3			;-3 (FEFFFF stored by code)
	MOV	DX,AX

	MOV	SI,OFFSET VOL_LABEL	;point to VOL label directory entry
	JMP	INIT_P2 		;jump to second part of initialization
					;this is redundant if the VDISK is in
					;extended memory, but is executed anyway

	SUBTTL	GET_PARMS Parameter Line Scan
	PAGE
;-----------------------------------------------------------------------;
;GET_PARMS gets the parameters from the CONFIG.SYS statement		;
;									;
;Register usage:							;
;	DS:SI indexes parameter string					;
;	AL contains character from parameter string			;
;	CX value from GET_NUMBER					;
;-----------------------------------------------------------------------;
	ASSUME	DS:NOTHING	;DS:BX point to Request Header
GET_PARMS PROC			;get parameters from CONFIG.SYS line
	PUSH	DS		;save DS
	LDS	SI,RH.RH0_BPBA	;DS:SI point to all after DEVICE=
				;in CONFIG.SYS line
	XOR	AL,AL		;not at end of line

;Skip until first delimiter is found.  There may be digits in the path string.

;DS:SI points to  \pathstring\VDISK.SYS nn nn nn
;The character following VDISK.SYS may have been changed to a null (00H).
;All letters have been changed to uppercase.

GET_PARMS_A:			;skip to DOS delimiter character
	CALL	GET_PCHAR	;get parameter character into AL
	JZ	GET_PARMS_X	;get out if end of line encountered
	OR	AL,AL		;test for null
	JZ	GET_PARMS_C	;
	CMP	AL,' '
	JE	GET_PARMS_C	;
	CMP	AL,','
	JE	GET_PARMS_C	;
	CMP	AL,';'
	JE	GET_PARMS_C	;
	CMP	AL,'+'
	JE	GET_PARMS_C	;
	CMP	AL,'='
	JE	GET_PARMS_C	;
	CMP	AL,TAB
	JNE	GET_PARMS_A	;skip until delimiter or CR



GET_PARMS_C:
	PUSH	SI		;save to rescan
	MOV	CS:EM_SW,0	;indicate no /E found
	JMP	GET_SLASH	;see if current character is an slash

GET_PARMS_D:			;scan for /
	CALL	GET_PCHAR
	JZ	GET_PARMS_B	;exit if end of line

GET_SLASH:			;check for slash
	CMP	AL,'/'          ;found slash?
	JNE	GET_PARMS_D	;no, continue scan

	CALL	GET_PCHAR	;get char following slash
	CMP	AL,'E'          ;don't have to test for lower case E,
				;letters have been changed to upper case
	JNE	GET_PARMS_E	;not 'E'
	MOV	CS:EM_SW,AL	;indicate /E found

	CALL	GET_PCHAR	;get char following E
	CMP	AL,':'          ;is it a delimeter ?
	JNE	GET_PARMS_D	;not a ':'


	CALL	GET_MAXSIZE	;get maximum sector size


	JMP	GET_PARMS_D	;continue forward scan

GET_PARMS_E:			;/ found, not 'E'
	OR	CS:ERR_FLAG,ERR_SWTCH	;indicate invalid switch character
	JMP	GET_PARMS_D	;continue scan



GET_PARMS_B:			;now pointing to first delimiter
	POP	SI		;get pointer, used to rescan for /E
	XOR	AL,AL		;not at EOL now
	CALL	GET_PCHAR	;get first character
	CALL	SKIP_TO_DIGIT	;skip to first digit
	JZ	GET_PARMS_X	;found EOL, no digits remain

	CALL	GET_NUMBER	;extract digits, convert to binary
	MOV	CS:BUFF_SIZE,CX ;store buffer size

	CALL	SKIP_TO_DIGIT	;skip to next digit
	JZ	GET_PARMS_X	;found EOL, no digits remain

	CALL	GET_NUMBER	;extract digits, convert to binary
	MOV	CS:BPB_SSZ,CX	;store sector size

	CALL	SKIP_TO_DIGIT	;skip to next digit
	JZ	GET_PARMS_X	;found EOL, no digits remain

	CALL	GET_NUMBER	;extract digits, convert to binary
	MOV	CS:BPB_DIRN,CX	;store number of directory entries



GET_PARMS_X:			;premature end of line
	POP	DS		;restore DS
	RET



GET_MAXSIZE PROC		;get maximum sector size

	CALL	GET_PCHAR	;get next character
	CALL	CHECK_NUM	;is it a number ?
	JZ	GET_NEXTNUM	;yes, go get next number
	OR	CS:ERR_FLAG,ERR_ESIZE	;indicate invalid sector size
	RET			;
GET_NEXTNUM:			;get next number
	CALL GET_NUMBER 	;extract digits and convert to binary
	MOV CS:MAXSEC_TRF,CX	;save maximum sector size to transfer
	RET
GET_MAXSIZE ENDP



GET_PCHAR PROC			;internal proc to get next character into AL
	CMP	AL,CR		;carriage return already encountered?
	JE	GET_PCHAR_X	;don't read past end of line
	CMP	AL,LF		;line feed already encountered?
	JE	GET_PCHAR_X	;don't read past end of line
	LODSB			;get char from DS:SI, increment SI
	CMP	AL,CR		;is the char a carriage return?
	JE	GET_PCHAR_X	;yes, set Z flag at end of line
	CMP	AL,LF		;no, is it a line feed?
GET_PCHAR_X:			;attempted read past end of line
	RET
GET_PCHAR ENDP			;returns char in AL


CHECK_NUM PROC			;check AL for ASCII digit
	CMP	AL,'0'          ;< '0'?
	JB	CHECK_NUM_X	;exit if it is

	CMP	AL,'9'          ;> '9'?
	JA	CHECK_NUM_X	;exit if it is

	CMP	AL,AL		;set Z flag to indicate numeric
CHECK_NUM_X:
	RET			;Z set if numeric, NZ if not numeric
CHECK_NUM ENDP


SKIP_TO_DIGIT PROC		;skip to first numeric character
	CALL	CHECK_NUM	;is current char a digit?
	JZ	SKIP_TO_DIGIT_X ;if so, skip is complete

	CALL	GET_PCHAR	;get next character from line
	JNZ	SKIP_TO_DIGIT	;loop until first digit or CR or LF
	RET			;character is CR or LF

SKIP_TO_DIGIT_X:
	CMP	AL,0		;digit found, force NZ
	RET
SKIP_TO_DIGIT ENDP

C10	   DW	10
GN_ERR	   DB	?		;zero if no overflow in accumulation

GET_NUMBER PROC 		;convert string of digits to binary value
	XOR	CX,CX		;accumulate number in CX
	MOV	CS:GN_ERR,CL	;no overflow yet
GET_NUMBER_A:			;accumulate next digit
	SUB	AL,'0'          ;convert ASCII to binary
	CBW			;clear AH
	XCHG	AX,CX		;previous accumulation in AX, new digit in CL
	MUL	CS:C10		;DX:AX := AX*10
	OR	CS:GN_ERR,DL	;set GN_ERR <> 0 if overflow
	ADD	AX,CX		;add new digit from
	XCHG	AX,CX		;number now in CX
	DEC	SI		;back up to prior entry
	MOV	AL,' '          ;blank out prior entry
	MOV	[SI],AL 	;
	INC	SI		;set to current entry
	CALL	GET_PCHAR	;get next character
	CALL	CHECK_NUM	;see if it was numeric
	JZ	GET_NUMBER_A	;continue accumulating
	CMP	CS:GN_ERR,0	;did we overflow?
	JE	GET_NUMBER_B	;if not, we're done
	XOR	CX,CX		;return zero (always invalid) if overflow
GET_NUMBER_B:
	RET			;number in CX, next char in AL
GET_NUMBER ENDP

GET_PARMS ENDP

	SUBTTL	APPLY_DEFAULTS
	PAGE
;-----------------------------------------------------------------------;
;	APPLY_DEFAULTS supplies any parameter values that the user	;
;	failed to specify						;
;-----------------------------------------------------------------------;
	ASSUME	DS:CSEG
APPLY_DEFAULTS	PROC
	XOR	AX,AX
	CMP	BUFF_SIZE,AX		;is buffer size zero?
	JNE	APPLY_DEFAULTS_A	;no, user specified something

	MOV	BUFF_SIZE,DFLT_BSIZE	;supply default buffer size
	OR	ERR_FLAG,ERR_BSIZE	;indicate buffersize adjusted

APPLY_DEFAULTS_A:
	CMP	BPB_SSZ,AX		;is sector size zero?
	JNE	APPLY_DEFAULTS_B	;no, user specified something

	MOV	BPB_SSZ,DFLT_SSZ	;supply default sector size
	OR	ERR_FLAG,ERR_SSZ	;indicate sector size adjusted

APPLY_DEFAULTS_B:
	CMP	BPB_DIRN,AX		;are directory entries zero?
	JNE	APPLY_DEFAULTS_C	;no, user specified something

	MOV	BPB_DIRN,DFLT_DIRN	;supply default directory entries
	OR	ERR_FLAG,ERR_DIRN	;indicate directory entries adjusted

APPLY_DEFAULTS_C:			;
	CMP	EM_SW,0 		;extended memory ?
	JE	APPLY_DEFAULTS_D	;no, jump around
	CMP	MAXSEC_TRF,AX		;is maximum sectors zero?
	JNE	APPLY_DEFAULTS_D	;no, user specified something

	MOV	MAXSEC_TRF,DFLT_ESS	;supply default maximum number of
					;sector to transfer
	OR	ERR_FLAG,ERR_ESIZE	;indicate transfer size adjusted
APPLY_DEFAULTS_D:
	RET
APPLY_DEFAULTS	ENDP

	SUBTTL	DETERMINE_START address of VDISK buffer
	PAGE
;-----------------------------------------------------------------------;
;	DETERMINE_START figures out the starting address of the VDISK	;
;	buffer								;
;-----------------------------------------------------------------------;
	ASSUME	DS:CSEG
DETERMINE_START PROC

;If extended memory is NOT being used, the VDISK buffer immediately
;follows the resident code.

;If extended memory IS being used, START_BUFFER_PARA becomes the
;end of device driver address passed back to DOS.

	MOV	AX,CS			;start para of VDISK code
	ADD	AX,VDISKP		;+ length of resident code
	MOV	START_BUFFER_PARA,AX	;save as buffer start para

	CMP	EM_SW,0 		;is extended memory requested?
	JE	DETERMINE_START_X	;if not, we're done here

;If this is the first extended memory VDISK device driver to be installed,
;the start address for I/O is 1 megabyte.

;If one or more extended memory VDISK device drivers have been installed,
;the start address for I/O for THIS device driver is acquired from the
;fields AVAIL_LO and AVAIL_HI in the FIRST VDISK device driver.

;The first extended memory VDISK device driver is located by INT 19H's vector.

	MOV	FIRST_EM_SW,0FFH	;indicate first VDISK device driver
	MOV	FIRST_VDISK,CS		;segment addr of first VDISK

	PUSH	DS			;preserve DS
	XOR	AX,AX
	MOV	DS,AX			;set DS = 0
	ASSUME	DS:INT_VEC

	MOV	AX,DS:BOOT_VECS 	;get segment addr of INT 19H routine
	MOV	DS,AX			;to DS
	ASSUME	DS:NOTHING

	PUSH	CS
	POP	ES			;set ES = CS
	MOV	SI,OFFSET VOL_LABEL	;DS:SI point to VOL label field
					;in first VDISK (if present)
	MOV	DI,SI			;ES:DI point to VOL label field of
					;this VDISK

	MOV	CX,VOL_LABEL_LEN	;length of volume label
	REP	CMPSB			;does INT 19H vector point to a VDISK
					;device driver?
	JNE	DETERMINE_START_A	;jump if this is the first VDISK

;Another extended memory VDISK device driver has been installed.
;Its AVAIL_LO and AVAIL_HI are the first free byte of extended memory.

	MOV	CS:FIRST_EM_SW,0	;indicate not first device driver
	MOV	CS:FIRST_VDISK,DS	;save pointer to 1st device driver

;Copy AVAIL_LO and AVAIL_HI from first VDISK to this VDISK

	MOV	SI,OFFSET AVAIL_LO	;DS:SI point to AVAIL_LO in first VDISK
	MOV	DI,SI			;ES:DI point to AVAIL_LO in this VDISK
	MOVSW				;copy AVAIL_LO from first to this VDISK
	MOVSB				;copy AVAIL_HI

DETERMINE_START_A:			;copy AVAIL_LO and AVAIL_HI to START_EM
	POP	DS			;set DS = CS

	MOV	SI,OFFSET AVAIL_LO	;source offset
	MOV	DI,OFFSET START_EM_LO	;destination offset

	MOVSW				;move AVAIL_LO to START_EM_LO
	MOVSB				;move AVAIL_HI to START_EM_HI
DETERMINE_START_X:
	RET
DETERMINE_START ENDP

	SUBTTL	VALIDATE parameters
	PAGE
;-----------------------------------------------------------------------;
;	VALIDATE adjusts parameters as necessary			;
;-----------------------------------------------------------------------;
VAL_SSZ_TBL LABEL WORD			;table of valid sector sizes
VAL_SSZ_S  DW	128			;smallest valid sector size
	   DW	256
VAL_SSZ_L  DW	512			;largest valid sector size
VAL_SSZ_N  EQU	($-VAL_SSZ_TBL)/2	;number of table entries

	ASSUME	DS:CSEG
VALIDATE	PROC			;validate parameters
	MOV	BPB_AUSZ,1		;initial allocation unit is 1 sector

	CALL	VAL_BSIZE		;validate buffer size

	CALL	VAL_SSZ 		;validate (adjust if necessary) BPB_SSZ

VALIDATE_A:
	AND	ERR_FLAG,255-ERR_PASS	;indicate nothing changed this pass

	MOV	AX,BPB_SSZ		;sector size
	CWD				;clear DX for division
	DIV	WPARA_SIZE		;sector size/para size
	MOV	PARAS_PER_SECTOR,AX	;number of paragraphs/sector

	MOV	AX,BUFF_SIZE		;requested buffersize in KB
	MUL	C1024			;DX:AX = buffer size in bytes
	DIV	BPB_SSZ 		;/sector size = # sectors
	MOV	BPB_SECN,AX		;store number of sectors

	CALL	VAL_DIRN		;validate number of directory entries

	TEST	ERR_FLAG,ERR_PASS	;may have reset sector size
	JNZ	VALIDATE_A		;recompute directory & FAT sizes

	CALL	VAL_FAT 		;compute FAT entries, validity test

	TEST	ERR_FLAG,ERR_PASS	;if cluster size altered this pass
	JNZ	VALIDATE_A		;recompute directory & FAT sizes

;Make certain buffer size is large enough to contain:
;	boot sector(s)
;	FAT sector(s)
;	directory sector(s)
;	at least 1 data cluster

	MOV	AL,BPB_FATN		;number of FAT copies
	CBW				;clear AH
	MUL	BPB_FATSZ		;* sectors for 1 FAT = FAT sectors
	ADD	AX,BPB_RES		;+ reserved sectors
	ADD	AX,DIR_SECTORS		;+ directory sectors
	MOV	CL,BPB_AUSZ		;get sectors/cluster
	XOR	CH,CH			;CX = sectors in one cluster
	ADD	AX,CX			;+ one data cluster
	CMP	BPB_SECN,AX		;compare with sectors available
	JAE	VALIDATE_X		;jump if enough sectors

	CMP	DIR_SECTORS,1		;down to 1 directory sector?
	JBE	VALIDATE_C		;can't let it go below 1

	MOV	AX,BPB_SSZ		;sector size
	CWD				;clear DX for division
	DIV	DIRE_SIZE		;sectorsize/dir entry size = entries/sector
	SUB	BPB_DIRN,AX		;reduce directory entries by 1 sector

	OR	ERR_FLAG,ERR_DIRN	;indicate directory entries adjusted
	JMP	VALIDATE_A		;retry with new directory entries number

VALIDATE_C:				;not enough space for any VDISK
	OR	ERR_FLAG,ERR_SYSSZ
VALIDATE_X:
	RET

	SUBTTL	VAL_BSIZE Validate buffer size
	PAGE
;-----------------------------------------------------------------------;
;	VAL_BSIZE adjusts the buffer size as necessary			;
;-----------------------------------------------------------------------;
VAL_BSIZE	PROC
	CALL	GET_MSIZE		;determine memory available to VDISK
					;returns available KB in AX
	OR	AX,AX			;is any memory available at all?
	JNZ	VAL_BSIZE_B		;yes, continue

	OR	ERR_FLAG,ERR_SYSSZ	;indicate system too small for VDISK
	MOV	BUFF_SIZE,1		;set up minimal values to continue init
	MOV	AX,VAL_SSZ_S		;smallest possible sector size
	MOV	BPB_SSZ,AX
	MOV	BPB_DIRN,4		;4 directory entries
	RET

VAL_BSIZE_B:				;some memory is available
	CMP	AX,BUFF_SIZE		;is available memory >= requested?
	JAE	VAL_BSIZE_C		;if so, we're done

	MOV	BUFF_SIZE,AX		;give all available memory
	OR	ERR_FLAG,ERR_BSIZE	;indicate buffersize adjusted
VAL_BSIZE_C:
	RET


GET_MSIZE	PROC			;determine memory available to VDISK
					;returns KB available in AX
	CMP	EM_SW,0 		;extended memory?
	JE	GET_MSIZE_2		;use non-extended memory routine

	MOV	AH,EM_MEMSIZE		;function code to AH
	INT	EM_INT			;get extended memory size in AX
	JC	GET_MSIZE_Z		;if error, no extended memory installed

	MUL	C1024			;DX,AX = bytes of extended memory
	ADD	DX,10H			;DX,AX = high addr of extended memory+1
	SUB	AX,AVAIL_LO		;- address of first available byte
	SBB	DL,AVAIL_HI		;is number of free bytes
	DIV	C1024			;AX = number of whole free kilobytes
	RET

GET_MSIZE_2:				;non-extended memory size determination

;Compute AX = total system size, - (VDISK end address + 64KB)

	MOV	AX,START_BUFFER_PARA	;paragraph end of VDISK code
	XOR	DX,DX			;clear for division
	DIV	PARA_PER_KB		;KB address of load point
	ADD	DX,0FFFFH		;round upward to KB boundary
	ADC	AX,MIN_MEMORY_LEFT	;pick up CY and the 64KB we should leave
	PUSH	AX			;save across interrupt
	INT	MEM_SIZE		;get total system size
	POP	DX			;amount of total that we can't use
	SUB	AX,DX			;available space to VDISK
	JNC	GET_MSIZE_X		;exit if positive

GET_MSIZE_Z:
	XOR	AX,AX			;indicate no memory available
GET_MSIZE_X:				;exit from memory size determination
	RET
GET_MSIZE	ENDP

VAL_BSIZE	ENDP

	SUBTTL	VAL_SSZ Validate Sector Size
	PAGE
;-----------------------------------------------------------------------;
;	VAL_SSZ validates sector size, adjusting if necessary		;
;-----------------------------------------------------------------------;
VAL_SSZ PROC				;validate sector size
	CMP	CS:EM_SW,0		;extended memory ?
	JE	VAL_SSZ_ST		;no,go check sector size
	MOV	BX,MAXSEC_TRF		;move number of sectors to transfer
	CMP	BX,1			;> or equal to 1 ?
	JB	DFLT_TRF		;set default if it is
	CMP	BX,8			;> than 8 ?
	JA	DFLT_TRF		;set default if it is
	JMP	VAL_SSZ_ST		;continue processing

DFLT_TRF:				;set default
	MOV	MAXSEC_TRF,DFLT_ESS	;
	MOV	BX,MAXSEC_TRF		;
	OR	CS:ERR_FLAG,ERR_ESIZE	;indicate transfer size adjusted

VAL_SSZ_ST:				;validate sector size
	MOV	MAX_CNT,BX		;initialize maximum number of sectors
					;to transfer for extended memory case
	MOV	BX,BPB_SSZ		;requested sector size
	MOV	CX,VAL_SSZ_N		;number of table entries
	MOV	SI,OFFSET VAL_SSZ_TBL	;DS:SI point to table start
VAL_SSZ_A:
	LODSW				;get table entry, step table pointer
	CMP	AX,BX			;is value in table?
	JE	VAL_SSZ_X		;exit if value found
	LOOP	VAL_SSZ_A		;loop until table end

	MOV	BX,DFLT_SSZ		;get default sector size
	MOV	BPB_SSZ,BX		;set sector size to default value
	OR	ERR_FLAG,ERR_SSZ	;indicate sector size adjusted
VAL_SSZ_X:

;Compute the maximum number of sectors that can be moved in 64KB (less one)
;Restricting moves to this amount avoids 64KB boundary problems.

	CMP	CS:EM_SW,0		;extended memory ?
	JNE	SIZE_DONE		;yes, we are done
	XOR	DX,DX
	MOV	AX,0FFFFH		;64KB - 1
	DIV	BX			;/sector size
	MOV	MAX_CNT,AX		;max sectors in one move
SIZE_DONE:
	RET
VAL_SSZ ENDP

	SUBTTL	VAL_DIRN Validate number of directory entries
	PAGE
;-----------------------------------------------------------------------;
;	VAL_DIRN validates and adjusts the number of directory entries. ;
;									;
;	Minimum is MIN_DIRN, maximum is MAX_DIRN.  If outside these	;
;	limits, DFLT_DIRN is used.					;
;									;
;	The number of directory entries is rounded upward to fill	;
;	a sector							;
;-----------------------------------------------------------------------;
VAL_DIRN PROC
	MOV	AX,BPB_DIRN		;requested directory entries
	CMP	AX,MIN_DIRN		;if less than minimum
	JB	VAL_DIRN_A		;use default instead

	CMP	AX,MAX_DIRN		;if <= maximum
	JBE	VAL_DIRN_B		;accept value as provided

VAL_DIRN_A:
	MOV	AX,DFLT_DIRN		;use default directory entries
	OR	ERR_FLAG,ERR_DIRN	;indicate directory entries adjusted
VAL_DIRN_B:				;AX is number of directory entries
	MUL	DIRE_SIZE		;* 32 = bytes of directory requested
	DIV	BPB_SSZ 		;/ sector size = # of directory sectors
	OR	DX,DX			;test remainder for zero
	JZ	VAL_DIRN_C		;jump if exact fit

	INC	AX			;increment directory sectors
	OR	ERR_FLAG,ERR_DIRN	;indicate directory entries adjusted
VAL_DIRN_C:				;make sure enough sectors available
	MOV	DX,BPB_SECN		;total sectors on media
	SUB	DX,BPB_RES		;less reserved sectors
	SUB	DX,2			;less minimum FAT and 1 data sector
	CMP	AX,DX			;if directory sectors <= available
	JLE	VAL_DIRN_D		;use requested amount

	MOV	AX,1			;use only one directory sector
	OR	ERR_FLAG,ERR_DIRN	;indicate directory entries adjusted
VAL_DIRN_D:
	MOV	DIR_SECTORS,AX		;save number of directory sectors
	MUL	BPB_SSZ 		;dir sectors * sector size = dir bytes
	DIV	DIRE_SIZE		;dir bytes / entry size = entries
	MOV	BPB_DIRN,AX		;store adjusted directory entries
	RET
VAL_DIRN ENDP

	SUBTTL	VAL_FAT Validate File Allocation Table (FAT)
	PAGE
;-----------------------------------------------------------------------;
;VAL_FAT computes:							;
;BPB_FATSZ, the number of sectors required per FAT copy 		;
;									;
;Each FAT entry is 12 bits long, for a maximum of 4095 FAT entries.	;
;(A few FAT entries are reserved, so the highest number of FAT entries	;
;we permit is 0FE0H.)  With large buffer sizes and small sector sizes,	;
;we have more allocation units to describe than a 12-bit entry will	;
;describe.  If the number of FAT entries is too large, the sector size	;
;is increased (up to a maximum of 512 bytes), and then the allocation	;
;unit (cluster) size is doubled, until we have few enough allocation	;
;units to be properly described in 12 bits.				;
;									;
;This computation is slightly conservative in that the FAT entries	;
;necessary to describe the FAT sectors are included in the computation. ;
;-----------------------------------------------------------------------;
VAL_FAT PROC
	MOV	AX,BPB_SECN		;total number of sectors
	SUB	AX,BPB_RES		;don't count boot sector(s)
	SUB	AX,DIR_SECTORS		;don't count directory sectors
	JG	VAL_FAT_A		;jump if some remaining
	MOV	BPB_SSZ,DFLT_SSZ	;force default sector size
	OR	ERR_FLAG,ERR_SSZ+ERR_PASS  ;indicate sector size adjusted
	JMP	SHORT VAL_FAT_X 	;recompute all values
VAL_FAT_A:
	XOR	DX,DX			;clear DX for division
	MOV	CL,BPB_AUSZ		;CX = sectors/cluster
	XOR	CH,CH
	DIV	CX			;whole number of clusters in AX
	ADD	DX,0FFFFH		;set carry if remainder
	ADC	AX,0			;increment AX if remainder
	CMP	AX,MAX_FATE		;number of FAT entries too large?
	JBE	VAL_FAT_C		;no, continue

	MOV	AX,BPB_SSZ		;pick up current sector size
	CMP	AX,VAL_SSZ_L		;already at largest permitted?
	JE	VAL_FAT_B		;yes, can't make it any larger

	SHL	BPB_SSZ,1		;double sector size
	OR	ERR_FLAG,ERR_SSZB	;indicate sector size adjusted
	JMP	SHORT VAL_FAT_X 	;recompute all sizes with new BPBSSZ

VAL_FAT_B:				;sector size is at maximum
	SHL	BPB_AUSZ,1		;double allocation unit size
	OR	ERR_FLAG,ERR_PASS	;indicate another pass required
	JMP	SHORT VAL_FAT_X 	;recompute values

VAL_FAT_C:				;FAT size =  1.5 * number of clusters
	MOV	CX,AX			;number of clusters
	SHL	AX,1			;* 2
	ADD	AX,CX			;* 3
	SHR	AX,1			;* 1.5
	ADC	AX,3			;add 3 bytes for first 2 FAT entries
					;(media descriptor and FFFFH), and CY
	XOR	DX,DX			;clear DX for division
	DIV	BPB_SSZ 		;FAT size/sector size
	ADD	DX,0FFFFH		;set carry if remainder
	ADC	AX,0			;round upward
	MOV	BPB_FATSZ,AX		;number of sectors for 1 FAT copy
VAL_FAT_X:
	RET
VAL_FAT ENDP


VALIDATE	ENDP

	SUBTTL	COPY_BPB Copy BPB to Boot Record
	PAGE
;-----------------------------------------------------------------------;
;	COPY_BPB copies the BIOS Parameter Block (BPB)			;
;	to the VDISK Boot Record					;
;-----------------------------------------------------------------------;
	ASSUME	DS:CSEG
COPY_BPB	PROC			;Copy BBP to Boot Record
	PUSH	DS
	POP	ES			;set ES = DS

	MOV	CX,BPB_LEN		;length of BPB
	MOV	SI,OFFSET BPB		;source offset
	MOV	DI,OFFSET BOOT_BPB	;target offset
	REP	MOVSB			;copy BPB to boot record
	RET
COPY_BPB	ENDP

	SUBTTL	VERIFY_EXTENDER
	PAGE
;-----------------------------------------------------------------------;
;	VERIFY_EXTENDER makes sure that if an Expansion Unit is 	;
;	installed, the memory size switches on the Extender Card	;
;	are correctly set.						;
;-----------------------------------------------------------------------;


	ASSUME	DS:CSEG
EXT_P210  EQU	0210H		;write to latch expansion bus data
				;read to verify expansion bus data
EXT_P213  EQU	0213H		;Expansion Unit status

VERIFY_EXTENDER PROC

	NOP

	MOV	DX,EXT_P210	;Expansion bus data port address

	MOV	AX,5555H	;set data pattern
	OUT	DX,AL		;write 55H to control port
	PUSH	DX
	POP	DX

	JMP	SHORT $+2	;Let the I/O circuits catch up
	IN	AL,020h 	;Clear the CMOS bus drivers!

	IN	AL,DX		;recover data
	CMP	AH,AL		;did we recover the same data?
	JNE	VERIFY_EXTENDER_X	;if not, no extender card

	NOT	AX		;set AX = 0AAAAH
	OUT	DX,AL		;write 0AAH to control port
	PUSH	DX		;load data line
	POP	DX		;load data line

	JMP	SHORT $+2	;Let the I/O circuits catch up
	IN	AL,020h 	;Clear the CMOS bus drivers!

	IN	AL,DX		;recover data
	CMP	AH,AL		;did we recover the same data?
	JNE	VERIFY_EXTENDER_X	;if not, no extender card

;Expansion Unit is present.

;Determine what the switch settings should be on the Extender Card

	INT	MEM_SIZE	;get system memory size in KB in AX
	ADD	AX,63D		;memory size + 63K
	MOV	CL,6		;2^6 = 64
	SHR	AX,CL		;divide by 64
				;AX is highest segment address
	MOV	AH,AL		;save number of segments

;Read Expander card switch settings

	MOV	DX,EXT_P213	;expansion unit status
	IN	AL,DX		;read status
				;bits 7-4 (hi nibble) are switches
	MOV	CL,4		;shift count
	SHR	AL,CL		;shift switches to bits 3-0 of AL

	CMP	AH,AL		;do switches match memory size?
	JE	VERIFY_EXTENDER_X	;yes, exit normally

	OR	ERR_FLAG,ERR_EXTSW	;indicate switch settings are wrong

VERIFY_EXTENDER_X:
	RET
VERIFY_EXTENDER ENDP

	SUBTTL	UPDATE_AVAIL
	PAGE
;-----------------------------------------------------------------------;
;	UPDATE_AVAIL updates the address of the first byte in extended	;
;	memory not used by any VDISK buffer				;
;-----------------------------------------------------------------------;
UPDATE_AVAIL	PROC		;update AVAIL_LO and AVAIL_HI of first VDISK
	MOV	AX,BUFF_SIZE	;number of KB of VDISK buffer
	MUL	C1024		;DX,AX = number of bytes of VDISK buffer

	PUSH	DS
	MOV	DS,FIRST_VDISK	;set DS to first VDISK
	ADD	DS:AVAIL_LO,AX	;update first available byte location
	ADC	DS:AVAIL_HI,DL
	POP	DS
	RET
UPDATE_AVAIL	ENDP

	SUBTTL	FORMAT_VDISK
	PAGE
;-----------------------------------------------------------------------;
;	This Request Header is used by MOVE_VDISK to move the		;
;	first few sectors of the virtual disk (boot, FAT, and		;
;	Directory) into extended memory.				;
;-----------------------------------------------------------------------;

MOVE_RH    DB	MOVE_RH_L		;length of request header
	   DB	0			;sub unit
	   DB	8			;output operation
	   DW	0			;status
	   DQ	?			;reserved for DOS
	   DB	?			;media descriptor byte
MOVE_RHO   DW	?			;offset of data transfer address
MOVE_RHS   DW	?			;segment of data transfer address
MOVE_RHCNT DW	?			;count of sectors to transfer
	   DW	0			;starting sector number
MOVE_RH_L  EQU	$-MOVE_RH		;length of request header

;-----------------------------------------------------------------------;
;	FORMAT_VDISK formats the boot sector, FAT, and directory of an	;
;	extended memory VDISK in storage immediately following		;
;	VDISK code, in preparation for moving to extended memory.	;
;-----------------------------------------------------------------------;
FORMAT_VDISK	PROC			;format boot record, FATs and directory

	MOV	AX,CS			;compute 20-bit address
	MUL	WPARA_SIZE		;16 * segment
	ADD	AX,OFFSET MSGEND	;+ offset
	ADC	DL,0			;pick up carry
	ADD	AX,STACK_SIZE		;plus stack size
	ADC	DL,0			;pick up carry

	DIV	WPARA_SIZE		;split into segment(AX)&offset(DX)
	MOV	MOVE_RHS,AX		;save in Request Header for move
	MOV	MOVE_RHO,DX

	MOV	DI,DX			;offset to DI
	MOV	ES,AX			;segment to ES

;copy the boot record

	MOV	SI,OFFSET BOOT_RECORD	;point to source field
	MOV	AX,BPB_RES		;number of reserved sectors
	MUL	BPB_SSZ 		;* sector size = length of boot records
	MOV	CX,AX			;length to CX for move
	REP	MOVSB			;move boot record(s)

;format the FAT(s)

	MOV	CL,BPB_FATN		;number of FATs
	XOR	CH,CH
FORMAT_VDISK_A: 			;set up one FAT
	PUSH	CX			;save loop counter on stack
	MOV	AL,BPB_MCB		;media control byte
	STOSB				;store media control byte, increment DI
	MOV	AX,0FFFFH		;bytes 2 and 3 of FAT are 0FFH
	STOSW
	MOV	AX,BPB_FATSZ		;number of sectors per FAT
	MUL	BPB_SSZ 		;* sector size = length of FAT in bytes
	SUB	AX,3			;less the 3 bytes we've stored
	MOV	CX,AX			;count to CX
	XOR	AX,AX
	REP	STOSB			;clear remainder of FAT
	POP	CX			;get loop counter off stack
	LOOP	FORMAT_VDISK_A		;loop for all copies of the FAT

;Format the directory

	MOV	SI,OFFSET VOL_LABEL	;point to volume label
	MOV	CX,VOL_LABEL_LEN	;length of volume directory entry
	REP	MOVSB			;move volume id to directory
	MOV	AX,DIR_ENTRY_SIZE	;length of 1 directory entry
	MUL	BPB_DIRN		;* number entries = bytes of directory
	SUB	AX,VOL_LABEL_LEN	;less length of volume label
	MOV	CX,AX			;CX = length of rest of directory
	XOR	AX,AX
	REP	STOSB			;clear directory to nulls
	RET
FORMAT_VDISK	ENDP

	SUBTTL	MOVE_VDISK
	PAGE
;-----------------------------------------------------------------------;
;	MOVE_VDISK moves the formatted boot sector, FAT, and directory	;
;	into extended memory.						;
;-----------------------------------------------------------------------;

MOVE_VDISK	PROC
	MOV	AL,BPB_FATN		;number of FAT copies
	CBW				;clear AH
	MUL	BPB_FATSZ		;number of FAT sectors
	ADD	AX,BPB_RES		;+ reserved sectors
	ADD	AX,DIR_SECTORS		;+ directory sectors
	MOV	MOVE_RHCNT,AX		;store as I/O length

	MOV	BX,OFFSET MOVE_RH	;DS:BX point to request header
	PUSH	DS			;make sure DS gets preserved
	CALL	INOUT			;move to extended memory
	POP	DS
	RET
MOVE_VDISK	ENDP

	SUBTTL	UPDATE_BOOT
	PAGE
;-----------------------------------------------------------------------;
;	UPDATE_BOOT updates the BOOT_EM word in the first extended	;
;	memory VDISK (address 10 001EH) to show the kilobyte address	;
;	of the first extended memory byte not used by any VDISK buffer. ;
;-----------------------------------------------------------------------;
UPDATE_BOOT	PROC
	PUSH	DS
	MOV	DS,FIRST_VDISK		;set DS to first VDISK
	MOV	AX,DS:AVAIL_LO		;24-bit end address of all VDISKs
	MOV	DL,DS:AVAIL_HI
	XOR	DH,DH
	POP	DS
	DIV	C1024			;address / 1024
	MOV	BOOT_EM,AX		;store in temporary location

	MOV	AX,2			;length of block move is 2 bytes
	MOV	TGT.DESC_LMT,AX
	MOV	SRC.DESC_LMT,AX

	MOV	AX,PARA_SIZE		;16
	MOV	CX,CS			;our segment address
	MUL	CX			;16 * segment address
	ADD	AX,OFFSET BOOT_EM	;+ offset of source data
	ADC	DL,0			;pick up any carry

	MOV	SRC.DESC_BASEL,AX	;store source base address
	MOV	SRC.DESC_BASEH,DL

	MOV	TGT.DESC_BASEL,BOOT_EM_OFF	;offset of BOOT_EM
	MOV	TGT.DESC_BASEH,10H	;1 megabyte

	MOV	CX,1			;move 1 word

	PUSH	CS
	POP	ES
	MOV	SI,OFFSET GDT		;ES:DI point to global descriptor table

	MOV	AH,EM_BLKMOVE		;function code
	INT	EM_INT			;move BOOT_EM to 10 001EH
	RET
UPDATE_BOOT	ENDP

	SUBTTL	STEAL_INT19
	PAGE
;-----------------------------------------------------------------------;
;	STEAL_INT19 changes the INT 19H vector to point to this VDISK	;
;	so that subsequent extended memory VDISKS may locate the	;
;	AVAIL_HI and AVAIL_LO fields to determine their buffer start	;
;	addresses.							;
;-----------------------------------------------------------------------;
STEAL_INT19	PROC
	PUSH	DS
	XOR	AX,AX
	MOV	DS,AX			;set DS = 0
	ASSUME	DS:INT_VEC
	CLI				;disable interrupts
	LES	DI,DS:BOOT_VEC		;get original vector's content
	MOV	CS:INTV19O,DI		;save original vector
	MOV	CS:INTV19S,ES
	MOV	DS:BOOT_VECO,OFFSET VDISK_INT19 ;offset of new INT routine
	MOV	DS:BOOT_VECS,CS 	;segment of new INT routine
	STI				;enable interrupts again
	POP	DS			;restore DS
	RET
STEAL_INT19	ENDP

	SUBTTL	FILL_RH Fill in Request Header
	PAGE
;-----------------------------------------------------------------------;
;	FILL_RH fills in the Request Header returned to DOS		;
;-----------------------------------------------------------------------;
	ASSUME	DS:CSEG
FILL_RH PROC				;fill in INIT Request Header fields
	MOV	CX,START_BUFFER_PARA	;segment end of VDISK resident code
	MOV	AX,PARAS_PER_SECTOR	;paragraphs per sector
	MUL	BPB_SECN		;* number of sectors
	ADD	AX,CX			;+ starting segment
	MOV	DX,AX			;DX is segment of end VDISK buffer
	CMP	EM_SW,0 		;if extended memory not requested
	JE	FILL_RH_A		;skip DX adjustment

	MOV	DX,CX			;end of code segment addr
FILL_RH_A:				;DX is proper ending segment address
	MOV	AL,1			;number of units
	test	CS:err_flag2,err_baddos
	jnz	dont_install

	TEST	ERR_FLAG,ERR_SYSSZ+ERR_EXTSW	;if bypassing install
	JZ	FILL_RH_B		;jump if installing driver

dont_install:
	MOV	DX,CS			;segment of end address
	XOR	AL,AL			;number of units is zero
FILL_RH_B:
	PUSH	DS			;preserve DS
	LDS	BX,RH_PTRA		;get Request Header addr in DS:BX
	MOV	RH.RH0_NUN,AL		;store number of units (0 or 1)
	MOV	RH.RH0_ENDO,0		;end offset is always zero
	MOV	RH.RH0_ENDS,DX		;end of VDISK or end of buffer
	MOV	RH.RH0_BPBO,OFFSET BPB_PTR
	MOV	RH.RH0_BPBS,CS		;BPB array address
	POP	DS			;restore DS
	RET
FILL_RH ENDP

	SUBTTL	WRITE_MESSAGES and associated routines
	PAGE
;-----------------------------------------------------------------------;
;	WRITE_MESSAGE writes a series of messages to the standard	;
;	output device showing the VDISK parameter values actually used. ;
;-----------------------------------------------------------------------;

CHAR4	   DB	'nnnn$'         ;build 4 ASCII decimal digits

	ASSUME	DS:CSEG
WRITE_MESSAGES	PROC		;display all messages

	MSG	IMSG		;'VDISK virtual disk $'

	test	CS:err_flag2,err_baddos
	jz	check_dos_version

	msg	errm8
	ret

;If DOS Version 3.x is in use, the Request Header contains a drive code
;that is displayed to show which drive letter was assigned to this
;VDISK.  This field is not present in the DOS Version 2 Request Header.

check_dos_version:
	MOV	AH,DOS_VERS	;get DOS version call
	INT	DOS		;invoke DOS

	CMP	AL,3		;DOS Version 3 or greater?
	JB	WRITE_MESSAGES_A	;no, bypass drive letter

	PUSH	DS		;preserve DS
	LDS	BX,RH_PTRA	;get Request Header Address
	MOV	DL,RH.RH0_DRIV	;get drive code
	ADD	DL,'A'          ;convert to drive letter
	POP	DS		;restore DS

	MOV	AH,DOS_PCHR	;function code to write character in DL
	INT	DOS		;display drive letter

	MOV	DL,':'          ;display trailing colon
	INT	DOS

WRITE_MESSAGES_A:
	MSG	MSGCRLF 	;end the first line

;If any of the user specified values has been adjusted, issue an
;appropriate message

	TEST	ERR_FLAG,ERR_BSIZE	;was buffersize adjusted?
	JZ	WRITE_MESSAGES_B	;if not, skip message

	MSG	ERRM1			;buffer size adjusted

WRITE_MESSAGES_B:
	TEST	ERR_FLAG,ERR_SSZ	;was sector size adjusted?
	JZ	WRITE_MESSAGES_C	;if not, skip message

	MSG	ERRM2			;sector size adjusted

WRITE_MESSAGES_C:
	TEST	ERR_FLAG,ERR_DIRN	;were directory entries adjusted?
	JZ	WRITE_MESSAGES_D0	;if not, skip message

	MSG	ERRM3			;directory entries adjusted

WRITE_MESSAGES_D0:
	TEST	ERR_FLAG,ERR_ESIZE	;was transfer size adjusted?
	JZ	WRITE_MESSAGES_D	;if not, skip message

	MSG	ERRM7			;transfer size adjusted

WRITE_MESSAGES_D:
	TEST	ERR_FLAG,ERR_SWTCH	;was an invalid switch character found?
	JZ	WRITE_MESSAGES_E	;if not, skip message

	MSG	ERRM5			;invalid switch character

WRITE_MESSAGES_E:
	TEST	ERR_FLAG,ERR_SYSSZ	;is system size too small to install?
	JZ	WRITE_MESSAGES_F	;if not, bypass error message

	MSG	ERRM4			;too large for system storage
	RET				;skip messages showing adjusted sizes

WRITE_MESSAGES_F:
	TEST	ERR_FLAG,ERR_EXTSW	;extender card switches wrong?
	JZ	WRITE_MESSAGES_G	;if not, bypass error message

	MSG	ERRM6			;extender card switches wrong msg
	RET				;skip remaining messages

WRITE_MESSAGES_G:			;display adjusted size messages
	MSG	MSG1			;buffer size:

	MOV	DX,BUFF_SIZE		;buffer size in binary
	CALL	STOR_SIZE		;convert  binary to ASCII decimal
	MSG	CHAR4			;print 4 decimals
	MSG	MSG2			;KB,CR,LF

	MSG	MSG3			;sector size:
	MOV	DX,BPB_SSZ
	CALL	STOR_SIZE		;convert binary to ASCII decimal
	MSG	CHAR4			;print 4 decimals
	MSG	MSGCRLF 		;finish off line

	MSG	MSG4			;directory entries:
	MOV	DX,BPB_DIRN		;number of directory entries
	CALL	STOR_SIZE
	MSG	CHAR4			;print 4 decimals
	MSG	MSGCRLF 		;finish off the line

	CMP	CS:EM_SW,0		;extended memory ?
	JE	END_LINE		;
	MSG	MSG5			;transfer size:
	MOV	DX,MAXSEC_TRF
	CALL	STOR_SIZE		;convert binary to ASCII decimal
	MSG	CHAR4			;print 4 decimals
	MSG	MSGCRLF 		;finish off line

END_LINE:
	MSG	MSGCRLF 		;one more blank line to set it off
	RET				;return to INIT_P1

;SHOW_MSG displays a string at DS:DX on the standard output device
;String is terminated by a $

SHOW_MSG	PROC			;display string at DS:DX
	PUSH	AX			;preserve AX across call
	MOV	AH,DOS_PSTR		;DOS function code
	INT	DOS			;invoke DOS print string function
	POP	AX			;restore AX
	RET
SHOW_MSG	ENDP

;STOR_SIZE converts the content of DX to 4 decimal characters in CHAR4
;(DX must be <= 9999)

STOR_SIZE PROC			;convert DX to 4 decimals in CHAR4
				;develop 4 packed decimal digits in AX
	XOR	AX,AX		;clear result register
	MOV	CX,16		;shift count
STOR_SIZE_B:
	SHL	DX,1		;shift high bit into carry
	ADC	AL,AL		;double AL, carry in
	DAA			;adjust for packed decimal
	XCHG	AL,AH
	ADC	AL,AL		;double high byte, carry in
	DAA
	XCHG	AL,AH
	LOOP	STOR_SIZE_B	;AX contains 4 packed decimal digits

	PUSH	CS
	POP	ES		;point ES:DI to output string
	MOV	DI,OFFSET CHAR4

	MOV	CX,1310H	;10H in CL is difference between blank and zero
				;13H in CH is decremented and ANDed to force
				;last character not to be zero suppressed
	PUSH	AX		;save AX on stack
	MOV	DL,AH		;2 decimals to DL
	CALL	STOR_SIZE_2	;display DL as 2 decimal characters
	POP	DX		;bring low 2 decimals into DL
STOR_SIZE_2:			;display DL as 2 decimal characters
	MOV	DH,DL		;save 2 decimals in DH
	SHR	DL,1		;shift high order decimal right to low position
	SHR	DL,1
	SHR	DL,1
	SHR	DL,1
	CALL	STOR_SIZE_1	;display low nibble of DL
	MOV	DL,DH		;get low decimal from pair
STOR_SIZE_1:			;display low nibble of DL as 1 decimal char
	AND	DL,0FH		;clear high nibble
	JZ	STOR_SIZE_Z	;if digit is significant,
	XOR	CL,CL		;defeat zero suppression
STOR_SIZE_Z:
	DEC	CH		;decrement zero suppress counter
	AND	CL,CH		;always display least significant digit
	OR	DL,'0'          ;convert packed decimal to ASCII
	SUB	DL,CL		;zero suppress (nop or change '0' to ' ')
	MOV	AL,DL		;char to DL
	STOSB			;store char at ES:DI, increment DI
	RET
STOR_SIZE ENDP

WRITE_MESSAGES	ENDP

INIT_P1 ENDP			;end of INIT part one

;-----------------------------------------------------------------------;
;	VDISK Message  definitions					;
;-----------------------------------------------------------------------;

IMSG	DB	'VDISK virtual disk ','$'

ERRM1	DB	'   Buffer size adjusted',CR,LF,'$'
ERRM2	DB	'   Sector size adjusted',CR,LF,'$'
ERRM3	DB	'   Directory entries adjusted',CR,LF,'$'
ERRM4	DB	'   VDISK not installed - insufficient memory'
	DB	CR,LF,CR,LF,BEL,'$'
ERRM5	DB	'   Invalid switch character',CR,LF,'$'
ERRM6	DB	'   VDISK not installed - Extender Card switches'
	DB	CR,LF
	DB	'   do not match system memory size'
	DB	CR,LF,CR,LF,BEL,'$'
ERRM7	DB	'   Transfer size adjusted',CR,LF,'$'
ERRM8	DB	'   VDISK not installed - Incorrect DOS version'
	DB	CR,LF,CR,LF,BEL,'$'

MSG1	DB	'   Buffer size:       $'
MSG2	DB	' KB'
MSGCRLF DB	CR,LF,'$'
MSG3	DB	'   Sector size:       $'
MSG4	DB	'   Directory entries: $'
MSG5	DB	'   Transfer size:     $'
MSGEND	LABEL	BYTE				; End of message text

CSEG	ENDS
	END