summaryrefslogtreecommitdiff
path: root/v4.0/src/DEV/XMAEM/INDEXMA.ASM
blob: c35dd24c10b4a6ca7b10c55f83c94465f7e0392d (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
PAGE    60,132
TITLE   INDEXMA - 386 XMA Emulator - XMA Emulation

COMMENT #
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*                                                                             *
* MODULE NAME     : INDEXMA                                                   *
*                                                                             *
*                    5669-196 (C) COPYRIGHT 1988 Microsoft Corp.              *
*                                                                             *
* DESCRIPTIVE NAME: Do the XMA emulation for the 80386 XMA Emulator           *
*                                                                             *
* STATUS (LEVEL)  : Version (0) Level (1.0)                                   *
*                                                                             *
* FUNCTION        : This module does the actual manipulation of the page      *
*                   tables to emulate the XMA card.  Using the 80386          *
*                   paging mechanism we let the 4K page frames represent      *
*                   the 4K XMA blocks on the XMA card.  We let the page       *
*                   tables represent the translate table.                     *
*                                                                             *
*                   The XMA "blocks" start at address 12000:0.  The        D1C*
*                   Emulator emulates the XMA 2 card with the INDXMAA      D1C*
*                   device driver.  On initial power up, the XMA 2 card is D1C*
*                   disabled.  The INDXMAA device driver then disables the D1C*
*                   memory from 0K to 640K and backs it with memory from   D1C*
*                   0K to 640K on the XMA 2 card.  The Emulator looks like D1C*
*                   it does the same thing.  The XMA blocks for 0K to 640K D1C*
*                   are taken from the system board memory from 0K to      D1C*
*                   640K.  This memory on the motherboard is treated as    D1C*
*                   XMA memory.  This emulates the INDXMAA device driver's D1C*
*                   mapping of 0K to 640K on the XMA card to real memory.  D1C*
*                   The XMA "blocks" for 640K and up are located in high   D1C*
*                   memory starting at 12000:0.  These "blocks" run up to  D1C*
*                   the start of the MOVEBLOCK buffer.  The MOVEBLOCK      D1C*
*                   buffer is a chunk of storage (in 16K multiples) at the D1C*
*                   end of available memory that is reserved for the       D1C*
*                   MOVEBLOCK functions.                                   D1C*
*                                                                             *
*                   The page tables are used to emulate the translate         *
*                   table.  By setting the address of the XMA "block" into    *
*                   the page table entry for a specific page frame we can     *
*                   make that address access that particular XMA page         *
*                   frame.  To the user this looks just like the translate    *
*                   table is active.                                          *
*                                                                             *
*                   The tricky part comes in disabling pages (blocks).  On D1C*
*                   the XMA 2 card, when a translate table entry is        D1C*
*                   disabled the addresses for that address range go to    D1C*
*                   real memory.  If the address is between 0K and 640K    D1C*
*                   then any access of that storage gets nothing because   D1C*
*                   there is no memory backed from 0K to 640K on the real  D1C*
*                   system.  All other addresses go to real memory.  So    D1C*
*                   when the user disables translation of a translate      D1C*
*                   table entry we need to check what range that entry     D1C*
*                   covers.  If the entry points to somewhere between 0K   D1C*
*                   and 640K then we will set the page table entry that    D1C*
*                   corresponds to the translate table entry to point to   D1C*
*                   non-existent memory.  For all other addresses we will  D1C*
*                   just point the page table entry back to the real       D1C*
*                   memory at that address.                                D1C*
*                                                                             *
*                   This module receives control on all "IN"s and "OUT"s      *
*                   done by the user.  If the "IN" or "OUT" is not to an      *
*                   XMA port then it passes the I/O on to INDEDMA.  If it     *
*                   is for an XMA port then the request is handled here.      *
*                                                                             *
*                   This module keeps its own copies of the XMA registers     *
*                   and the translate table.  When any I/O comes for the      *
*                   XMA card it updates its copies of the registers and       *
*                   the translate table.  Then it does any needed             *
*                   modifications on the page tables to emulate the XMA       *
*                   request.                                                  *
*                                                                             *
* MODULE TYPE     : ASM                                                       *
*                                                                             *
* REGISTER USAGE  : 80386 Standard                                            *
*                                                                             *
* RESTRICTIONS    : None                                                      *
*                                                                             *
* DEPENDENCIES    : None                                                      *
*                                                                             *
* ENTRY POINTS    : INW         - Emulate "IN" for a word with port number    *
*                                 in DX                                       *
*                   INWIMMED    - Emulate "IN" for a word with an immediate   *
*                                 port number                                 *
*                   INIMMED     - Emulate "IN" for a byte with an immediate   *
*                                 port number                                 *
*                   XMAIN       - Emulate "OUT" for a byte with port number   *
*                                 in DX                                       *
*                   OUTW        - Emulate "OUT" for a word with port number   *
*                                 in DX                                       *
*                   OUTWIMMED   - Emulate "OUT" for a word with an immediate  *
*                                 port number                                 *
*                   XMAOUTIMMED - Emulate "OUT" for a byte with an immediate  *
*                                 port number                                 *
*                   XMAOUT      - Emulate "OUT" for a byte with port number   *
*                                 in DX                                       *
*                                                                             *
* LINKAGE         : Jumped to by INDEEXC                                      *
*                                                                             *
* INPUT PARMS     : None                                                      *
*                                                                             *
* RETURN PARMS    : None                                                      *
*                                                                             *
* OTHER EFFECTS   : None                                                      *
*                                                                             *
* EXIT NORMAL     : Go to POPIO in INDEEMU to IRET to the V86 task            *
*                                                                             *
* EXIT ERROR      : None                                                      *
*                                                                             *
* EXTERNAL                                                                    *
* REFERENCES      : POPIO:NEAR     - Entry in INDEEMU to return to V86 task   *
*                   HEXW:NEAR      - Entry in INDEEXC to display word in AX   *
*                   DMAIN:NEAR     - Entry in INDEDMA to "IN" from DMA port   *
*                   DMAOUT:NEAR    - Entry in INDEDMA to "OUT" to DMA port    *
*                   PGTBLOFF:WORD  - Offset of the normal page tables         *
*                   SGTBLOFF:WORD  - Offset of the page directory             *
*                   NORMPAGE:WORD  - Entry for the 1st page directory entry   *
*                                    so that it points to the normal          *
*                                    page tables                              *
*                   XMAPAGE:WORD   - Entry for the 1st page directory entry   *
*                                    that points to the XMA page tables       *
*                   TTTABLE:WORD   - The translate table                      *
*                   BUFF_SIZE:WORD - Size of the MOVEBLOCK buffer             *
*                   MAXMEM:WORD    - Number of kilobytes on this machine      *
*                                                                             *
* SUB-ROUTINES    : TTARCHANGED - Put the block number at the translate table *
*                                 entry in 31A0H into "ports" 31A2H and 31A4H *
*                   UPDATETT    - Update the translate table and page tables  *
*                                 to reflect the new block number written to  *
*                                 either 31A2H or 31A4H                       *
*                                                                             *
* 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               *
*                                                                             *
* CONTROL BLOCKS  : INDEDAT.INC - system data structures                      *
*                                                                             *
* CHANGE ACTIVITY :                                                           *
*                                                                             *
* $MOD(INDEXMA) COMP(LOAD) PROD(3270PC) :                                     *
*                                                                             *
* $D0=D0004700 410 870530 D : NEW FOR RELEASE 1.1                             *
* $P1=P0000293 410 870731 D : LIMIT LINES TO 80 CHARACTERS                    *
* $P2=P0000312 410 870804 D : CHANGE COMPONENT FROM MISC TO LOAD              *
* $D1=D0007100 410 870810 D : CHANGE TO EMULATE XMA 2                         *
*                                                                             *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
#

        .286P                 ; Enable recognition of 286 privileged instructs.

        .XLIST                ; Turn off the listing
        INCLUDE INDEDAT.INC   ; Include system data structures

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

CRT_SELECTOR    EQU     00030H          ; Selector for mono display buffer
SEX_ATTR        EQU     04B00H          ; Attribute for turquoise on red
STACK_ATTR      EQU     00700H          ; Attribute for white o black
BLANK           EQU     00020H          ; ASCII for a blank
XMA_PAGES_SEL   EQU     RSDA_PTR        ; Selector for the XMA pages
HIMEM           EQU     120H            ; Adjustment for XMA pages >= 640K. @D1C
                                        ;   They start at address 12000:0.
                                        ;   It's the block number corresponding
                                        ;   to address 12000:0.
                                        ;                                   @D1D

PROG    SEGMENT PARA PUBLIC 'PROG'

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

INDEXMA LABEL   NEAR

; The following entry points are in other modules

        EXTRN   POPIO:NEAR              ; Return to V86 task - in INDEEMU
        EXTRN   HEXW:NEAR               ; Display word in AX - in INDEEXC
        EXTRN   DMAIN:NEAR              ; "IN" from DMA port - in INDEDMA
        EXTRN   DMAOUT:NEAR             ; "OUT" to DMA port  - in INDEDMA

; The following data are in INDEI15.ASM

        EXTRN   PGTBLOFF:WORD           ; Offset of the normal page tables
        EXTRN   SGTBLOFF:WORD           ; Offset of the page directory
        EXTRN   NORMPAGE:WORD           ; Entry for the 1st page directory entry
                                        ;   so that it points to the normal
                                        ;   page tables
        EXTRN   XMAPAGE:WORD            ; Entry for the 1st page directory entry
                                        ;  that points to the XMA page tables
        EXTRN   TTTABLE:WORD            ; The translate table
        EXTRN   BUFF_SIZE:WORD          ; Size of the MOVEBLOCK buffer
        EXTRN   MAXMEM:WORD             ; Number of kilobytes on this machine

; Let the following entries be known to other modules

        PUBLIC  INDEXMA
        PUBLIC  INW
        PUBLIC  INWIMMED
        PUBLIC  INIMMED
        PUBLIC  XMAIN
        PUBLIC  OUTW
        PUBLIC  OUTWIMMED
        PUBLIC  XMAOUTIMMED
        PUBLIC  XMAOUT
        PUBLIC  NOTXMAOUT

; Let the following data be known to other modules

        PUBLIC  WORD_FLAG
        PUBLIC  XMATTAR
        PUBLIC  XMATTIO
        PUBLIC  XMATTII
        PUBLIC  XMATID
        PUBLIC  XMACTL

; The following XMA labels represent the XMA ports starting at 31A0H.
; THEY MUST BE KEPT IN THE FOLLOWING ORDER.

XMATTAR DW      0             ; Port 31A0H - Translate table index
XMATTIO DW      0             ; Port 31A2H - XMA block number
XMATTII DW      0             ; Port 31A4H - Block number with auto-increment
XMATID  DB      0             ; Port 31A6H - Bank ID
XMACTL  DB      02H           ; Port 31A7H - Control flags.  Virtual        @D1C
                              ;              enable bit is initially on.

; How about some flags?

WORD_FLAG DB    0             ; If set to 1 then I/O is for a word.
                              ;   Else, it's for a byte

PAGE

; Control comes here for an "IN" for a word with the port value in DX

INW:
        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our
        MOV     DS,AX                   ;   data segment so we can set WORD_FLAG
        MOV     WORD_FLAG,1             ; Flag this as a word operation
        JMP     XMAIN                   ; Go do the "IN"

; Control comes here for an "IN" for a word with an immediate port value

INWIMMED:
        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our
        MOV     DS,AX                   ;   data segment so we can set WORD_FLAG
        MOV     WORD_FLAG,1             ; Flag this as a word operation

; Control comes here for an "IN" for a byte with an immediate port value

INIMMED:

        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the "IN" instruction

; Get the port address from the instruction.  The port address is in the byte
; immediately following the "IN" op-code.  We will load the port address into
; DX.  This way when we join the code below it will look like the port address
; was in DX all along.

        MOV     AX,HUGE_PTR             ; Load DS with a selector that accesses
        MOV     DS,AX                   ;   all of memory as data

        MOV     SS:WORD PTR [BP+BP_IP2],0 ; Clear the high words of the V86
        MOV     SS:WORD PTR [BP+BP_CS2],0 ;   task's CS and IP

        DATAOV                          ; Load ESI (32 bit SI) with the V86
        MOV     SI,SS:[BP+BP_IP]        ;   task's IP
        DATAOV
        MOV     AX,SS:[BP+BP_CS]        ; Load EAX with the V86 task's CS
        DATAOV                          ;   and then shift left four bits to
        SHL     AX,4                    ;   convert it to an offset
        DATAOV                          ; Add the CS offset to "IP" in SI
        ADD     SI,AX                   ; SI now contains CS:IP as a 32 bit
                                        ;   offset from 0
        ADDROV                          ; Get the byte after the "IN" instruc-
        LODSB                           ;   tion.  This is the port address.

        ADDROV                          ; Intel bug # A0-119
        NOP                             ; Intel bug # A0-119

        SUB     DX,DX                   ; Clear DX to prepare for one byte move
        MOV     DL,AL                   ; DX now has the port address

; Control comes here for an "IN" for a byte with the port value in DX

XMAIN   PROC    NEAR

        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our
        MOV     DS,AX                   ;    data segment

        CMP     DX,31A0H                ; Is the port address below 31A0H?
        JB      NOTXMAIN                ; Yup.  Then it's not XMA.

        CMP     DX,31A7H                ; Is the port address above 31A7H?
        JA      NOTXMAIN                ; Yup.  Then it's not XMA.

; It's an XMA port so lets do the "IN" for the guy.

        AND     XMATTAR,0FFFH           ; First lets clear the high nibbles of
        AND     XMATID,0FH              ;   our ports.  This insures that we
        AND     XMACTL,0FH              ;   have valid values in our ports.

        LEA     SI,XMATTAR              ; Point SI to the port requested by
        ADD     SI,DX                   ;   first pointing it to port 31A0H
        SUB     SI,31A0H                ;   and then adding on the difference
                                        ;   between 31A0H and the requested port
        CMP     WORD_FLAG,0             ; Is this a word operation?
        JNE     GETWORD                 ; Yes.  Then go get a word.

        LODSB                           ; Else get a byte from the "port"
        MOV     BYTE PTR SS:[BP+BP_AX],AL ; Put it in the V86 task's AL register
        JMP     INEXIT                  ; Th-th-that's all folks!

; For non-XMA ports we just pass the "IN" on to INDEDMA

NOTXMAIN:
        JMP     DMAIN

; The "IN" is for a word

GETWORD:
        LODSW                           ; Get a word from the "port"
        MOV     WORD PTR SS:[BP+BP_AX],AX ; Put it in the V86 task's AX register

        MOV     WORD_FLAG,0             ; Reset the word flag

        CMP     DX,31A4H                ; Is this an "IN" from the auto-
                                        ;   increment port?
        JNE     INEXIT                  ; Nope.  Then just leave.

        INC     XMATTAR                 ; The "IN" is from the auto-increment
                                        ;   port so increment the translate
        CALL    TTARCHANGED             ;   table index and call TTARCHANGED
                                        ;   to update the status of the "card"
INEXIT:
        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the instruction (past
                                        ;   the port value for immediate insts.)
        JMP     POPIO                   ; Go return to the V86 task

PAGE

; Control comes here for an "OUT" for a word with the port value in DX

OUTW:
        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our
        MOV     DS,AX                   ;   data segment so we can set WORD_FLAG
        MOV     WORD_FLAG,1             ; Flag this as a word operation
        JMP     XMAOUT                  ; Go do the "OUT"

; Control comes here for an "OUT" for a word with an immediate port value

OUTWIMMED:
        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our
        MOV     DS,AX                   ;   data segment so we can set WORD_FLAG
        MOV     WORD_FLAG,1             ; Flag this as a word operation

; Control comes here for an "OUT" for a byte with an immediate port value

XMAOUTIMMED:

        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the "OUT" instruction

; Get the port address from the instruction.  The port address is in the byte
; immediately following the "OUT" op-code.  We will load the port address into
; DX.  This way when we join the code below it will look like the port address
; was in DX all along.

        MOV     AX,HUGE_PTR             ; Load DS with a selector that accesses
        MOV     DS,AX                   ;   all of memory as data

        MOV     SS:WORD PTR [BP+BP_IP2],0 ; Clear the high words of the V86
        MOV     SS:WORD PTR [BP+BP_CS2],0 ;   task's CS and IP

        DATAOV                          ; Load ESI (32 bit SI) with the V86
        MOV     SI,SS:[BP+BP_IP]        ;   task's IP
        DATAOV
        MOV     AX,SS:[BP+BP_CS]        ; Load EAX with the V86 task's CS
        DATAOV                          ;   and then shift left four bits to
        SHL     AX,4                    ;   convert it to an offset
        DATAOV                          ; Add the CS offset to "IP" in SI
        ADD     SI,AX                   ; SI now contains CS:IP as a 32 bit
                                        ;   offset from 0
        ADDROV                          ; Get the byte after the "OUT" instruc-
        LODSB                           ;   tion.  This is the port address.

        ADDROV                          ; Intel bug # A0-119
        NOP                             ; Intel bug # A0-119

        SUB     DX,DX                   ; Clear DX to prepare for one byte move
        MOV     DL,AL                   ; DX now has the port address

; Control comes here for an "OUT" for a byte with the port value in DX

XMAOUT:
        MOV     AX,SYS_PATCH_DS         ; Load DS and ES with the selector for
        MOV     DS,AX                   ;   our data area
        MOV     ES,AX

        CMP     DX,31A0H                ; Is the port address below 31A0H?
        JB      NOTXMAOUT               ; Yes.  Then it's not XMA.

        CMP     DX,31A7H                ; Is the port address above 31A7H?
        JA      NOTXMAOUT               ; Yes.  Then it's not XMA.

        LEA     DI,XMATTAR              ; Point SI to the port requested by
        ADD     DI,DX                   ;   first pointing it to port 31A0H
        SUB     DI,31A0H                ;   and then adding on the difference
                                        ;   between 31A0H and the requested port
        CMP     WORD_FLAG,0             ; Is this a word operation?
        JNE     PUTWORD                 ; Yes.  Then go put a word.

        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Put the value in the V86 task's AL
        STOSB                           ;   register into the "port"

        CMP     DX,31A6H                ; Is this "OUT" to the bank ID port?
        JE      CHKCNTRL                ; If so, go set the new bank

        CMP     DX,31A7H                ; Is the "OUT" to the control port?
        JE      CHKCNTRL                ; Affirmative.  Go handle control bits.

        CMP     DX,31A1H                ; Is this "OUT" to the TT index?
                                        ;   (high byte)
        JBE     TTAROUT                 ; Yup.  Go update dependent fields.

        JMP     OUTEXIT                 ; Any other ports just exit.

; The "OUT" is for a word

PUTWORD:
        MOV     AX,WORD PTR SS:[BP+BP_AX] ; Put the value in the V86 task's AX
        STOSW                           ;   register into the "port"

        MOV     WORD_FLAG,0             ; Reset the word flag

        CMP     DX,31A0H                ; Is the "OUT" to the TT index port?
        JE      TTAROUT                 ; Si.  Go update the dependent fields.

        CMP     DX,31A2H                ; Is the "OUT" to set a block number?
        JNE     CHKA4                   ; No.  Go do some more checks.

        MOV     XMATTII,AX              ; The "OUT" is to 31A2H.  Set the auto-
                                        ;   increment port to the same value.
                                        ;   The two ports should always be in
                                        ;   sync.
        CALL    UPDATETT                ; Update the "translate table" with the
                                        ;   new block number
        JMP     OUTEXIT                 ; That's it.  Let's leave.

CHKA4:
        CMP     DX,31A4H                ; Is "OUT" to the auto-increment port
        JNE     CHKCNTRL                ; No.  Then it must be to the bank ID/
                                        ;   control byte port (31A6H).
        MOV     XMATTIO,AX              ; The "OUT is to the auto-increment port
        CALL    UPDATETT                ; Update the "translate table"
        INC     XMATTAR                 ; Increment the translate table index
        CALL    TTARCHANGED             ; Update fields that depend on the
                                        ;   translate table index
        JMP     OUTEXIT                 ; And return to the V86 task

; The translate table index was changed

TTAROUT:
        CALL    TTARCHANGED             ; Update fields that depend on the
                                        ;   setting of the translate table index
        JMP     OUTEXITDMA              ; Skip flushing the page-translation
                                        ;   cache since the page tables have
                                        ;   not changed.

; It's not an XMA "OUT" so pass it on to INDEDMA

NOTXMAOUT:
        JMP     DMAOUT

; The "OUT" is to the bank ID port (31A6H), the control port (31A7H) or both

CHKCNTRL:

        TEST    XMACTL,02H              ; Is the virtual enable bit on?
        JNZ     SETXMA                  ; Aye.  Go make the specified XMA bank
                                        ;   active.
        DATAOV                          ; Nay.  We simulate disabling the XMA
        MOV     AX,NORMPAGE             ;   card by making the normal page
                                        ;   tables active.
        MOV     DI,SGTBLOFF             ; This is done by setting the first
        DATAOV                          ;   entry in the page directory to
        STOSW                           ;   point to the page table for normal
                                        ;   memory.
        JMP     OUTEXIT                 ; Return to the V86 task

SETXMA:
        AND     XMATID,0FH              ; Wipe out the high nibble of the bank
        MOV     AL,XMATID               ;   ID.  XMA only has 16 banks.
        DATAOV                          ; Now multiply by 4K (shift left 12 ;P1C
        SHL     AX,28                   ;   bits) to get the offset from the
        DATAOV                          ;   base of the XMA page tables of the
        SHR     AX,28-12                ;   page table for the requested bank.
                                        ;   Page tables are 4K in length.  In
                                        ;   the process of shifting we shift the
                                        ;   high order 16 bits off the left end
                                        ;   of EAX so that they are 0 when we
                                        ;   shift back.
        DATAOV                          ; Add on the offset of the base of the
        ADD     AX,XMAPAGE              ;   page tables.  EAX now has the offset
                                        ;   of the page table for the XMA bank.
        MOV     DI,SGTBLOFF             ; Point to the first entry in the page
                                        ;   directory.
        DATAOV                          ; Set the first entry in the page
        STOSW                           ;   directory to point to the XMA page
                                        ;   table

; Since the page tables have changed we need to purge the page-translation
; cache.  "For greatest efficiency in address translation, the processor
; stores the most recently used page-table data in an on-chip cache...  The
; existence of the page-translation cache is invisible to applications
; programmers but not to systems programmers; operating-system programmers
; must flush the cache whenever the page tables are changed."
;   -- 80386 Programmer's Reference Manual (C) Intel 1986

OUTEXIT:
        CMOV    EAX,CR3                 ; Get the page directory base register
        NOP                             ; 386 errata B0-110
        CMOV    CR3,EAX                 ; Write it back to reset the cache
        NOP                             ; 386 errata B0-110

OUTEXITDMA:
        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the "OUT" instruction
        JMP     POPIO                   ; Return to the V86 task

PAGE

; TTARCHANGED updates all the fields that depend on the translate table index
; in port 31A0H.  This is mainly getting the translate table entries for the
; specified index and putting them in the block number ports 31A2H and 31A4H.

TTARCHANGED     PROC

        MOV     AX,XMATTAR              ; Get the new translate table index
        AND     AX,0FFFH                ; The high nibble is not used
        SHL     AX,1                    ; Change it to a word index.  The
                                        ;   translate table entries are words.
        LEA     SI,TTTABLE              ; Point SI to the translate table base
        ADD     SI,AX                   ; Add on the offset into the table
        LODSW                           ; Get the XMA block number for the
                                        ;  specified translate table entry
        MOV     XMATTIO,AX              ; Put it into "port" 31A2H
        MOV     XMATTII,AX              ; Put it into "port" 31A4H

        RET

TTARCHANGED     ENDP

PAGE

; UPDATETT will update the "translate table" and the corresponding page
; tables when an XMA block number is written to either port 31A2H or the
; auto-increment port 31A4H.  A write to either of these ports means to set
; the XMA block specified at the translate table entry indicated in port
; 31A0H.
;
; The Emulator is set up to look like an XMA 2 card with the INDXMAA device  D1C
; driver.  When the system comes up the XMA card is initially disabled.      D1C
; INDXMAA then backs memory from 0K to 640K on the system board with memory  D1C
; from 0K to 640K on the XMA card.  To emulate this, the Emulator treats     D1C
; real memory from 0K to 640K as XMA blocks from 0K to 640K on the XMA card. D1C
; This both saves memory and requires no code to back the real memory from   D1C
; 0K to 640K with XMA memory on initialization.  The Emulator therefore only D1C
; needs to allocate XMA memory for the XMA blocks over 640K.  The XMA memory D1C
; for over 640K starts at 12000:0.  The XMA blocks 00H to 9FH will be mapped D1C
; to the motherboard memory at 0K to 640K.  The XMA blocks A0H and up will   D1C
; be mapped to the memory at 12000:0 and up.                                 D1C
;
; Bits 15 (IBM bit 0) and 11 (IBM bit 4) of the XMA block number have
; special meanings.  When bit 15 is on it means that the block number is a
; 15 bit number.  This is in anticipation of larger block numbers in the
; future.  Current block numbers are 11 bits.  When bit 11 is on it means
; that the XMA translation for this translation table entry should be
; disabled.  The memory for this 4K block should be mapped back to real
; memory.
;
; We also check to make sure that the XMA block is not above the XMA memory
; limit.  XMA memory ends where the MOVEBLOCK buffer starts.  If the XMA
; block is above the end of XMA memory then the page table entry for that
; address is set to point to non-existent memory.
;
; When address translation is disabled for addresses above 640K then the     D1C
; page table entry for that address is set to point back to real memory.     D1C
; For disabled pages in the range 0K to 640K the page table entry is set to  D1C
; point to non-existent memory.                                              D1C

UPDATETT        PROC

        MOV     AX,XMATTAR              ; Get the index of the TT entry that
                                        ;   is to be changed
        AND     AX,0FFFH                ; Clear the high four bits.  They are
                                        ;   not used.
        SHL     AX,1                    ; Change to a word offset since the TT
                                        ;   entries are words.
        LEA     DI,TTTABLE              ; Point DI to the translate table base
        ADD     DI,AX                   ; Add on the offset of the entry that
                                        ;   is to be changed
        MOV     AX,XMATTIO              ; Get the block number to be written
        STOSW                           ; Store the block number in the TT

; Convert bank number to a page address.
; The following code works only with paging enabled at 256k boundary.
; It is intended to support up to 128M at 4k granularity.
; It interprets the high order bits as a superset of XMA.
; Following is a truth table for bits 11 (XMA inhibit bit) and 15 ("enable-hi").
;   15   11
;    0    0   =   enabled 11 bit address
;    0    1   =   disabled address
;    1    x   =   enabled 15 bit address

        TEST    AH,80H                  ; Is this a 15 bit block number?
        JZ      SMALL                   ; Far from it.  Go do stuff for 11 bit
                                        ;   block numbers.

; We have a 15 bit address

        CMP     AX,0FFFFH               ; If it's FFFFH then we treat it the
                                        ;   the same as 0FFFH which means
        JE      DISABLEPAGE             ;   disable the page

        AND     AX,7FFFH                ; Turn off the 15 bit address bit
        JMP     BOTH                    ;   leaving a valid block number for
                                        ;   our calculations later

SMALL:
        TEST    AH,08H                  ; Is the disable bit on?
        JNZ     DISABLEPAGE             ; Yes.  Go disable the page.

        AND     AX,07FFH                ; No.  Turn off the high nibble and the
                                        ;   disable bit leaving a valid block
                                        ;   number for our upcoming calculations
BOTH:
        CMP     AX,640/4                ; Is this block number for 640K or over?
        JB      NOADJUST                ; Yup.  There's no adjustment       @D1C
                                        ;   needed for blocks between 0K and
                                        ;   640K since we use real memory for
                                        ;   these blocks.
                                        ; XMA 1 emulation code deleted     3@D1D
        ADD     AX,HIMEM-(640/4)        ; Add on the adjustment needed for  @D1C
                                        ;   blocks above 640K to point to
                                        ;   the XMA blocks starting at 12000:0.
                                        ;   But don't forget to subtract the
                                        ;   block number for 640K.  This makes
                                        ;   the block number 0 based before we
                                        ;   add on the block number for 12000:0.
NOADJUST:
        DATAOV                          ; Shift the high order 16 bits of EAX
        SHL     AX,16                   ;   off the left end of the register.
        DATAOV                          ; Now shift the block number back four
        SHR     AX,16-12                ;   bits.  This results in a net shift
                                        ;   left of 12 bits which converts the
                                        ;   block number to an offset, and it
                                        ;   clears the high four bits.
        OR      AL,7                    ; Set the access and present bits.  This
                                        ;   converts our offset to a valid page
                                        ;   table entry.
        DATAOV                          ; Save the page table entry in EBX for
        MOV     BX,AX                   ;   now

; Now we must make sure the offset of our XMA page frame is within the address
; space of the XMA pages, that is, it is below the start of the MOVEBLOCK
; buffer.

        DATAOV                          ; Clear all 32 bits of EAX
        SUB     AX,AX
        MOV     AX,MAXMEM               ; Load up the number of K on the box
        SUB     AX,BUFF_SIZE            ; Subtract the number of K reserved
                                        ;   for the MOVEBLOCK buffer
        DATAOV                          ; Multiply by 1K (shift left 10) to
        SHL     AX,10                   ;   convert it to an offset
        DATAOV                          ; Is the XMA page address below the
        CMP     BX,AX                   ;   MOVEBLOCK buffer address?
        JB      ENABLED                 ; Yup.  Whew!  Let's go set up the page
                                        ;   table entry for this XMA block.
        JMP     EMPTY                   ; Nope.  Rats!  Well, we'll just have
                                        ;   to point this TT entry to unbacked
                                        ;   memory.

; We come here when we want to disable translation for this translate table
; entry.  For TT entries for 640K and over we just point the translate table
; entry back to real memory.  For TT entries between 0K and 640K we point
; the translate table to unbacked memory.  This memory on the motherboard
; was disabled under real XMA so we emulate it by pointing to unbacked
; memory.

DISABLEPAGE:
                                        ; XMA 1 emulation code deleted     2@D1D
        CMP     BYTE PTR XMATTAR,640/4  ; Is the address at 640K or above?
        JNB     SPECIAL                 ; Aye.  Go point back to real memory.

; The address is between 256K and 640K.  Let's set the page table entry to
; point to non-exiatent memory.

EMPTY:
        DATAOV                          ; Clear EAX
        SUB     AX,AX
        MOV     AX,MAXMEM               ; Get the total number of K on the box
        DATAOV                          ; Multiply by 1024 to convert to an
        SHL     AX,10                   ;   offset.  AX now points to the 4K
                                        ;   page frame after the end of memory.
        OR      AL,7                    ; Turn on the accessed and present bits
                                        ;    to avoid page faults
        DATAOV                          ; Save the page table entry in EBX
        MOV     BX,AX
        JMP     ENABLED                 ; Go set up the page table

; If the address is above 640K then the translate table (page table) entry   D1C
; is set to point back to real memory.
SPECIAL:
        MOV     AX,XMATTAR              ; Get the index of the TT entry that is
                                        ;   to be disabled
        DATAOV                          ; Dump the high 24 bits off the left end
        SHL     AX,24                   ;   of the register
        DATAOV                          ; Now shift it back 12 bits.  This
        SHR     AX,24-12                ;   results in a net shift left of 12
                                        ;   bits which multiplies the TT index
                                        ;   by 4K while at the same time clear-
                                        ;   ing the high order bits.  EAX is now
                                        ;   the offset of the memory pointed to
                                        ;   by the TT entry.
        OR      AL,7                    ; Turn on the accessed and present bits
                                        ;   to make this a page table entry
        DATAOV                          ; Save the page table entry in EBX
        MOV     BX,AX

; Now let's put the new page table entry in EBX, which represents the XMA block,
; into the page table.
ENABLED:
        MOV     AX,XMATTAR              ; Get the index of the TT entry

; Now we want ot convert the index of the TT entry to an offset into our XMA
; page tables.  The bank number is now in AH and the 4K block number of the
; bank is in AL.  To point to the right page table we need to multiply the bank
; number by 4K (shift left 12 bits) since page tables are 4K in length.  The
; bank number is already shifted left 8 bits by virtue of it being in AH.  It
; needs to be shifted left four more bits.  In order to access the right entry
; in the page table, the block number in AL must be multiplied by 4 (shifted
; left two bits) because the page table entries are 4 bytes in length.  So,
; first we shift AH left two bits and then shift AX left two bits.  In the end
; this shifts AH, the bank ID, left four bits and shifts AL, the block number,
; two bits, which is what we wanted.  A long explanation for two instructions,
; but they're pretty efficient, don't you think?

        SHL     AH,2                    ; Shift the bank ID left two bits
        SHL     AX,2                    ; Shift the bank ID and the block number
                                        ;   left two bits
        MOV     DI,AX                   ; Load DI with the offset of the page
                                        ;   table entry that is to be changed
        PUSH    ES                      ; Save ES
        MOV     AX,XMA_PAGES_SEL        ; Load ES with the selector for our
        MOV     ES,AX                   ;   XMA page tables.  Now ES:DI points
                                        ;   to the page table entry to be
                                        ;   changed
        DATAOV                          ; Get the new value for the page table
        MOV     AX,BX                   ;   entry which was saved in EBX.
        DATAOV                          ; Stuff it into the page table - all
        STOSW                           ;   32 bits of it.

        POP     ES                      ; Restore ES

        RET                             ; And return

UPDATETT        ENDP

XMAIN   ENDP

PROG    ENDS

        END