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

COMMENT #
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*                                                                             *
* MODULE NAME     : INDEDMA                                                   *
*                                                                             *
*                                                                             *
*                    5669-196 (C) COPYRIGHT 1988 Microsoft Corporation        *
*                                                                             *
* DESCRIPTIVE NAME: DMA handler for the 80386 XMA emulator                    *
*                                                                             *
* STATUS (LEVEL)  : Version (0) Level (1.0)                                   *
*                                                                             *
* FUNCTION        : This module intercepts any I/O going to the DMA address   *
*                   ports.  We can't let the DMA requests go to the virtual   *
*                   addresses.  On the real XMA card the addresses on the     *
*                   bus lines are translated by the card so that it accesses  *
*                   the correct memory.  But with our emulation the addresses *
*                   are translated before they hit the bus lines.  The DMA    *
*                   addresses go straight to the bus lines without being      *
*                   translated.  This would result in DMA reading and writing *
*                   data to the wrong memory location.  Not good.  Therefore  *
*                   we intercept the I/O that is going to the DMA address     *
*                   ports.  We run these addresses through our paging mech-   *
*                   anism and then write the real addresses to the DMA        *
*                   address ports.                                            *
*                                                                             *
* MODULE TYPE     : ASM                                                       *
*                                                                             *
* REGISTER USAGE  : 80386 Standard                                            *
*                                                                             *
* RESTRICTIONS    : None                                                      *
*                                                                             *
* DEPENDENCIES    : None                                                      *
*                                                                             *
* ENTRY POINTS    : DMAIN   - Entry point for "IN" instructions               *
*                   DMAOUT  - Entry point for "OUT" instructions              *
*                   MANPORT - Entry point to issue an OUT to the manufacturing*
*                             port to re-IPL the system                       *
*                                                                             *
* LINKAGE         : Jumped to by INDEXMA                                      *
*                                                                             *
* INPUT PARMS     : None                                                      *
*                                                                             *
* RETURN PARMS    : None                                                      *
*                                                                             *
* OTHER EFFECTS   : None                                                      *
*                                                                             *
* EXIT NORMAL     : Jump to POPIO to return to the V86 task                   *
*                                                                             *
* EXIT ERROR      : None                                                      *
*                                                                             *
* EXTERNAL                                                                    *
* REFERENCES      : DISPLAY   - Entry point in INDEEXC to signal an error     *
*                   POPIO     - Entry point in INDEEMU to return to the V86   *
*                               task                                          *
*                   PGTBLOFF  - Word in INDEI15 that contains the offset of   *
*                               the page tables                               *
*                   SGTBLOFF  - Word in INDEI15 that contains the offset of   *
*                               the page directory                            *
*                   NORMPAGE  - Double Word in INDEI15 that contains the      *
*                               entry that goes into the first page directory *
*                               entry so that it points to the normal page    *
*                               tables                                        *
*                   BUFF_SIZE - Word in INDEI15 that contains the size of the *
*                               MOVEBLOCK buffer                              *
*                   MAXMEM    - Word in INDEI15 that contains the total       *
*                               number of K in the box                        *
*                   WORD_FLAG - Byte in INDEXMA that indicates whether the    *
*                               I/O instruction was for a word or a byte      *
*                                                                             *
* SUB-ROUTINES    : XLATE  - Translate the virtual DMA address to a real DMA  *
*                            address                                          *
*                                                                             *
* 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             *
*                   LJB    - Long jump if below                               *
*                   LJA    - Long jump if above                               *
*                   LJAE   - Long jump if above or equal                      *
*                   LJNE   - Long jump if not equal                           *
*                                                                             *
* CONTROL BLOCKS  : INDEDAT.INC - System data structures                      *
*                                                                             *
* CHANGE ACTIVITY :                                                           *
*                                                                             *
* $MOD(INDEDMA) COMP(LOAD) PROD(3270PC) :                                     *
*                                                                             *
* $D0=D0004700 410 870101 D : NEW FOR RELEASE 1.1                             *
* $P1=P0000312 410 870804 D : CLEAN UP WARNING MESSAGES                       *
*                                                                             *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
#

        .286P                 ; Enable recognition of 286 privileged instructs.

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

        IF1                   ; Only include macros on the first pass
        INCLUDE INDEOVP.MAC   ; Override prefix macros
        INCLUDE INDEINS.MAC   ; 386 instruction macros
        ENDIF
        .LIST                 ; Turn the listing back on

PROG    SEGMENT PARA PUBLIC 'PROG'

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

INDEDMA LABEL   NEAR

        EXTRN   DISPLAY:NEAR    ; Entry point in INDEEXC to signal an error
        EXTRN   POPIO:NEAR      ; Entry point in INDEEMU to return to the V86
                                ;   task
        EXTRN   PGTBLOFF:WORD   ; Word in INDEI15 that contains the offset of
                                ;   the page tables
        EXTRN   SGTBLOFF:WORD   ; Word in INDEI15 that contains the offset of
                                ;   the page directory
        EXTRN   NORMPAGE:WORD   ; Double Word in INDEI15 that contains the
                                ;   entry that goes into the first page direct-
                                ;   ory entry so that it points to the normal
                                ;   page tables
        EXTRN   BUFF_SIZE:WORD  ; Word in INDEI15 that contains the size of the
                                ;   MOVEBLOCK buffer
        EXTRN   MAXMEM:WORD     ; Word in INDEI15 that contains the total
                                ;   number of K in the box
        EXTRN   WORD_FLAG:BYTE  ; Byte in INDEXMA that indicates whether the
                                ;   I/O instruction was for a word or a byte

; Let these entry points be known to other modules

        PUBLIC  INDEDMA
        PUBLIC  DMAIN
        PUBLIC  DMAOUT
        PUBLIC  MANPORT

PAGE

; Define control blocks for each of the DMA channels 0 to 7.  The control
; blocks have information on where the user wanted to do DMA, where we will
; actually do the DMA, the channel number and the page port.  The following
; is an overlay for the control blocks.  After that the actual control
; blocks are defined.

DMACB   STRUC                   ; DMA control block

DMACHN  DB      0               ; Channel number
DMALSB  DB      0               ; Least significant address byte
DMAMSB  DB      0               ; Most significant address byte (16 bits)
DMAPAGE DB      0               ; Page - Hi-order of 24-bit address
DMALR   DB      0               ; Real Least significant address byte
DMAMR   DB      0               ; Real Most significant address byte (16 bits)
DMAPR   DB      0               ; Real Page - Hi-order of 24-bit address
DMAPP   DB      0               ; Compatability mode page port

DMACB   ENDS

DMASTART EQU    0
DMAENTRYLEN  EQU     DMAPP+1-DMASTART

; Now, the channel control blocks

DMATABLE DB     0                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     87H                     ; Page port

         DB     1                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     83H                     ; Page port

         DB     2                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     81H                     ; Page port

         DB     3                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     82H                     ; Page port

         DB     4                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     8FH                     ; Page port

         DB     5                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     8BH                     ; Page port

         DB     6                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     89H                     ; Page port

         DB     7                       ; Channel number
         DB     DMAENTRYLEN-2 DUP (0)   ; The other stuff
         DB     8AH                     ; Page port

; And now some more variables

DMACURRENT DB   0                       ; Channel we're working on
DMABYTE DB      0                       ; This flag is toggled between 0 and 1
                                        ;   to indicated whether the least
                                        ;   significant or most significant byte
                                        ;   of the DMA address is being written
DMA_ADV_CHN DB  0                       ; Advanced mode channel number

PAGE

; Define the jump table.  There are 32 entries in the table that correspond to
; the first 32 ports, 0 to 20H.  The code uses the port number as an index into
; this table which then gives control to the appropriate routine for that port.
; The table is initialized so that all entries jump to the code that will pass
; the I/O back to the real port.  Then the entries for the ports we want to
; handle are set to the corresponding routines for those ports.

OUT_TABLE:
        .XLIST
        REPT    32
        JMP     DOOUT
        ENDM
        .LIST
OUT_TABLE_END:

; Set the entries for the ports we want to handle.

        ORG     OUT_TABLE+(00H*3)
        JMP     CHN_0
        ORG     OUT_TABLE+(02H*3)
        JMP     CHN_1
        ORG     OUT_TABLE+(04H*3)
        JMP     CHN_2
        ORG     OUT_TABLE+(06H*3)
        JMP     CHN_3
        ORG     OUT_TABLE+(0CH*3)
        JMP     RESET_BYTE_PTR
        ORG     OUT_TABLE+(18H*3)
        JMP     CHK_FUNCTION
        ORG     OUT_TABLE+(1AH*3)
        JMP     CHK_BASE_REG

        ORG     OUT_TABLE_END

PAGE

; Control comes here from INDEXMA when it determines that the "IN" instruction
; is not for one of the XMA ports.  On entry, WORD_FLAG is already set for word
; or byte operation.  IP points to next instruction minus 1.  DX has the port
; value in it.

DMAIN   PROC    NEAR
        CMP     WORD_FLAG,0             ; Is this "IN" for a word?
        JNE     GETWORDREAL             ; Yes.  Then go get a word.

        IN      AL,DX                   ; Else we'll just get a byte
        MOV     BYTE PTR SS:[BP+BP_AX],AL ; And put it into the user's AL reg.
        JMP     INEXIT                  ; Go return to the user

GETWORDREAL:
        IN      AX,DX                   ; Get a word from the port
        MOV     WORD PTR SS:[BP+BP_AX],AX ; Put it into the user's AX register
        MOV     WORD_FLAG,0             ; Reset the word flag

INEXIT:
        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the instruction, or past
                                        ;   the port value in the case of I/O
                                        ;   with an immediate port value
        JMP     POPIO                   ; Return to the V86 task

PAGE

; Control comes here from INDEXMA when it determines that the "OUT" instruction
; is not for one of the XMA ports.  On entry, WORD_FLAG is already set for word
; or byte operation.  IP points to next instruction minus 1.  DX has the port
; value in it.

DMAOUT:

; Check for DMA page registers in compatibility mode

        MOV     AX,SYS_PATCH_DS         ; Load DS with the selector for our data
        MOV     DS,AX                   ;   segment
        CMP     WORD_FLAG,0             ; Is this a word operation?
       LJNE     DISPLAY                 ; No?  Sorry.  We don't support word DMA
                                        ;   yet.  We'll just have to signal an
                                        ;   error.
        LEA     BX,DMATABLE             ; Point BX to the base of our channel
                                        ;   control blocks
        CMP     DX,0081H                ; Is this out to the channel 2 page port
       LJB      NOT_DMA_PAGE            ; If the port number is less than 81H
                                        ;   then the "OUT" is not to a DMA page
        JA      CHK_CHN_0               ; If the port number is above 81H, then
                                        ;   go check if it's below 87H, the page
                                        ;   port for channel 0
        ADD     BX,DMAENTRYLEN*2        ; If it's not below or above, then it
                                        ;   must be port 81H!  Point BX to the
                                        ;   control block for channel 2
        JMP     CHNCOM                  ; Continue

CHK_CHN_0:
        CMP     DX,0087H                ; Is it the page port for channel 0?
        JA      CHK680                  ; Nope. It's above that.  Go check for
                                        ;   the Roundup IPL port
        JE      CHNCOM                  ; It IS the page port for channel 0.
                                        ;   BX already points to the control
                                        ;   block for channel 0.
        CMP     DX,0083H                ; Is it the page port for channel 1?
        JB      SET_CHN_3               ; No.  It's below that.  Then it must
                                        ;   be the page port for channel 3!
       LJA      DISPLAY                 ; No.  It's above it.  We don't know any
                                        ;   ports between 83H and 87H.  Go
                                        ;   signal an error.
        ADD     BX,DMAENTRYLEN*1        ; Yes.  It's the page port for channel 1
                                        ;   Point BX to the control block for
                                        ;   channel 1.
        JMP     CHNCOM                  ; And continue

; The port is greater than 87H

CHK680:
        CMP     DX,680H                 ; Is it the manufacturing port (for IPL)
                                        ;   on the Roundup?
        JE      MANPORT                 ; Yes.  Go IPL the system.
        JMP     DOOUT                   ; No.  Pass the "OUT" on to the real
                                        ;   port.

; The "OUT" is to the page port for channel 3

SET_CHN_3:
        ADD     BX,DMAENTRYLEN*3        ; Point BX to the control block for
                                        ;   channel 3

; Check to see if the value written to the page port is greater than 0FH.
; Why?  Let me tell you.  Addresses are 20 bits, right?  The user puts the
; lower 16 bits into the channel port in two eight bit writes.  The value
; written to the page port is the upper eight bits of the address.  But to
; address 1M you only need 20 bits.  Therefore, only the lower four bits are
; valid if the address is to remain within the 1M address limit.  So if the
; value to be written to the page port is 10H or greater it is invalid, so
; we will signal an error.

CHNCOM:
        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Get the value that is to be written
                                        ;   to the page port
        CMP     AL,10H                  ; Is it 10H or greater?
        JB      CHNCONT                 ; Nope.  We're still OK.

; Oops.  It's an invalid page port value.  Time to signal an error.  But wait.
; If we just jump to DISPLAY as usual the code will just return to the V86
; task.  This is not good since we haven't Revised the DMA and it will end
; up going to the wrong address.  What we really want to do is kill the system.
; To do this we will issue an interrupt 6, invalid instruction.  The exception
; handler checks to see from whence the interrupt came.  If it came from the
; emulator then it assumes something is terribly wrong and issues an NMI.
; This is what we want.  So we'll issue an INT 6 instead of a JMP DISPLAY.

INVALID_PAGE:
        INT     6                       ; Signal and error
        JMP     INVALID_PAGE            ; Do it again in case control comes
                                        ;   back here

; At this point were still OK.  BX points to the control block for the channel.
; Let's translate the address we currently have to its real address and send
; it out to the DMA channel.

CHNCONT:
        MOV     BYTE PTR [BX+DMAPAGE],AL ; Put the page port value into the
                                        ;   control block
        CALL    XLATE                   ; Create the real address entries in
                                        ;   the control block
        MOV     DL,BYTE PTR [BX+DMACHN] ; Get the channel number from the c.b.
        SHL     DX,1                    ; Convert it to a port address
        MOV     AL,BYTE PTR [BX+DMALR]  ; Get the LSB of the real address
        OUT     DX,AL                   ; "OUT" it to the address port
        JMP     $+2                     ; Wait a bit
        MOV     AL,BYTE PTR [BX+DMAMR]  ; Get the MSB of the real address
        OUT     DX,AL                   ; "OUT" it to the address port
        JMP     $+2                     ; Wait a bit
        MOV     DL,BYTE PTR [BX+DMAPP]  ; Get the page port
        MOV     AL,BYTE PTR [BX+DMAPR]  ; Get the real page number
        OUT     DX,AL                   ; Do the "OUT" to the DMA page port
        JMP     OUTEXITDMA              ; That's it

PAGE

; This is where we come when we want to simply send the "OUT" to the real port.

DOOUT:
        CMP     WORD_FLAG,0             ; Is this an "OUT" for a word?
        JNE     PUTWORDREAL             ; Aye.  Go put out a word.

        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Nay.  It is for a byte.  Get the
                                        ;   byte from the user's AL register
        OUT     DX,AL                   ; And send it out to the port
        JMP     OUTEXITDMA              ; Time to leave

PUTWORDREAL:
        MOV     AX,WORD PTR SS:[BP+BP_AX] ; Get the word form the user's AX
        OUT     DX,AX                   ; And thrust it out to the port
        MOV     WORD_FLAG,0             ; Reset the word flag

OUTEXITDMA:

        ADD     WORD PTR SS:[BP+BP_IP],1 ; Step IP past the instruction, or past
                                        ;   the port value in the case of I/O
                                        ;   with an immediate port value
        JMP     POPIO                   ; Return to the V86 task

PAGE

; It's not an "OUT" to one of the DMA page ports.

NOT_DMA_PAGE:
        CMP     DX,(OUT_TABLE_END-OUT_TABLE)/3 ; Is the port within the range
                                        ;   covered by our jump table, i.e.,
                                        ;   0 to 20H
        JAE     NOTCOWBOY               ; Nope.  Let's go check if it's the IPL
                                        ;   port on the AT
        MOV     AL,DL                   ; Yes, it's handled by our jump table
        MOV     AH,3                    ; Convert the port number to an index
        MUL     AH                      ;   into the jump table by multiplying
                                        ;   by 3.  Jump table entry are 3 bytes.
        LEA     CX,OUT_TABLE            ; Get the offset of the jump table
        ADD     AX,CX                   ; And add it on to the index
        JMP     AX                      ; Jump to the jump table entry for this
                                        ;   port

NOTCOWBOY:
        CMP     DX,80H                  ; Is it the manufacturing (IPL) port for
                                        ;   the AT?
        JNE     DOOUT                   ; Negative.  Then let's just do a plain
                                        ;   vanilla "OUT" to the real port.
MANPORT:
        MOV     AL,0FEH                 ; It's the IPL port!  Send out a FEH to
        OUT     064H,AL                 ;   reset the system.

HALT:   HLT                             ; In case that trick didn't work    @P1C
        JMP     HALT                    ;   we'll just wait here until      @P1C
                                        ;   somebody hits the big red switch.

PAGE

; This is the entry for an "OUT" to port 0CH.  An "OUT" to this port will
; reset the controller so that the next out will be to the least significant
; byte of the address register.  The way you set the lower 16 bits of the
; address is by sending the two bytes to the same port, first the least
; significant byte (LSB) and then the most significant byte (MSB).  The port
; knows that successive bytes to the port mean successively higher bytes of
; the address.  We emulate this with our DMABYTE flag.  Since the user will
; only be sending two bytes to an address port this flag will be toggled
; between 0 and 1 indicating that the "OUT" was to the LSB if 0 and to the
; MSB if 1.  A write to port 0CH tells the controller that the next write
; will be for the LSB.  We emulate this by setting our DMABYTE flag to 0.

RESET_BYTE_PTR:
        MOV     DMABYTE,0               ; Reset the byte flag
        JMP     DOOUT                   ; Send the "OUT" to the real port

PAGE

; The following entries handle the "OUT"s to the address ports for channels
; 0 to 3.  These are ports 00, 02, 04 and 06 respectively.  As mentioned
; above the address written to these ports is done in two steps.  First the
; least significant byte (LSB) is written, the the most significant byte (MSB)
; is written.  This results in a 16 bit value in the address port.  Combine
; this with the byte in the page port and you have a 24 bit DMA address.  The
; code below emulates this by putting the byte that is "OUT"ed to the LSB if
; DMA BYTE is 0 and to the MSB if DMABYTE is 1.  DMABYTE is toggled between 0
; and 1 each time there is a write to the port.  So the bytes go alternately
; to the LSB and the MSB.

; "OUT" is to port 00, the address port for channel 0.

CHN_0:
        LEA     BX,DMATABLE             ; Point BX to the control block for
                                        ;   channel 0
        JMP     MOVBYTE                 ; Go get the byte

; "OUT" is to port 02, the address port for channel 1.

CHN_1:
        LEA     BX,DMATABLE+(1*DMAENTRYLEN) ; Point BX to the control block for
                                        ;   channel 1
        JMP     MOVBYTE                 ; Go get the byte

; "OUT" is to port 04, the address port for channel 2.

CHN_2:
        LEA     BX,DMATABLE+(2*DMAENTRYLEN) ; Point BX to the control block for
                                        ;   channel 2
        JMP     MOVBYTE                 ; Go get the byte

; "OUT" is to port 06, the address port for channel 3.

CHN_3:
        LEA     BX,DMATABLE+(3*DMAENTRYLEN) ; Point BX to the control block for
                                        ;   channel 3

MOVBYTE:
        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Get the byte from the user's AL
        XOR     DMABYTE,1               ; Toggle the DMABYTE flag
        JZ      MOVMSB                  ; Was the flag set to 1?  If so, go
                                        ;   put the byte to the MSB
        MOV     BYTE PTR [BX+DMALSB],AL ; Else it was 0 so put the byte in the
                                        ;   LSB for this channel
        JMP     OUTEXITDMA              ; And exit

MOVMSB:
        MOV     BYTE PTR [BX+DMAMSB],AL ; Put the byte in the MSB for this
                                        ;   channel
        JMP     OUTEXITDMA              ; And exit

PAGE

; This is the entry point for an "OUT" to port 18H.  It has something to do
; with the advanced DMA channels 4 thru 7.  If the function number in the high
; nibble of AL is 2, set the base register, then we'll save the advanced channel
; number given in the low nibble of AL.  If the function is not 2 then we will
; inhibit the setting of the base register.

CHK_FUNCTION:
        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Get the value to be "OUT"ed to 18H
        SHR     AL,4                    ; Shift the function number into AL
        CMP     AL,2                    ; Is this the function to set the base
                                        ;   register?
        JE      SAVE_CHN                ; Yup.  Go save the channel number.
        MOV     DMABYTE,3               ; Nope.  Inhibit setting the base reg.
        JMP     DOOUT                   ; Send the "OUT" to the real port

SAVE_CHN:
        MOV     AL,BYTE PTR SS:[BP+BP_AX] ; Get the value in AL again
        AND     AL,07H                  ; Mask off the function number leaving
                                        ;   the channel number
        MOV     DMA_ADV_CHN,AL          ; And save it
        JMP     RESET_BYTE_PTR          ; Go reset the byte flag

PAGE

; This is the entry for an "OUT" to port 1AH.

CHK_BASE_REG:
        CMP     DMABYTE,3               ; Are we inhibiting setting the base
                                        ;   register?
       LJAE     DOOUT                   ; Yes.  Then just sent the "OUT" to the
                                        ;   real port
        LEA     BX,DMATABLE             ; Point BX to the channel control blocks
        MOV     AL,DMA_ADV_CHN          ; Get the current advanced channel
        MOV     AH,DMAENTRYLEN          ;   and multiply by the size of a
        MUL     AH                      ;   control block.  Now AX is the index
                                        ;   for the current control block
        ADD     BX,AX                   ; Add this on to the base and BX points
                                        ;   to the control block
        SUB     AX,AX                   ; Purge AX
        MOV     AL,DMABYTE              ; Get the byte flag
        ADD     BX,AX                   ; And add it on to BX
        MOV     CL,BYTE PTR SS:[BP+BP_AX] ; Get the out value into CL

; Now put it in the control block.  Notice that BX is the base of the control
; block plus the byte flag.  Now we add on the offset for the LSB entry.  A
; little pondering of this code will reveal that for DMABYTE = 0 the byte in
; CL goes to the LSB entry, for DMABYTE = 1 it goes to the MSB entry and for
; DMABYTE = 2 it goes to the page entry.

        MOV     BYTE PTR [BX+DMALSB],CL ; Save the byte in the control block
        INC     DMABYTE                 ; Increment our byte counter
        CMP     DMABYTE,3               ; Was the page entry written?
       LJNE     OUTEXITDMA              ; Nope.  Let's just exit.

        SUB     BX,AX                   ; The page was written.  Point BX back
                                        ;   to the start of the control block.
        CMP     CL,10H                  ; Does the page point to over 1M?
       LJAE     INVALID_PAGE            ; Yes.  Better signal an error.

        CALL    XLATE                   ; The page is OK.  Translate the virtual
                                        ;   address to the real address
        MOV     AL,BYTE PTR [BX+DMALR]  ; Get the LSB of the real address
        OUT     1AH,AL                  ; "OUT" it to the port 1AH
        JMP     $+2                     ; Wait a bit
        MOV     AL,BYTE PTR [BX+DMAMR]  ; Get the MSB of the real address
        OUT     1AH,AL                  ; "OUT" it to the port 1AH
        JMP     $+2                     ; Wait a bit
        MOV     AL,BYTE PTR [BX+DMAPR]  ; Get the real page number
        OUT     1AH,AL                  ; Do the "OUT" to port 1AH
        JMP     OUTEXITDMA              ; That's all

PAGE

; XLATE is a procedure to translate the virtual DMA address to the real DMA
; address.  It takes the virtual address that was "OUT"ed to the DMA ports
; and follows it through the page tables to get the real address.  It puts
; the real address into the current channel control block.

XLATE   PROC

; Calcuate the page fram offset of the real address,  the lower 12 bits.

        MOV     AX,WORD PTR [BX+DMALSB] ; Get the virtual LSB and MSB
        AND     AH,0FH                  ; Wipe out the top nibble.  This leaves
                                        ;   us with only the offset into the 4K
                                        ;   page frame.  This real address will
                                        ;   have the same offset into the page
                                        ;   frame
        MOV     WORD PTR [BX+DMALR],AX  ; So save this in the real LSB

; Pick up page table address.

        MOV     SI,SGTBLOFF             ; Point ST to the first page directory
                                        ;  entry
        DATAOV                          ; Get the address of the current page
        LODSW                           ;   table
        SUB     AL,AL                   ; Clear the access rights byte.  This
                                        ;   makes it a real offset.
        DATAOV                          ; Point SI to the page table
        MOV     SI,AX
        MOV     AX,WORD PTR [BX+DMAMSB] ; Get the MSB and page
        SHR     AX,4                    ; Shift the top four bits off the end
                                        ;   of the register these four bits are
                                        ;   not used.  We are dealing with a 24
                                        ;   bit address where only 20 bits are
                                        ;   used.
        SHL     AX,4-2                  ; Shift back 2 bits.  This puts zeroes
                                        ;   in the high bits while converting
                                        ;   the address to a page table index.
                                        ;   Page table entries are 4 bytes long,
                                        ;   hence, shift left 2 bits.
        ADD     SI,AX                   ; Add the page table index on to the
                                        ;   offset of the page table.  SI now
                                        ;   points to the correct page table
                                        ;   entry.
        ADD     SI,1                    ; Step over the access rights byte

        MOV     AX,HUGE_PTR             ; Load DS with a selector that accesses
        MOV     DS,AX                   ;   all of memory as data
        ADDROV                          ; Load the address of the page frame
        LODSW                           ;   into EAX
        MOV     CX,SYS_PATCH_DS         ; Point DS back to our data segment
        MOV     DS,CX

; Now AX contains the address of the page frame shifted right 8 bits.  Remember
; that we incremented SI to skip the access rights?  This gave us the page
; frame offset with out the lower byte.  The lSB real address was already set
; above, as well as the low nibble of the real MSB.  AX now contains the page
; and MSB of the real address.  The low nibble of the MSB was already set so
; we just OR on the high nibble of the MSB.  Then we set the real page.  Then
; we're all done.

        OR      BYTE PTR [BX+DMAMR],AL  ; Turn on high 4 bits of the real MSB
        MOV     BYTE PTR [BX+DMAPR],AH  ; Set the real page

        RET

XLATE   ENDP

DMAIN   ENDP

PROG    ENDS

        END