summaryrefslogtreecommitdiff
path: root/v4.0/src/DEV/XMAEM/INDEINI.ASM
blob: 19c8b7988a4c5c2389c6669e9b765bcd66821042 (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
PAGE	60,132
TITLE	INDEINI - 386 XMA EMULATOR - Initialization

COMMENT #
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*									      *
* MODULE NAME	  : INDEINI						      *
*                                                                             *
*                    5669-196 (C) COPYRIGHT 1988 Microsoft Corp.              *
*                                                                             *
* DESCRIPTIVE NAME: 80386 XMA EMULATOR INITIALIZATION			      *
*									      *
* STATUS (LEVEL)  : VERSION (0) LEVEL (2.0)				      *
*									      *
* FUNCTION	  : Do all the initialization needed for the 386 XMA emulator.*
*									      *
*		    The 386 XMA emulator is installed by putting the follow-  *
*		    ing command in the CONFIG.SYS file: 		      *
*									      *
*			      DEVICE=\386XMAEM.SYS bbb			      *
*									      *
*		    where "bbb" is the number of K reserved for the MOVEBLOCK *
*		    function.  If EMS is used, this command must appear       *
*		    before the command to load EMS.			      *
*									      *
*		    This module first of all does all the stuff to set up     *
*		    the device driver linkage to DOS.  The driver is a	      *
*		    character device.  The only command it recognizes is   P3C*
*		    "initialize".  When it receives the initialize command    *
*		    it does all the set up for the emulator.  For information *
*		    on device drivers see the DOS Technical Reference.	      *
*									      *
*		    Then it checks to see if we're on a model_80 and the      *
*		    emulator has not been previously installed.  If this is   *
*		    the case, then it procedes to do the following:	      *
*			Get the MOVEBLOCK buffer size from the parameter list *
*			Save the maximum XMA block number in the header       *
*			Relocate to high memory 			      *
*			Initialize the page directory and page tables	      *
*			Call INDEIDT to initialize the IDT		      *
*			Call INDEGDT to initialize the GDT		      *
*			Switch to virtual mode				      *
*			Initialize the TSS for the virtual 8086 task	      *
*			Initialize the XMA page tables			      *
*			Enable paging					      *
*									      *
*		    This module also contains code to handle the Generic   D2A*
*		    IOCTL call which is used to query the highest valid    D2A*
*		    XMA block number.  This code is left resident.	   D2A*
*									      *
* MODULE TYPE	  : ASM 						      *
*									      *
* REGISTER USAGE  : 80386 STANDARD					      *
*									      *
* RESTRICTIONS	  : None						      *
*									      *
* DEPENDENCIES	  : None						      *
*									      *
* LINKAGE	  : Invoked as a DOS device driver			      *
*									      *
* INPUT PARMS	  : The number of 1K blocks reserved for the MOVE BLOCK       *
*		    service can be specified after the DEVICE command in the  *
*		    CONFIG.SYS file.  0K is the default.		      *
*									      *
* RETURN PARMS	  : A return code is returned to DOS in the device header     *
*		    at offset 3.					      *
*									      *
* OTHER EFFECTS   : None						      *
*									      *
* EXIT NORMAL	  : Return to DOS after device driver is loaded 	      *
*									      *
* EXIT ERROR	  : Return to DOS after putting up error messages	      *
*									      *
* EXTERNAL								      *
* REFERENCES	  : SIDT_BLD - Entry point for INDEIDT to build the IDT       *
*		    GDT_BLD  - Entry point for INDEGDT to build the GDT       *
*		    WELCOME  - The welcome message			      *
*		    GOODLOAD - Message saying we loaded OK		      *
*		    NO_80386 - Error message for not running on a model_80    *
*		    WAS_INST - Error message for protect mode in use	      *
*		    SP_INIT  - Initial protect mode SP			      *
*		    REAL_CS  - Place to save our real mode CS		      *
*		    REAL_SS  - Place to save our real mode SS		      *
*		    REAL_SP  - Place to save our real mode SP		      *
*		    PGTBLOFF - Offset of the page tables		      *
*		    SGTBLOFF - Offest of the page directory		      *
*		    NORMPAGE - Normal page directory entry		      *
*		    XMAPAGE  - Page directory entry for the first XMA page D1A*
*		    BUFF_SIZE- Size of the MOVEBLOCK buffer		      *
*		    MAXMEM   - Maximum amount of memory on the box	      *
*		    CRT_SELECTOR - Selector for the display buffer	      *
*									      *
* SUB-ROUTINES	  : GATE_A20  - Gate on or off address bit 20		      *
*		    GET_PARMS - Get the MOVEBLOCK buffer size specified on    *
*				the command in CONFIG.SYS and convert to      *
*				binary. 				      *
*									      *
* MACROS	  : DATAOV  - Add prefix for the next instruction so that it  *
*			      accesses data as 32 bits wide		      *
*		    ADDROV  - Add prefix for the next instruction so that it  *
*			      uses addresses that are 32 bits wide	      *
*		    CMOV    - Move to and from control registers	      *
*		    JUMPFAR - Build an instruction that will jump to the      *
*			      offset and segment specified		      *
*									      *
* CONTROL BLOCKS  : INDEDAT.INC 					      *
*									      *
* CHANGE ACTIVITY :							      *
*									      *
* $MOD(INDEINI) COMP(LOAD) PROD(3270PC) :				      *
*									      *
* $D0=D0004700 410 870521 D : NEW FOR RELEASE 1.1.  CHANGES TO THE ORIGINAL   *
*			      CODE ARE MARKED WITH D0A. 		      *
* $P1=P0000281 410 870730 D : SAVE 32 BIT REGISTERS ON model_80 	      *
* $P2=P0000312 410 870804 D : CHANGE COMPONENT FROM MISC TO LOAD	      *
* $P3=P0000335 410 870811 D : HEADER INFORMATION ALL SCREWED UP 	      *
* $D1=D0007100 410 870810 D : CHANGE TO EMULATE XMA 2			      *
*			      CHANGE ID STRING TO "386XMAEMULATOR10"          *
* $P4=P0000649 411 880125 D : A20 NOT ENABLED WHEN PASSWORD SET 	      *
* $P5=P0000650 411 880128 D : COPROCESSOR APPLICATIONS FAIL		      *
* $P6=P0000740 411 880129 D : IDSS CAPTURED DCR 87 CODE.  REMOVE IT.	      *
* $D2=D0008700 120 880206 D : SUPPORT DOS 3.4 IOCTL CALL		      *
* $P7=P0000xxx 120 880331 D : FIX INT 15.  LOAD AS V86 MODE HANDLER.	      *
*									      *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
#

	.286P		      ; Enable recognition of 286 privileged instructs.

	.XLIST		      ; Turn off the listing
	INCLUDE INDEDAT.INC

	IF1		      ; Only include the macros in the first pass
	INCLUDE INDEOVP.MAC   ;   of the assembler
	INCLUDE INDEINS.MAC
	ENDIF
	.LIST		      ; Turn on the listing

	; Let these variables be known to external procedures

	PUBLIC	POST
	PUBLIC	INDEINI

PROG	SEGMENT PARA PUBLIC  'PROG'

	ASSUME	CS:PROG
	ASSUME	SS:NOTHING
	ASSUME	DS:PROG
	ASSUME	ES:NOTHING

INDEINI LABEL	NEAR

	; These variables are located in INDEI15

	EXTRN	SP_INIT:WORD	  ; Initial protect mode SP
	EXTRN	REAL_CS:WORD	  ; Place to save our real mode CS
	EXTRN	REAL_SS:WORD	  ; Place to save our real mode SS
	EXTRN	REAL_SP:WORD	  ; Place to save our real mode SP
	EXTRN	PGTBLOFF:WORD	  ; Offset of the page tables
	EXTRN	SGTBLOFF:WORD	  ; Offest of the page directory
	EXTRN	NORMPAGE:WORD	  ; Normal page directory entry.  Points to the
				  ;   page table that maps the real 0 to 4M.
	EXTRN	XMAPAGE:WORD	  ; Page directory entry for the first XMA
				  ;   page table (page table for bank 0)    @D1A
	EXTRN	BUFF_SIZE:WORD	  ; Size of the MOVEBLOCK buffer
	EXTRN	MAXMEM:WORD	  ; Maximum amount of memory on the box
	EXTRN	CRT_SELECTOR:WORD ; Selector for the display buffer

	; These are the messages

	EXTRN	WELCOME:BYTE	  ; The welcome message
	EXTRN	GOODLOAD:BYTE	  ; Message saying we loaded OK
	EXTRN	NO_80386:BYTE	  ; Error message for not running on a model_80
	EXTRN	WAS_INST:BYTE	  ; Error message for protect mode in use
	Extrn	Small_Parm:Byte   ; Parm value < 64 and > 0			;an000; dms;
	Extrn	No_Mem:Byte	  ; Parm value > memory available		;an000; dms;

	; These entries are located in external procedures

	EXTRN	SIDT_BLD:NEAR	  ; Build the interrupt descriptor table (IDT)
	EXTRN	GDT_BLD:NEAR	  ; Build the global descriptor table (GDT)

; General equates

DDSIZE		EQU	GDT_LOC 	; Size of the device driver
HIGH_SEG	EQU	0FFF0H		; The segment we relocate to
MEG_SUPPORTED	EQU	24		; Must be a multiple of 4
XMA_PAGES_SEL	EQU	RSDA_PTR	; Selector for XMA pages
DISPSTRG	EQU	09H		; DOS display string function number D0A
GET_VECT	EQU	35H		; DOS get vector function number     P7A
SET_VECT	EQU	25H		; DOS set vector function number     P7A
model_80	EQU	0F8H		; Model byte for the Wangler	     D0A
XMA640KSTRT	EQU	580H		; Start address of XMA block for     D0A
					;   640K (16000:0 / 1K) 	D0A  P3C

; ASCII character equates

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

SUBTTL	Structure Definitions
PAGE
;------------------------------------------------------------------------------;
;	Request Header (Common portion) 				       ;
;------------------------------------------------------------------------------;

RH	EQU	DS:[BX] 	; The Request Header structure is based off
				;   of DS:[BX]

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

; Status values for RHC_STA

STAT_DONE   EQU 0100H		; Function complete status (high order byte)@P3C
STAT_CMDERR EQU 8003H		; Invalid command code error
STAT_GEN    EQU 800CH		; General error code			     D0A

;------------------------------------------------------------------------------;
;	Request Header for INIT command 				       ;
;------------------------------------------------------------------------------;

RH0	STRUC
	   DB	(TYPE RHC) DUP (?)	; Reserve space for the header

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.
				    ; On the INIT command the BPB points to
				    ; the characters following the "DEVICE="
				    ; in the CONFIG.SYS file.

;---------------------------------------------------------------------------D2A;
;	Request Header for Generic IOCTL Request			    D2A;
;---------------------------------------------------------------------------D2A;

RH19	STRUC
	    DB	 (TYPE RHC) DUP (?)	; Reserve space for the header	   @D2A

RH19_MAJF   DB	 ?		; Major function			   @D2A
RH19_MINF   DB	 ?		; Minor function			   @D2A
RH19_SI     DW	 ?		; Contents of SI			   @D2A
RH19_DI     DW	 ?		; Contents of DI			   @D2A
RH19_RQPK   DD	 ?		; Pointer to Generic IOCTL request packet  @D2A
RH19	ENDS

SUBTTL	Device Driver Header
PAGE
POST	PROC	NEAR

	; Declare the device driver header

	ORG  0		      ; Device header must the very first thing in the
			      ;   device driver
	DD   -1 	      ; Becomes pointer to next device header
	DW   0C040H	      ; Character device, does IOCTL	       @P3C @D2C
	DW   OFFSET STRATEGY  ; Pointer to device "strategy" routine
	DW   OFFSET IRPT      ; Pointer to device "interrupt handler"
	DB   "386XMAEM"       ; Device name                                 @D0C

	; End of device driver header

;------------------------------------------------------------------------------;
;	Request Header (RH) address, saved here by "strategy" routine          ;
;------------------------------------------------------------------------------;

RH_PTRA    LABEL DWORD
RH_PTRO    DW	?	      ; Offset of the request header
RH_PTRS    DW	?	      ; Segment of the request header
			      ; Character ID "386XMAEMULATOR10" deleted   2@D2D
HI_XMA_BLK DW	?	      ; The highest XMA block number		   @D0A
EXT_MEM    DW	?	      ; Number of K of extended memory		   @P7A
			      ; 					    D0A
RBX	   DW	?	      ; Temporary save area for register BX	   @P1A
ISmodel_80 DB	-1	      ; model_80 flag.	Set to 1 if on a model_80  @P1A
			      ;   Set to 0 if not on a model_80 	   @D1C

SUBTTL	Device Strategy
PAGE
;------------------------------------------------------------------------------;
;	Device "strategy" entry point                                          ;
;									       ;
;	Retain the Request Header address for use by Interrupt routine	       ;
;------------------------------------------------------------------------------;

STRATEGY PROC	FAR

	MOV	CS:RH_PTRO,BX ; Save the offset of the request header
	MOV	CS:RH_PTRS,ES ; Save the segment of the request header
	RET

STRATEGY ENDP

SUBTTL	Device Interrupt Intry Point
PAGE

;------------------------------------------------------------------------------;
;	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
	   DW	OFFSET REMOVABLE_MEDIA	;15 - Removable media
	   DW	OFFSET INVALID_FCN	;16 - Invalid IOCTL function	    @D2A
	   DW	OFFSET INVALID_FCN	;17 - Invalid IOCTL function	    @D2A
	   DW	OFFSET INVALID_FCN	;18 - Invalid IOCTL function	    @D2A
	   DW	OFFSET GENERIC_IOCTL	;19 - Generic IOCTL function	    @D2A
	   DW	OFFSET INVALID_FCN	;20 - Invalid IOCTL function	    @D2A
	   DW	OFFSET INVALID_FCN	;21 - Invalid IOCTL function	    @D2A
	   DW	OFFSET INVALID_FCN	;22 - Invalid IOCTL function	    @D2A
	   DW	OFFSET GET_LOG_DEVICE	;23 - Get Logical Device	    @D2A
MAX_CMD    EQU	($-CMD_TABLE)/2 	; Highest valid command follows
	   DW	OFFSET SET_LOG_DEVICE	;24 - Set Logical Device	    @D2A

;------------------------------------------------------------------------------;
;	Device "interrupt" entry point                                         ;
;------------------------------------------------------------------------------;
IRPT	PROC	FAR		; Device interrupt entry point

; First we must save all the registers that we use so that when we return to
; DOS the registers are not changed.

	PUSH	DS		; Save the segment registers modified
	PUSH	ES

	CMP	CS:ISmodel_80,-1; Did we already check what machine we are  @D2A
	JNE	DID_CHECK	;   running on? 			    @D2A

	MOV	CS:RBX,BX	; Save BX				    @P1A
	MOV	BX,0FFFFH	; Check the model byte at FFFF:000E    @D0A @P1M
	MOV	ES,BX		;   to see if we're running on a       @D0A @P1M
	MOV	BX,0EH		;   model_80 (PS/2 model 80).	       @D0A @P1M
	CMP	BYTE PTR ES:[BX],model_80 ;				    @P1A
	MOV	BX,CS:RBX	; Restore BX			       @P1A @D2M
	JNE	NO_model_80

	MOV	CS:ISmodel_80,1 ; Set the flag saying we're on a       @P1A @D2M
	JMP	DID_CHECK	;   model_80				    @D2A

NO_model_80:
	MOV	CS:ISmodel_80,0 ; Set the flag saying we're not on a        @D2M
				;   model_80

DID_CHECK:			;					     D2A
	CMP	ISmodel_80,1	; Are we on a model_80? 		    @D2A
	JE	PUSH32		; If so, go save the 32 bit registers	    @P1A

; Push 16 bit registers onto the stack.

	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	DI
	PUSH	SI
	PUSH	BP

	JMP	PUSHED		;					    @P1A

; Push 32 bit registers onto the stack					     P1A
				;					    @D2D
PUSH32: DATAOV			; Save all the 32 bit registers.  The	    @P1A
	PUSHA			;   model_80's BIOS uses 32 bit registers,  @P1A
				;   so we must not trash the high order      P1A
				;   words as well as the low order words.    P1A

PUSHED: CLD			; All moves go forward

	LDS	BX,CS:RH_PTRA	; Get the request header address passed to the
				;   "strategy" routine into DS:BX

	MOV	AL,RH.RHC_CMD	; Get the command code from the 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	; Then jump to error routine

	ADD	AX,AX		; Double command code for table offset since
				;   table entries are words
	MOV	DI,AX		; Put into index register for CALL
				;					    @D2D
;
; At entry to command processing routine:
;
;	DS:BX	= Request Header address
;	CS	= 386XMAEM code segment address
;	AX	= 0
;
	CALL	CS:CMD_TABLE[DI]	; Call routine to handle the command
	JMP	IRPT_CMD_EXIT


IRPT_CMD_HIGH:			; JMPed to if RHC_CMD > MAX_CMD
	MOV	AX,STAT_CMDERR	; Return "Invalid Command" error code
	OR	AX,STAT_DONE	; Add "done" bit to status word             @P3C
	MOV	RH.RHC_STA,AX	; Store status into request header

IRPT_CMD_EXIT:			; Return from command routine

; Restore the registers before returning to DOS.

	CMP	CS:ISmodel_80,1 ; Are we on a model_80? 		    @P1A
	JE	POP32		; Yes.	Then pop the 32 bit registers.	    @P1A

; Pop 16 bit registers off of the stack.

	POP	BP
	POP	SI
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX

	JMP	POPPED		;					    @P1A

; Pop 32 bit registers off of the stack.				     P1A
				;					     P1A
POP32:	DATAOV			;					    @P1A
	POPA			;					    @P1A

; Pop the segment registers off of the stack.

POPPED: POP	ES
	POP	DS

	RET
IRPT	ENDP

SUBTTL	Command Routines
PAGE

MEDIA_CHECK:			;
BLD_BPB:			;
INPUT_IOCTL:			; IOCTL input
INPUT:				;
INPUT_NOWAIT:			; Non-destructive input no wait
INPUT_STATUS:			; Input status
INPUT_FLUSH:			; Input flush
OUTPUT: 			;
OUTPUT_VERIFY:			;
OUTPUT_IOCTL:			; IOCTL output
OUTPUT_STATUS:			; Output status
OUTPUT_FLUSH:			; Output flush
DEVICE_OPEN:			;
DEVICE_CLOSE:			;
REMOVABLE_MEDIA:		;
INVALID_FCN:			;					    @D2A
GET_LOG_DEVICE: 		;					    @D2A
SET_LOG_DEVICE: 		;					    @D2A

	MOV	AX,STAT_GEN	; Return general error code		    @D2A
	OR	AX,STAT_DONE	; Add "done" bit to status word             @D2A
	MOV	RH.RHC_STA,AX	; Store status into request header	    @D2A

	RET

SUBTTL	Generic IOCTL Service Routine
PAGE

;------------------------------------------------------------------------------;
; This routine handles the Generic IOCTL call.	The Emulator provides an    D2A;
; interface through the Generic IOCTL call to query the number of XMA	    D2A;
; blocks available.  When the function code in the parameter list is 0 the  D2A;
; Emulator will return the number of XMA blocks available.  There are no    D2A;
; other functions uspported at this time.				    D2A;
;------------------------------------------------------------------------------;

GIP	EQU	ES:[DI] 	      ; 				    @D2A

GEN_IOCTL_PARM	STRUC		      ; 				    @D2A

GIOPLEN DW	?		      ; Length of the parameter list	    @D2A
GIOPFCN DW	?		      ; Function code			    @D2A
GIOPBLK DW	?		      ; Number of XMA blocks available	    @D2A

GEN_IOCTL_PARM	ENDS		      ; 				    @D2A

MAXFCN	EQU	0		      ; Highest function number allowed     @D2A

; Return codes								     D2A

GOODRET EQU	0		      ; Good return code		    @D2A
BADLEN	EQU	1		      ; Bad parameter list length	    @D2A
BADFCN	EQU	2		      ; Bad function number		    @D2A

GENERIC_IOCTL:			      ; 				     D2A

	LES	DI,RH.RH19_RQPK       ; Point ES:DI to the Generic IOCTL    @D2A
				      ;   request packet		     D2A

; First check to make sure the parameter list is long enough to return the   D2A
; number of XMA blocks. 						     D2A

	CMP	GIP.GIOPLEN,4	      ; Do we have at least four bytes?     @D2A
	JAE	GIP_CHKFCN	      ; Yup.  Go to check function number.  @D2A

	MOV	GIP.GIOPFCN,BADLEN    ; Nope.  Sorry.  Return the error     @D2A
	JMP	GIP_DONE	      ;   code and go to the end.	    @D2A

; Check if the function number in the parameter list is a valid function.    D2A

GIP_CHKFCN:			      ; 				     D2A
	CMP	GIP.GIOPFCN,MAXFCN    ; Is the function code less than or   @D2A
				      ;   equal to the maximum supported?    D2A
	JLE	GIP_CONT	      ; Yes. Good boy. You get to continue. @D2A

	MOV	GIP.GIOPFCN,BADFCN    ; No.  Shamey, shamey.  Set the bad   @D2A
	JMP	GIP_DONE	      ;   return code and go to the end.    @D2A

; Parameter list is OK.  Let's return the number of XMA blocks.              D2A

GIP_CONT:			      ; 				     D2A
	MOV	GIP.GIOPFCN,GOODRET   ; Set a good return code		    @D2A
	MOV	AX,CS:HI_XMA_BLK      ; Get the number of XMA blox	    @D2A
	MOV	GIP.GIOPBLK,AX	      ; Put it in the paramter list	    @D2A


GIP_DONE:			      ; 				     D2A
	MOV	RH.RHC_STA,STAT_DONE  ; Store done status and good return   @D2A
				      ;   code into request header	     D2A
	RET			      ; 				    @D2A

INT15F88   PROC    FAR		      ; 				     P7A

; The following is the interrupt chaining structure specified in the PC AT   P7A
; Technical Reference.							     P7A

	   JMP	   SHORT BEGIN	      ; 				     P7A

CHAINOFF   DW	   0		      ; Offest of the previous INT 15 vect. @P7A
CHAINSEG   DW	   0		      ; Segment of the previous INT 15 vect.@P7A
SIGNATURE  DW	   424BH	      ; Says we're doing chaining           @P7A
FLAGS	   DB	   0		      ; 				    @P7A
FIRST	   EQU	   80H		      ; 				    @P7A
	   JMP	   SHORT RESET	      ; 				    @P7A
RESERVED   DB	   7 DUP (0)	      ; 				    @P7A

; OK.  Let's see if the user asked for function 88H, query memory size.      P7A
; The function number is specified in the AL register.	If it's              P7A
; function88h, then put the memory size in AX and IRET to the caller.	     P7A
; Else, just pass the interrupt on to the guy who was installed in the INT   P7A
; 15 vector before us.							     P7A

BEGIN:	   CMP	   AH,88H	       ; Is it function 88H?		    @P7A
	   JNE	   NOT_MINE	       ; It's not ours to handle            @P7A

	   MOV	   AX,CS:EXT_MEM       ; Put the number of K into AX	    @P7A
	   IRET 		       ; Return to the caller		    @P7A

NOT_MINE:  JMP	   CS:DWORD PTR CHAINOFF
				       ; Pass the interrupt on to the	    @P7A
				       ;   previously installed vector	    @P7A

RESET:	   RET			       ; This, too, is part of the interrupt@P7A
				       ;   chaining structure.	We will just@P7A
				       ;   return on a call to reset.  Note @P7A
				       ;   that this is a far return.	    @P7A
INT15F88   ENDP 		       ;				    @P7A

LEAVE_RES  LABEL NEAR		      ; Leave code up to here resident.@D0A @D2M

SUBTTL	Initialize Routine
PAGE
INIT_P1:

	PUSH	ES			; Save our code segment at the	    @D0A
	MOV	DI,0			;   fixed location 0:4F4.  This     @P3C
	MOV	ES,DI			;   gives us a quick way to find CS @P3C
	MOV	DI,4F4H 		;   and also enables us to break on @P3C
	MOV	ES:[DI],CS		;   a write to 0:4F4 which helps us @P3C
	POP	ES			;   find the code on the ICE386.    @D0A
	MOV	AH,DISPSTRG		; Display the welcome message.	    @D0A
	MOV	DX,OFFSET WELCOME	;				    @D0A
	PUSH	DS			; Save DS since DS:BX points to the @D0A
	PUSH	CS			;   request header		    @D0A
	POP	DS			; DS:DX points to the message	    @D0A
	INT	21H			; Display the message		    @D0A
	POP	DS			; Restore DS			    @D0A
					;				    @P3D
	MOV	RH.RH0_ENDS,CS		; Set the segment and offset of the end
	MOV	RH.RH0_ENDO,OFFSET LEAVE_RES ; of code to leave resident
	MOV	RH.RHC_STA,STAT_DONE	; Store "done" status into request
					;   header
	CMP	CS:ISmodel_80,1 	; Check if we're on a model_80 @D0A @P1C
	JE	CONT			; If so, then continue		    @D0A
					;				     D0A
	MOV	RH.RH0_ENDO,0		; Leave nothing resident	    @D0A
	MOV	AX,STAT_GEN		; Return general error code	    @D0A
	OR	AX,STAT_DONE		; Add "done" bit to status word@D0A @P3C
	MOV	RH.RHC_STA,AX		; Store status into request header  @D0A
					;				     D0A
	MOV	AH,DISPSTRG		; Display the message that we are   @D0A
	MOV	DX,OFFSET NO_80386	;   not on a model_80		    @D0A
	PUSH	CS			;				    @D0A
	POP	DS			;				    @D0A
	INT	21H			;				    @D0A
					;				     D0A
	RET				;				     D0A
					;				     D0A
CONT:					;				    @D0M
	SMSW	AX			; Get machine status register
	TEST	AL,1			; Check if the processor is already in
					;   protect mode.  If so, then someone
					;   else (maybe us) has already taken
					;   over protect mode.
	JZ	STILLOK 		; If not, keep going		    @D0C

	MOV	RH.RH0_ENDO,0		; Leave nothing resident	    @D0A
	MOV	AX,STAT_GEN		; Return general error code	    @D0A
	OR	AX,STAT_DONE		; Add "done" bit to status word@D0A @P3C
	MOV	RH.RHC_STA,AX		; Store status into request header  @D0A
					;				     D0A
	MOV	AH,DISPSTRG		; Display the message that protect  @D0A
	MOV	DX,OFFSET WAS_INST	;   mode is taken.		    @D0A
	PUSH	CS			; DS:DX points to the message	    @D0A
	POP	DS			;				    @D0A
	INT	21H			;				    @D0A

	RET				;
STILLOK:			       ;				     D0A
	PUSH	0DEADH		      ; Push stack delimiter
				      ; Don't have to set character ID      @D2D
	CALL	GET_PARMS	      ; Get the MOVEBLOCK buffer size if
	jnc	StillOK1
		MOV	RH.RH0_ENDO,0		; Leave nothing resident	    @D0A
		MOV	AX,STAT_GEN		; Return general error code	    @D0A
		OR	AX,STAT_DONE		; Add "done" bit to status word@D0A @P3C
		MOV	RH.RHC_STA,AX		; Store status into request header  @D0A
					;				     D0A
		pop	ax
		ret		      ; exit program

StillOK1:
				      ;   one was specified
	CLI			      ; Disable interrupts

	PUSH	CS		      ; Now we can point DS to our own	    @P3A
	POP	DS		      ;   code segment			    @P3A
				      ; 				   2@D2D
	MOV	AX,CS
	MOV	REAL_CS,AX	      ; Save real CS for when we	    @P3C
	MOV	AX,SS		      ;   switch to protect mode
	MOV	REAL_SS,AX	      ; Save real SS			    @P3C
	MOV	AX,SP
	MOV	REAL_SP,AX	      ; Save real SP			    @P3C

;------------------------------------------------------------------------------;
;	Enable address line A20 					       ;
;------------------------------------------------------------------------------;

				      ; 				   3@P4D
	CALL	GATE_A20

	INT	11H		      ; Get the BIOS equipment flags
	AND	AL,30H		      ; Bits 5 and 6 on means it's a mono
	CMP	AL,30H
	JE	LEAVEBW
	MOV	CRT_SELECTOR,C_CCRT_PTR ; Set the CRT selector to color display
LEAVEBW:
	MOV	AH,88H		      ; Get number of 1k blocks above 1M
	INT	15H
	ADD	AX,1024 	      ; Add 640K for the memory below 640K  @P7C
	MOV	MAXMEM,AX	      ; Save for later

; Get the maximum XMA block number and save it in the header up front	     D0A
; All memory is treated as XMA memory.					     D1C
				      ; 				     D0A
	SUB	AX,BUFF_SIZE	      ; Can't use the MOVEBLOCK buffer for  @D0A
				      ;   XMA memory.  AX = number of K      D0A
				      ;   available.			     D0A
	SUB	AX,(1024-640) +128    ; Subtract 128k for the Emulator code	;an000; dms;
	SHR	AX,2		      ; Divide by four to get the number of @D0A
				      ;   4K blocks			     D0A
	DEC	AX		      ; Subtract 1.  This converts the	    @P3A
				      ;   number of blocks available to the  P3A
				      ;   highest block number available.    P3A
				      ;   Block numbers are zero based.      P3A
	MOV	HI_XMA_BLK,AX	      ; Save it in the header		     D0A

;------------------------------------------------------------------------------;
;     Now lets relocate ourselves to high memory.			       ;
;------------------------------------------------------------------------------;

	MOV	AX,HIGH_SEG	      ; Set ES to the highest segment value
	MOV	ES,AX
	MOV	DI,0		      ; ES:DI points to the place to relocate to
	MOV	AX,CS
	MOV	DS,AX
	MOV	SI,0		      ; DS:SI points to our code to be moved
	MOV	CX,DDSIZE/2	      ; Length of code / 2 since moving words
	CLD
	REP	MOVSW		      ; Copy myself to high memory

	JUMPFAR NEXT,HIGH_SEG	      ; Jump to my relocated code
NEXT:

	MOV	AX,HIGH_SEG	      ; Set DS to be the same as CS
	MOV	DS,AX
;------------------------------------------------------------------------------;
;     The machine is still in real mode.  Zero out GDT and IDT ram.	       ;
;------------------------------------------------------------------------------;

	MOV	DI,GDT_LOC	      ; DI points to GDT location

	MOV	CX,(GDT_LEN+SIDT_LEN)/2 ; Set GDT and IDT to zero
	MOV	AX,0		      ; Store zeroes for now
	REP	STOSW

;------------------------------------------------------------------------------;
;    Use good-old real-mode selectors to set up the page tables.  The	       ;
;    page directory is a 4K block that is placed just before the	       ;
;    beginning of the GDT and on a 4K boundary.  Note that the DATAOV	       ;
;    macro creates a prefix for the following instruction so that its	       ;
;    data references are 32 bits wide.					       ;
;------------------------------------------------------------------------------;

	DATAOV
	SUB	AX,AX		      ; Clear EAX (32 bit AX reg.)
	MOV	AX,HIGH_SEG	      ; Get the current code segment
	DATAOV
	SUB	BX,BX		      ; Clear EBX (32 bit BX reg.)
	MOV	BX,GDT_LOC/16	      ; Load the offset of the GDT, converted
				      ;   to paragraphs
	DATAOV			      ; Add it on to the current code segment
	ADD	AX,BX		      ;   to get the segment address of the GDT.
				      ;   This will be over 1M, so use 32 bits.
	AND	AX,0FF00H	      ; Round down to nice 4k boundary
	DATAOV
	SUB	BX,BX		      ; Clear EBX
	MOV	BX,4096/16	      ; Load with the size of the page directory
				      ;   converted to paragraphs
	DATAOV			      ; Subtract the number of paragraphs needed
	SUB	AX,BX		      ;   for the page directory
	DATAOV
	SHL	AX,4		      ; Convert from paragraphs to bytes
	CMOV	CR3,EAX 	      ; Load the address of the page directory
				      ;   into CR3
	DATAOV
	SUB	BX,BX		      ; Clear EBX
	MOV	BX,HIGH_SEG	      ; Load our current code segment
	DATAOV
	SHL	BX,4		      ; Convert from paragraphs to bytes
	DATAOV
	SUB	AX,BX		      ; Subtract from the address of the page
				      ;   directory to get the offset of the
				      ;   directory in our code segment
	MOV	SGTBLOFF,AX	      ; Save for later

; Now let's clear the page directory

	MOV	CX,2048 	      ; Length is 4K/2 since storing words
	DATAOV
	MOV	DI,AX		      ; ES:EDI points to beginning of directory
	MOV	AX,0
	REP	STOSW		      ; Clear the page directory!

;------------------------------------------------------------------------------;
;   Initialize the first directory entries to our page tables		       ;
;------------------------------------------------------------------------------;

	CMOV	EAX,CR3 	; Get back CR3 - the address of the page dir.
	DATAOV
	MOV	DI,SGTBLOFF	; Point ES:EDI to first entry in directory
	DATAOV
	SUB	BX,BX		; Clear EBX
	MOV	BX,MEG_SUPPORTED/4*4096 ; Load the size of the page tables.
				; Each page table maps 4M of memory, so divide
				;   the number of Meg supported by 4 to get the
				;   number of page tables.  Each page table is
				;   4K in size, so multiply by 4K.
	DATAOV
	SUB	AX,BX		; Subtract the size needed for the page tables
				;   from the address of the page directory to
				;   get the address of the first page table.
	ADD	AX,7		; Set the present bit and access rights.
				;   This converts the address to a valid entry
				;   for the page directory.
	DATAOV
	MOV	NORMPAGE,AX	; Save for later
	MOV	CX,MEG_SUPPORTED/4 ; Load the number of page tables into CX
	DATAOV
	SUB	BX,BX		; Clear EBX
	MOV	BX,1000H	; Set up 4k increment
;
; Now we load the page directory.  EAX contains the address of the first
; page table, EBX contains 4K, CX contains the number of page tables, and
; ES:EDI (32 bit DI reg.) points to the first page directory entry.  Now what
; we do is stuff EAX into the 32bits pointed to by EDI.  EDI is then auto-
; incremented by four bytes, because of the 32 bit stuff, and points to the
; next page directory entry.  (Page directory and page table entries are four
; bytes long.)	Then we add the 4K in EBX to the address in EAX making EAX
; the address of the next page table.  This is done for the number of page
; table entries in CX.	Pretty slick, huh?
;
LPT:
	DATAOV			; Stuff the page table address into the
	STOSW			;   page directory
	DATAOV			; Add 4K to the page table address in EAX
	ADD	AX,BX		;   so that it contains the address of the
				;   next page table
	LOOP	LPT		; Do it again

; Now calcuate the offset from our code segment of the page tables

	DATAOV
	SUB	BX,BX		; Clear EBX
	MOV	BX,HIGH_SEG	; Load our current code segment
	DATAOV
	SHL	BX,4		; Convert paragraphs to bytes
	DATAOV			; Load EAX with the address of the first
	MOV	AX,NORMPAGE	;   page table
	DATAOV
	SUB	AX,BX		; Convert EAX to an offset
	AND	AL,0F8H 	; AND off the access rights
	MOV	PGTBLOFF,AX	; Save for later

;------------------------------------------------------------------------------;
;   Initialize the page tables						       ;
;------------------------------------------------------------------------------;

	MOV	DI,PGTBLOFF	      ; ES:DI points to the first page table
	DATAOV
	SUB	AX,AX		      ; Zero EAX
	ADD	AX,7		      ; Set the present and access rights
	MOV	CX,MEG_SUPPORTED/4*1024 ; Load CX with the number of page table
				      ;   entries to initialize.  As mentioned
				      ;   above, the number of page tables =
				      ;   number of Meg / 4.  There are 1K
				      ;   entries per table so multiply by 1K
	DATAOV
	SUB	BX,BX		      ; Clear EBX
	MOV	BX,1000H	      ; Set up 4k increment
;
; As with the page directory, we use a tight loop to initialize the page tables.
; EAX contains the address of the first page frame, which is 0000, plus the
; access rights.  EBX contains a 4K increment.	ES:DI points to the first entry
; in the first page table.  CX contains the number of page table entries to
; initialize.  The stuff and increment works the same as for the page directory
; with an added touch.	Note that this does all the page tables in one fell
; swoop.  When we finish stuffing the last address into the first page table
; the next place we stuff is into the first entry in the second page table.
; Since our page tables are back to back we can just zoom up the page tables
; incrementing by 4K as we go and thus initialize all the page tables in one
; fell swoop.
;
BPT:
	DATAOV			      ; Stuff the page frame address into the
	STOSW			      ;   page table
	DATAOV
	ADD	AX,BX		      ; Next 4k page frame
	LOOP	BPT

;------------------------------------------------------------------------------;
;   Now set up the first 64K over 1M to point to point to the first 64K        ;
;   in low memory to simulate the segment wrap over 1M. 		       ;
;   For now will set it up to point to itself and try to get DOS to load       ;
;   the device driver up there.  Will find out if anyone tries to alter        ;
;   it because it will be marked for system use only.			       ;
;------------------------------------------------------------------------------;

	MOV	DI,1024 	      ; 1M offset into page table
	ADD	DI,PGTBLOFF	      ; Page table offset
	MOV	AX,10H		      ; Set EAX to contain 1M address by loading
	DATAOV			      ;   it with 10H and shifting it 16 bits to
	SHL	AX,16		      ;   get 00100000.  (Same as 10000:0)
	ADD	AX,5		      ; Present, system use, read only
	MOV	CX,16		      ; 16 entries = 64k
BPT2:
	DATAOV
	STOSW			      ; Stuff the address in the page table
	DATAOV
	ADD	AX,BX		      ; Next 4k page frame
	LOOP	BPT2

PAGE
;------------------------------------------------------------------------------;
;	Build the Global Descriptor Table and load the GDT register.	       ;
;------------------------------------------------------------------------------;
	CALL	GDT_BLD

	MOV	DI,GDT_PTR	      ; Get the offset of the GDT descriptor
	ADD	DI,GDT_LOC	      ;   located in the GDT
	MOV	BP,DI		      ; Transfer the offset to BP
	LGDT	ES:FWORD PTR[BP]      ; Put the descriptor for the GDT into
				      ;   the GDT register

PAGE
;------------------------------------------------------------------------------;
;	Build and initialize the system Interrupt Descriptor Table,	       ;
;	then load the IDT register.					       ;
;------------------------------------------------------------------------------;
	CALL	SIDT_BLD

	MOV	DI,MON_IDT_PTR	      ; Get the offset of the IDT descriptor
	ADD	DI,GDT_LOC	      ;   located in the GDT
	MOV	BP,DI		      ; Transfer the offset to BP

	LIDT	ES:FWORD PTR[BP]      ; Put the descriptor for the IDT into
				      ;   the IDT register

PAGE
;------------------------------------------------------------------------------;
;	At this point we prepare to switch to virtual mode.  The first	       ;
;	instruction after the LMSW that causes the switch must be a	       ;
;	jump far to set a protected mode segment selector into CS.	       ;
;------------------------------------------------------------------------------;

	MOV	AX,VIRTUAL_ENABLE     ; Machine status word needed to
	LMSW	AX		      ;  switch to virtual mode

	JUMPFAR DONE,SYS_PATCH_CS     ; Must purge pre-fetch queue
				      ;   and set selector into CS
DONE:
PAGE
;------------------------------------------------------------------------------;
; Initialize all the segment registers					       ;
;------------------------------------------------------------------------------;

	MOV	AX,SYS_PATCH_DS       ; Load DS, ES, and SS with the selector
	MOV	DS,AX		      ;   for our data area.  This is the same
	MOV	ES,AX		      ;   as our code area but has read/write
				      ;   access.
	MOV	SS,AX
	MOV	SP,OFFSET SP_INIT

	PUSH	0002H		      ; Clean up our flags.  Turn off all bits
	POPF			      ;   except the one that is always on.

;------------------------------------------------------------------------------;
; Load the LDTR to avoid faults 					       ;
;------------------------------------------------------------------------------;

	MOV	AX,SCRUBBER.TSS_PTR   ; Load DS with the data descriptor for
	MOV	DS,AX		      ;   the virtual machine's TSS
	MOV	AX,SCRUBBER.VM_LDTR   ; Get the LDTR for virtual machine
	MOV	DS:VM_LDT,AX	      ; Set LDTR in TSS
	LLDT	AX		      ; Set the LDTR.  Temporary for now.

; Have to always have space allocated for the dispatch task TSS

	MOV AX,SCRUBBER.VM_TR	      ; Low mem gets clobbered without this @P5C
	LTR AX			      ; Set current Task Register
				      ; This TSS is located right after the IDT

PAGE
;------------------------------------------------------------------------------;
;	Now we initialize the TSS (Task State Segment) for the one and only    ;
;	virtual 8086 task.  This task encompasses everything that runs in real ;
;	mode.  First we clear the TSS and its I/O bit map.  Then we initialize ;
;	the bit map for all the I/O ports we want to trap.  Then we set up the ;
;	registers for the V86 task.  These registers are given the same values ;
;	as we got on entry.  IP is set to point to TEST_EXIT.		       ;
;------------------------------------------------------------------------------;

	MOV	AX,SCRUBBER.TSS_PTR   ; Load ES and DS with the descriptor
	MOV	DS,AX		      ;   for the VM's TSS with read/write
	MOV	ES,AX		      ;   access rights
	CLD
	MOV	DI,0		      ; Point ES:DI to the beginning of the TSS
	MOV	AX,0		      ; Clear AX
	MOV	BX,0		      ; Clear BX
	MOV	CX,TSS_386_LEN	      ; Load CX with the length of the TSS
	REP	STOSB		      ; Clear the TSS
	MOV	CX,TSS_BM_LEN	      ; Load CX with the length of the I/O bit
				      ;   map.	The bit map immediately follows
				      ;   the TSS and is in the TSS segment.
	REP	STOSB		      ; Clear the bit map
	MOV	AL,0FFH 	      ; Intel requires this byte
	STOSB

;
; Now set up the bit map.  Turn on bits for I/O ports that we want to trap.
;

	MOV	DI,0+TSS_386_LEN      ; Set bits 0,2,4,6 to 1 - DMA ports
	MOV	AL,055H
	STOSB
	MOV	DI,1+TSS_386_LEN      ; Set C to 1 - DMA port
	MOV	AL,010H
	STOSB
	MOV	DI,3+TSS_386_LEN      ; Set 18,1A to 1 - DMA ports
	MOV	AL,005H
	STOSB
	MOV	DI,16+TSS_386_LEN     ; Set 80-8f to 1s - DMA page ports
	MOV	AL,0FFH 	      ;  + manufacturing port for ctl-alt-del
	STOSB
	STOSB
	MOV	DI,0680H/8+TSS_386_LEN ; Set Roundup manuf. port to 1
	MOV	AL,001H
	STOSB
	MOV	DI,31A0H/8+TSS_386_LEN ; Set 31a0-31a7 to 1s (XMA)
	MOV	AL,0FFH
	STOSB

	MOV	WORD PTR [BX].ETSS_BM_OFFSET,TSS_386_LEN
				      ; Put the bit map offset in the TSS
	MOV	WORD PTR [BX].ETSS_SP0,OFFSET SP_INIT
				      ; Put our SP as the SP for privilege
				      ;   level 0
	MOV	WORD PTR [BX].ETSS_SS0,SYS_PATCH_DS
				      ; Put our SS as the SS for privilege
				      ;   level 0

; Next we set up the segment registers

	MOV  WORD PTR [BX].ETSS_GS,SEG PROG	    ; GS - our code segment
	MOV  WORD PTR [BX].ETSS_FS,SEG PROG	    ; FS - our code segment
	MOV  WORD PTR [BX].ETSS_DS,SEG PROG	    ; DS - our code segment
	MOV  WORD PTR [BX].ETSS_ES,SEG PROG	    ; ES - our code segment

; Next the SS,SP

	MOV  AX,CS:REAL_SS	; Set the real mode SS as the SS for the task
	MOV  WORD PTR [BX].ETSS_SS,AX
	MOV  AX,CS:REAL_SP	; Set the real mode SP as the SP for the task
	MOV  WORD PTR [BX].ETSS_SP,AX

; The flags register

	MOV  WORD PTR [BX].ETSS_FL2,2	 ; Set the VM flag.  Task is a V86 task.
	MOV  WORD PTR [BX].ETSS_FL,0202H ; Set interrupts enabled

; Set up CS and IP

	MOV  AX,CS:REAL_CS	; Set the real mode CS as the CS for the task
	MOV  WORD PTR [BX].ETSS_CS,AX ; This is the CS we got when we loaded
				;   in low memory, before relocating
	MOV  AX,OFFSET PROG:TEST_EXIT ; Set IP to the label TEST_EXIT below.
	MOV  WORD PTR [BX].ETSS_IP,AX

; The LDTR

	MOV  WORD PTR [BX].ETSS_LDT,SCRUBBER.VM_LDTR

; And finally, CR3, the page directory base register

	CMOV	EAX,CR3 	; Get CR3
	DATAOV
	MOV  WORD PTR [BX].ETSS_CR3,AX ; Save it in the TSS

PAGE
;------------------------------------------------------------------------------;
;	Now initialize our wonderful XMA page tables.  Each table maps 4M.     ;
;	There is one table for each XMA bank since 4M is enough to map the     ;
;	1M address space.  All the XMA tables are initialized to point to      ;
;	the real memory at 0 to 4M.  This is done by just copying the page     ;
;	table entry for 0 to 4M that was initialized above.		       ;
;------------------------------------------------------------------------------;

	MOV	AX,SYS_PATCH_DS    ; Load DS with the selector for our data
	MOV	DS,AX
	MOV	SI,PGTBLOFF	   ; DS:SI point to the real page table for 0-4M
	MOV	AX,XMA_PAGES_SEL   ; Load ES with the selector for the XMA pages
	MOV	ES,AX
	SUB	DI,DI		   ; ES:DI point to the first XMA page table
	MOV	CX,2048 	   ; Copy 4K / 2 since we're copying words
	REP	MOVSW		   ; Copy the first XMA page table
;
; Now ES:DI points to the second XMA page table.  Set DS:SI to point to the
; first XMA page table as the source for the copy.  Now we can put a count
; of 15 page tables in CX.  After each page is copied it is used as the source
; for the next page.  This method lets us zip up the page tables initializing
; them all to be the same as the original page table for 0 - 4M.
;
	MOV	AX,XMA_PAGES_SEL   ; Load DS with the selector for the XMA page
	MOV	DS,AX		   ;   tables
	SUB	SI,SI		   ; DS:SI points to the first XMA page table
	MOV	CX,2048*15	   ; Copy 15 more page tables
	REP	MOVSW		   ; Copy to the other 15 XMA ID'S page tables

;									     D1A
; Set the first page directory entry to point to the page table for bank 0.  D1A
; This is another way of saying, "Let's make bank 0 the active bank."  We    D1A
; are now emulating the  XMA 2 card along with its initialization device     D1A
; driver, INDXMAA.SYS.	When the device driver exits, it leaves the XMA 2    D1A
; card enabled and set to bank 0.  Therefore, we must do the same.	     D1A
;									     D1A
				   ;					     D1A
	MOV	AX,SYS_PATCH_DS    ; Load DS and ES with our data segment   @D1A
	MOV	DS,AX		   ;   selector 			    @D1A
	MOV	ES,AX		   ;					    @D1A
	MOV	DI,SGTBLOFF	   ; Point ES:DI to the first page	    @D1A
				   ;   directory entry			     D1A
	DATAOV			   ; Load AX with the page directory entry  @D1A
	MOV	AX,XMAPAGE	   ;   for the first XMA page table	    @D1A
	DATAOV			   ; Stuff the address of the page table    @D1A
	STOSW			   ;   for bank 0 into the page directory   @D1A

PAGE
;------------------------------------------------------------------------------;
;	And now, the moment you've all been waiting for -- TURN ON THE PAGING  ;
;	MECHANISM!!!							       ;
;------------------------------------------------------------------------------;

	CMOV	EAX,CR0 	; Get CR0				    @P5A
	MOV	BX,8000H	; Set up BX to OR on the Paging Enable bit  @P5C
	DATAOV
	SHL	BX,16		; It's the one all the way on the left      @P5C
	DATAOV			;					    @P5A
	OR	AX,BX		; Set the paging enabled bit		    @P5A
	OR	AL,02H		; Set co-processor bit on		    @P5A
	AND	AL,0F7H 	; Turn off Task Switch bit		    @P5C
	CMOV	CR0,EAX 	; Here we go...

; Make sure high order bits of ESP are zero - a1 errata

	MOV	AX,SP		; Save SP in AX 'cause it changes when we do...
	PUSH	0		;   this PUSH.	Push 0 for high 16 bits of ESP
	PUSH	AX		; Push low 16 bits of SP
	DATAOV
	POP	SP		; Pop 32 bit ESP!

PAGE
;------------------------------------------------------------------------------;
;	Now we give control back to the V86 task by setting up the stack    P5C;
;	for an IRET back to the V86 task.  This requires putting the V86    P5C;
;	task's segment registers, SS and ESP, and the EFLAGS, CS and IP on  P5C;
;	the stack.  The 80386 puts all these values on the stack when it    P5C;
;	interrupts out of V86 mode, so it expects them there on an IRET     P5C;
;	back to V86 mode.  But really we are giving control back to	       ;
;	ourself.  The CS:IP on the stack point to the label TEST_EXIT	       ;
;	below, but it is in the copy of the emulator that was originally       ;
;	loaded, not the copy that was relocated to high memory and is now      ;
;	running in protect mode.  This clever trick will result in the	       ;
;	original copy of the emulator returning to DOS which will continue     ;
;	to load the rest of the system.  The system will come up completely    ;
;	unaware that it is running in a small universe of a V86 task which     ;
;	is being monitored by the XMA emulator. 			       ;
;------------------------------------------------------------------------------;


	MOV	AX,SCRUBBER.TSS_PTR   ; Load DS with the descriptor for the @P5A
	MOV	DS,AX		      ;   VM's TSS with read/write access   @P5A
	MOV	BX,0		      ;   VM's TSS with read/write access   @P5A
;									     P5A
; Set up our stack for an IRET to the V86 task.  This is an inter-level      P5A
; IRET to a V86 task so we need the V86 task's SS, ESP, ES, DS, FS and GS    P5A
; as well as his EFLAGS, EIP and CS.					     P5A
;									     P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_GS ; Put V86 task's GS on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_FS ; Put V86 task's FS on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_DS ; Put V86 task's DS on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_ES ; Put V86 task's ES on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_SS ; Put V86 task's SS on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_SP ; Put V86 task's ESP on the stack     @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_FL ; Put V86 task's EFLAGS on the stack  @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_CS ; Put V86 task's CS on the stack      @P5A
	DATAOV			      ; 				    @P5A
	PUSH	WORD PTR [BX].ETSS_IP ; Put V86 task's EIP on the stack     @P5A
	DATAOV			      ; 				    @P5A
	IRET			      ; 				    @P5A
				      ; 				    @P5D

TEST_EXIT:			; We are now running in V86 mode
	POP	AX		; Pop the stack until our DEAD delimiter is
	CMP	AX,0DEADH	;   found
	JNE	TEST_EXIT

; Replace the interrupt 15 vector with our handler (INT15F88).		     P7A

	MOV	AH,GET_VECT	; Get the current vector at interrupt 15H   @P7A
	MOV	AL,15H		;					    @P7A
	INT	21H		;					    @P7A

	MOV	CS:CHAINSEG,ES	; Save it in the chaining header in	    @P7A
	MOV	CS:CHAINOFF,BX	;   INT15F88				    @P7A

	MOV	AH,SET_VECT	; Set the entry point of INT15F88 as the    @P7A
	MOV	AL,15H		;   new interrupt 15 vector		    @P7A
	PUSH	CS		;					    @P7A
	POP	DS		;					    @P7A
	MOV	DX,OFFSET INT15F88 ;					    @P7A
	INT	21H		;					    @P7A

; Copy the number of K for extended memory from BUFF_SIZE to EXT_MEM.  This  P7A
; is needed because BUFF_SIZE does not stay resident, EXT_MEM does.	     P7A

	MOV	AX,BUFF_SIZE	;					    @P7A
	MOV	EXT_MEM,AX	;					    @P7A

; Issue the message that says we installed successfully

	MOV	AH,DISPSTRG	; Set AH to DOS display string function     @D0A
	MOV	DX,OFFSET GOODLOAD ;					    @D0A
	PUSH	CS		;					    @D0A
	POP	DS		; DS:DX points to the message		    @D0A
	INT	21H		; Display the message			    @D0A

	RET			; Return to IRPT which called INIT_P1

SUBTTL	Gate A20
PAGE
;------------------------------------------------------------------------------;
; GATE_A20								       ;
;	This routine controls a signal which gates address bit 20.	       ;
;	Bit 2 of port 92H controls the enabling of A20.  If bit 2 is on,    P4C;
;	then A20 is enabled.  Conversely, if bit 2 is off, A20 is disabled. P4C;
;									       ;
;------------------------------------------------------------------------------;

; Equates for the Gate A20 enable

ENABLE_A20	EQU	02H	; Bit 2 of port 92H turns on A20	    @P4C

GATE_A20    PROC

	IN	AL,92H		; Get the current value of port 92	    @P4A
	OR	AL,ENABLE_A20	; Turn on the bit to enable A20 	    @P4A
	OUT	92H,AL		; Send it back out to port 92		    @P4A
	RET
				;					  15@P4D
GATE_A20	ENDP

SUBTTL	GET_PARMS parameter line scan
PAGE
;------------------------------------------------------------------------------;
; GET_PARMS								       ;
; This procedure converts the numeric parameter following the DEVICE statement ;
; in the CONFIG.SYS file to a binary number and saves it in BUFF_SIZE.	The    ;
; number is rounded up to the nearest 16K boundary.			       ;
;									       ;
; 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

	PUSH	DS		; Save DS
	push	bx		; save bx					;an000; dms;

	LDS	SI,RH.RH0_BPBA	; DS:SI point to all text after "DEVICE="
				;   in CONFIG.SYS
	XOR	AL,AL		; Start with a null character in AL.

;------------------------------------------------------------------------------;
; Skip until first delimiter is found.	There may be digits in the path string.;
;									       ;
; DS:SI points to  \pathstring\386XMAEM.SYS nn nn nn			       ;
; The character following 386XMAEM.SYS may have been changed to a null (00H).  ;
; All letters have been changed to uppercase.				       ;
;------------------------------------------------------------------------------;

GET_PARMS_A:
	CALL	GET_PCHAR	; Get a character from the parameter string
	JZ	Get_Parms_Null	; The zero flag is set if the end of the line
				;   is found.  If so, then exit.

; Check for various delimeters

	OR	AL,AL		; Null
	JZ	GET_PARMS_B
	CMP	AL,' '          ; Blank
	JE	GET_PARMS_B
	CMP	AL,','          ; Comma
	JE	GET_PARMS_B
	CMP	AL,';'          ; Semi-colon
	JE	GET_PARMS_B
	CMP	AL,'+'          ; Plus sign
	JE	GET_PARMS_B
	CMP	AL,'='          ; Equals
	JE	GET_PARMS_B
	CMP	AL,TAB		; Tab
	JNE	GET_PARMS_A	; Skip until delimiter or CR is found

GET_PARMS_B:			; Now pointing to first delimiter
	CALL	SKIP_TO_DIGIT	; Skip to first digit
	JZ	Get_Parms_C	; Found EOL, no digits remain

	CALL	GET_NUMBER	; Extract the digits and convert to binary
	jmp	Get_Parms_Found ; Parm found

Get_Parms_Null:

	xor	cx,cx		; set cx to 0					;an000; dms;

Get_Parms_Found:

	mov	bx,cx		; put cx value in bx				;an000; dms;

	cmp	cx,0		; 0 pages requested?				;an000; dms;
	jne	Get_Parm_Max	; allocate maximum number			;an000; dms;
		MOV	CS:BUFF_SIZE,0; Store buffer size
		jmp	Get_Parms_C

Get_Parm_Max:

	cmp	bx,64		; >= 64 pages requested?			;an000; dms;
	jnb	Get_Parms_64_Pg ; yes - continue				;an000; dms;
		mov	dx,offset Small_Parm	; Parm < 64 and > 0		;an000; dms;
		mov	ah,Dispstrg		; Display the welcome message.	;an000; dms;
		push	ds			; Save DS			;an000; dms;
		push	cs			;				;an000; dms;
		pop	ds			; DS:DX points to the message	;an000; dms;
		int	21h			; Display the message		;an000; dms;
		pop	ds			; Restore DS			;an000; dms;
		stc				; flag an error occurred	;an000; dms;
		jmp	Get_Parms_C		; exit routine			;an000; dms;

Get_Parms_64_Pg:

	mov	ax,bx		; prepare to adjust to Kb value 		;an000; dms;
	mov	cx,10h		; 16Kb per page 				;an000; dms;
	xor	dx,dx		; clear high word				;an000; dms;
	mul	cx		; get Kb value					;an000; dms;

	mov	bx,ax		; store page Kb value in bx			;an000; dms;
	add	bx,128		; adjust for emulator code

	mov	ah,88h		; get number of 1k blocks above 1Mb		;an000; dms;
	int	15h		;

	sub	ax,bx		; get number of blocks to allocate for extended ;an000; dms;
	jnc	Get_Parms_Ext	; set extended memory value in buff size	;an000; dms;
		mov	dx,offset No_Mem	; not enough memory for parm
		mov	ah,Dispstrg		; Display the welcome message.	;an000; dms;
		push	ds			; Save DS			;an000; dms;
		push	cs			;				;an000; dms;
		pop	ds			; DS:DX points to the message	;an000; dms;
		int	21h			; Display the message		;an000; dms;
		pop	ds			; Restore DS			;an000; dms;
		stc				; flag an error 		;an000; dms;
		jmp	Get_Parms_C		; exit routine			;an000; dms;

Get_Parms_Ext:

	MOV	CS:BUFF_SIZE,ax ; Store buffer size
	clc

GET_PARMS_C:
	pop	bx		; restore bx					;an000; dms;
	POP	DS		; Restore DS

	RET

;------------------------------------------------------------------------------;
; GET_PCHAR -- Get a character from the parameter string into AL	       ;
;------------------------------------------------------------------------------;

GET_PCHAR PROC
	CMP	AL,CR		; Carriage return already encountered?
	JE	GET_PCHAR_X	; Don't read past end of line
	LODSB			; Get character from DS:SI, increment SI
	CMP	AL,CR		; Is the character a carriage return?
	JE	GET_PCHAR_X	; Yes, leave the zero flag set to signal end
				;   of line
	CMP	AL,LF		; No, is it a line feed?  This will leave the
				;   zero flag set if a line feed was found.
GET_PCHAR_X:
	RET

GET_PCHAR ENDP

;------------------------------------------------------------------------------;
; CHECK_NUM -- Check if the character in AL is a numeric digit, ASCII for      ;
;	       0 - 9.  The zero flag is set if the character is a digit,       ;
;	       otherwise it is reset.					       ;
;------------------------------------------------------------------------------;

CHECK_NUM PROC
	CMP	AL,'0'          ; If character is less than a "0" then it is not
	JB	CHECK_NUM_X	;   a number, so exit

	CMP	AL,'9'          ; If character is greater than a "9" then it is
	JA	CHECK_NUM_X	;   not a number, so exit

	CMP	AL,AL		; Set the zero flag to show it is a number
CHECK_NUM_X:
	RET			; Zero flag is left reset if character is not
CHECK_NUM ENDP			;   a number

;------------------------------------------------------------------------------;
; SKIP_TO_DIGIT -- Scan the parameter string until a numeric character is      ;
;		   found or the end of the line is encountered.  If a numeric  ;
;		   character is not found then the zero flag is set.  Else if  ;
;		   a character was found then the zero flag is reset.	       ;
;------------------------------------------------------------------------------;

SKIP_TO_DIGIT PROC
	CALL	CHECK_NUM	; Is the current character a digit?
	JZ	SKIP_TO_DIGIT_X ; If zero flag is set then it is a number

	CALL	GET_PCHAR	; Get the next character from the line
	JNZ	SKIP_TO_DIGIT	; Loop until first digit or CR or LF is found
	RET			; Fall through to here if digit not found

SKIP_TO_DIGIT_X:
	CMP	AL,0		; Digit found, reset the zero flag to show digit
	RET			;   was found
SKIP_TO_DIGIT ENDP

;------------------------------------------------------------------------------;
; GET_NUMBER -- Convert the character digits in the parameter string to a      ;
;		binary value.  The value is returned in CX, unless the	       ;
;		calculation overflows, in which case return a 0.  The next     ;
;		character after the digits is left in AL.		       ;
;------------------------------------------------------------------------------;

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		; Clear CX, the resulting number
	MOV	CS:GN_ERR,CL	; No overflow yet

GET_NUMBER_A:
	SUB	AL,'0'          ; Convert the ASCII character in AL 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	; Any overflow from AX goes into DX.  Any non-
				;   zero value in DL will signal an error
	ADD	AX,CX		; Add the new digit to ten times the previous
				;   digits
	XCHG	AX,CX		; New number now in CX
	CALL	GET_PCHAR	; Get the next character
	CALL	CHECK_NUM	; Check if it is numeric
	JZ	GET_NUMBER_A	; If so, then go back and add this digit to the
				;   result
	CMP	CS:GN_ERR,0	; Did we overflow?
	JE	GET_NUMBER_B	; If not, we're done
	XOR	CX,CX		; Return a zero result if overflow
GET_NUMBER_B:
	RET
GET_NUMBER ENDP

GET_PARMS ENDP

POST	ENDP

PROG	ENDS
	END