2 TITLE INDEDMA
- 386 XMA Emulator
- DMA Emulation
5 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7 * MODULE NAME
: INDEDMA
*
10 * 5669-196 (C
) COPYRIGHT
1988 Microsoft Corporation
*
12 * DESCRIPTIVE NAME
: DMA handler for the
80386 XMA emulator
*
14 * STATUS
(LEVEL
) : Version
(0) Level
(1.0) *
16 * FUNCTION
: This module intercepts any I
/O going to the DMA address
*
17 * ports
. We can
't let the DMA requests go to the
virtual *
18 * addresses
. On the real XMA card the addresses
on the
*
19 * bus lines are translated by the card so that it accesses
*
20 * the correct memory
. But with our emulation the addresses
*
21 * are translated before they hit the bus lines
. The DMA
*
22 * addresses go straight to the bus lines without being
*
23 * translated
. This would result
in DMA reading
and writing
*
24 * data to the wrong memory location
. Not good
. Therefore
*
25 * we intercept the I
/O that is going to the DMA address
*
26 * ports
. We run these addresses through our paging mech
- *
27 * anism
and then write the real addresses to the DMA
*
32 * REGISTER USAGE
: 80386 Standard
*
34 * RESTRICTIONS
: None
*
36 * DEPENDENCIES
: None
*
38 * ENTRY POINTS
: DMAIN
- Entry point for
"IN" instructions
*
39 * DMAOUT
- Entry point for
"OUT" instructions
*
40 * MANPORT
- Entry point to issue an
OUT to the manufacturing
*
41 * port to re
-IPL the system
*
43 * LINKAGE
: Jumped to by INDEXMA
*
45 * INPUT PARMS
: None
*
47 * RETURN PARMS
: None
*
49 * OTHER EFFECTS
: None
*
51 * EXIT NORMAL
: Jump to POPIO to return to the V86 task
*
56 * REFERENCES
: DISPLAY - Entry point
in INDEEXC to signal an error
*
57 * POPIO
- Entry point
in INDEEMU to return to the V86
*
59 * PGTBLOFF
- Word in INDEI15 that contains the offset of
*
61 * SGTBLOFF
- Word in INDEI15 that contains the offset of
*
62 * the page directory
*
63 * NORMPAGE
- Double
Word in INDEI15 that contains the
*
64 * entry that goes
into the first page directory
*
65 * entry so that it points to the normal page
*
67 * BUFF_SIZE
- Word in INDEI15 that contains the size of the
*
69 * MAXMEM
- Word in INDEI15 that contains the total
*
70 * number of K
in the box
*
71 * WORD_FLAG
- Byte in INDEXMA that indicates whether the
*
72 * I
/O instruction was for a
word or a
byte *
74 * SUB-ROUTINES
: XLATE
- Translate the
virtual DMA address to a real DMA
*
77 * MACROS
: DATAOV
- Add prefix for the next instruction so that it
*
78 * accesses
data as 32 bits wide
*
79 * ADDROV
- Add prefix for the next instruction so that it
*
80 * uses addresses that are
32 bits wide
*
81 * LJB
- Long jump
if below
*
82 * LJA
- Long jump
if above
*
83 * LJAE
- Long jump
if above
or equal
*
84 * LJNE
- Long jump
if not equal
*
86 * CONTROL BLOCKS
: INDEDAT
.INC - System
data structures
*
90 * $
MOD(INDEDMA
) COMP
(LOAD) PROD
(3270PC
) : *
92 * $D0=D0004700
410 870101 D
: NEW FOR RELEASE
1.1 *
93 * $P1
=P0000312
410 870804 D
: CLEAN UP WARNING MESSAGES
*
95 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
98 .286P
; Enable recognition of 286 privileged instructs.
100 .XLIST
; Turn off the listing
101 INCLUDE INDEDAT
.INC ; Include system data structures and equates
103 IF1
; Only include macros on the first pass
104 INCLUDE INDEOVP
.MAC
; Override prefix macros
105 INCLUDE INDEINS
.MAC
; 386 instruction macros
107 .LIST
; Turn the listing back on
109 PROG
SEGMENT PARA
PUBLIC 'PROG'
118 EXTRN DISPLAY:NEAR ; Entry point in INDEEXC to signal an error
119 EXTRN POPIO
:NEAR ; Entry point in INDEEMU to return to the V86
121 EXTRN PGTBLOFF
:WORD ; Word in INDEI15 that contains the offset of
123 EXTRN SGTBLOFF
:WORD ; Word in INDEI15 that contains the offset of
125 EXTRN NORMPAGE
:WORD ; Double Word in INDEI15 that contains the
126 ; entry that goes into the first page direct-
127 ; ory entry so that it points to the normal
129 EXTRN BUFF_SIZE
:WORD ; Word in INDEI15 that contains the size of the
131 EXTRN MAXMEM
:WORD ; Word in INDEI15 that contains the total
132 ; number of K in the box
133 EXTRN WORD_FLAG
:BYTE ; Byte in INDEXMA that indicates whether the
134 ; I/O instruction was for a word or a byte
136 ; Let these entry points be known to other modules
145 ; Define control blocks for each of the DMA channels 0 to 7. The control
146 ; blocks have information on where the user wanted to do DMA, where we will
147 ; actually do the DMA, the channel number and the page port. The following
148 ; is an overlay for the control blocks. After that the actual control
149 ; blocks are defined.
151 DMACB
STRUC ; DMA control block
153 DMACHN
DB 0 ; Channel number
154 DMALSB
DB 0 ; Least significant address byte
155 DMAMSB
DB 0 ; Most significant address byte (16 bits)
156 DMAPAGE
DB 0 ; Page - Hi-order of 24-bit address
157 DMALR
DB 0 ; Real Least significant address byte
158 DMAMR
DB 0 ; Real Most significant address byte (16 bits)
159 DMAPR
DB 0 ; Real Page - Hi-order of 24-bit address
160 DMAPP
DB 0 ; Compatability mode page port
165 DMAENTRYLEN EQU DMAPP
+1-DMASTART
167 ; Now, the channel control blocks
169 DMATABLE
DB 0 ; Channel number
170 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
173 DB 1 ; Channel number
174 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
177 DB 2 ; Channel number
178 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
181 DB 3 ; Channel number
182 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
185 DB 4 ; Channel number
186 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
189 DB 5 ; Channel number
190 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
193 DB 6 ; Channel number
194 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
197 DB 7 ; Channel number
198 DB DMAENTRYLEN
-2 DUP (0) ; The other stuff
201 ; And now some more variables
203 DMACURRENT
DB 0 ; Channel we're working on
204 DMABYTE
DB 0 ; This flag is toggled between 0 and 1
205 ; to indicated whether the least
206 ; significant or most significant byte
207 ; of the DMA address is being written
208 DMA_ADV_CHN
DB 0 ; Advanced mode channel number
212 ; Define the jump table. There are 32 entries in the table that correspond to
213 ; the first 32 ports, 0 to 20H. The code uses the port number as an index into
214 ; this table which then gives control to the appropriate routine for that port.
215 ; The table is initialized so that all entries jump to the code that will pass
216 ; the I/O back to the real port. Then the entries for the ports we want to
217 ; handle are set to the corresponding routines for those ports.
227 ; Set the entries for the ports we want to handle.
229 ORG OUT_TABLE
+(00H*3)
231 ORG OUT_TABLE
+(02H*3)
233 ORG OUT_TABLE
+(04H*3)
235 ORG OUT_TABLE
+(06H*3)
237 ORG OUT_TABLE
+(0CH*3)
239 ORG OUT_TABLE
+(18H
*3)
241 ORG OUT_TABLE
+(1
AH*3)
248 ; Control comes here from INDEXMA when it determines that the "IN" instruction
249 ; is not for one of the XMA ports. On entry, WORD_FLAG is already set for word
250 ; or byte operation. IP points to next instruction minus 1. DX has the port
254 CMP WORD_FLAG
,0 ; Is this "IN" for a word?
255 JNE GETWORDREAL
; Yes. Then go get a word.
257 IN AL,DX ; Else we'll just get a byte
258 MOV BYTE PTR SS:[BP+BP_AX
],AL ; And put it into the user's AL reg.
259 JMP INEXIT
; Go return to the user
262 IN AX,DX ; Get a word from the port
263 MOV WORD PTR SS:[BP+BP_AX
],AX ; Put it into the user's AX register
264 MOV WORD_FLAG
,0 ; Reset the word flag
267 ADD WORD PTR SS:[BP+BP_IP
],1 ; Step IP past the instruction, or past
268 ; the port value in the case of I/O
269 ; with an immediate port value
270 JMP POPIO
; Return to the V86 task
274 ; Control comes here from INDEXMA when it determines that the "OUT" instruction
275 ; is not for one of the XMA ports. On entry, WORD_FLAG is already set for word
276 ; or byte operation. IP points to next instruction minus 1. DX has the port
281 ; Check for DMA page registers in compatibility mode
283 MOV AX,SYS_PATCH_DS
; Load DS with the selector for our data
285 CMP WORD_FLAG
,0 ; Is this a word operation?
286 LJNE
DISPLAY ; No? Sorry. We don't support word DMA
287 ; yet. We'll just have to signal an
289 LEA BX,DMATABLE
; Point BX to the base of our channel
291 CMP DX,0081H ; Is this out to the channel 2 page port
292 LJB NOT_DMA_PAGE
; If the port number is less than 81H
293 ; then the "OUT" is not to a DMA page
294 JA CHK_CHN_0
; If the port number is above 81H, then
295 ; go check if it's below 87H, the page
297 ADD BX,DMAENTRYLEN
*2 ; If it's not below or above, then it
298 ; must be port 81H! Point BX to the
299 ; control block for channel 2
300 JMP CHNCOM
; Continue
303 CMP DX,0087H ; Is it the page port for channel 0?
304 JA CHK680
; Nope. It's above that. Go check for
305 ; the Roundup IPL port
306 JE CHNCOM
; It IS the page port for channel 0.
307 ; BX already points to the control
308 ; block for channel 0.
309 CMP DX,0083H ; Is it the page port for channel 1?
310 JB SET_CHN_3
; No. It's below that. Then it must
311 ; be the page port for channel 3!
312 LJA
DISPLAY ; No. It's above it. We don't know any
313 ; ports between 83H and 87H. Go
315 ADD BX,DMAENTRYLEN
*1 ; Yes. It's the page port for channel 1
316 ; Point BX to the control block for
318 JMP CHNCOM
; And continue
320 ; The port is greater than 87H
323 CMP DX,680H
; Is it the manufacturing port (for IPL)
325 JE MANPORT
; Yes. Go IPL the system.
326 JMP DOOUT
; No. Pass the "OUT" on to the real
329 ; The "OUT" is to the page port for channel 3
332 ADD BX,DMAENTRYLEN
*3 ; Point BX to the control block for
335 ; Check to see if the value written to the page port is greater than 0FH.
336 ; Why? Let me tell you. Addresses are 20 bits, right? The user puts the
337 ; lower 16 bits into the channel port in two eight bit writes. The value
338 ; written to the page port is the upper eight bits of the address. But to
339 ; address 1M you only need 20 bits. Therefore, only the lower four bits are
340 ; valid if the address is to remain within the 1M address limit. So if the
341 ; value to be written to the page port is 10H or greater it is invalid, so
342 ; we will signal an error.
345 MOV AL,BYTE PTR SS:[BP+BP_AX
] ; Get the value that is to be written
347 CMP AL,10H
; Is it 10H or greater?
348 JB CHNCONT
; Nope. We're still OK.
350 ; Oops. It's an invalid page port value. Time to signal an error. But wait.
351 ; If we just jump to DISPLAY as usual the code will just return to the V86
352 ; task. This is not good since we haven't Revised the DMA and it will end
353 ; up going to the wrong address. What we really want to do is kill the system.
354 ; To do this we will issue an interrupt 6, invalid instruction. The exception
355 ; handler checks to see from whence the interrupt came. If it came from the
356 ; emulator then it assumes something is terribly wrong and issues an NMI.
357 ; This is what we want. So we'll issue an INT 6 instead of a JMP DISPLAY.
360 INT 6 ; Signal and error
361 JMP INVALID_PAGE
; Do it again in case control comes
364 ; At this point were still OK. BX points to the control block for the channel.
365 ; Let's translate the address we currently have to its real address and send
366 ; it out to the DMA channel.
369 MOV BYTE PTR [BX+DMAPAGE
],AL ; Put the page port value into the
371 CALL XLATE
; Create the real address entries in
373 MOV DL,BYTE PTR [BX+DMACHN
] ; Get the channel number from the c.b.
374 SHL DX,1 ; Convert it to a port address
375 MOV AL,BYTE PTR [BX+DMALR
] ; Get the LSB of the real address
376 OUT DX,AL ; "OUT" it to the address port
378 MOV AL,BYTE PTR [BX+DMAMR
] ; Get the MSB of the real address
379 OUT DX,AL ; "OUT" it to the address port
381 MOV DL,BYTE PTR [BX+DMAPP
] ; Get the page port
382 MOV AL,BYTE PTR [BX+DMAPR
] ; Get the real page number
383 OUT DX,AL ; Do the "OUT" to the DMA page port
384 JMP OUTEXITDMA
; That's it
388 ; This is where we come when we want to simply send the "OUT" to the real port.
391 CMP WORD_FLAG
,0 ; Is this an "OUT" for a word?
392 JNE PUTWORDREAL
; Aye. Go put out a word.
394 MOV AL,BYTE PTR SS:[BP+BP_AX
] ; Nay. It is for a byte. Get the
395 ; byte from the user's AL register
396 OUT DX,AL ; And send it out to the port
397 JMP OUTEXITDMA
; Time to leave
400 MOV AX,WORD PTR SS:[BP+BP_AX
] ; Get the word form the user's AX
401 OUT DX,AX ; And thrust it out to the port
402 MOV WORD_FLAG
,0 ; Reset the word flag
406 ADD WORD PTR SS:[BP+BP_IP
],1 ; Step IP past the instruction, or past
407 ; the port value in the case of I/O
408 ; with an immediate port value
409 JMP POPIO
; Return to the V86 task
413 ; It's not an "OUT" to one of the DMA page ports.
416 CMP DX,(OUT_TABLE_END
-OUT_TABLE
)/3 ; Is the port within the range
417 ; covered by our jump table, i.e.,
419 JAE NOTCOWBOY
; Nope. Let's go check if it's the IPL
421 MOV AL,DL ; Yes, it's handled by our jump table
422 MOV AH,3 ; Convert the port number to an index
423 MUL AH ; into the jump table by multiplying
424 ; by 3. Jump table entry are 3 bytes.
425 LEA CX,OUT_TABLE
; Get the offset of the jump table
426 ADD AX,CX ; And add it on to the index
427 JMP AX ; Jump to the jump table entry for this
431 CMP DX,80H
; Is it the manufacturing (IPL) port for
433 JNE DOOUT
; Negative. Then let's just do a plain
434 ; vanilla "OUT" to the real port.
436 MOV AL,0FEH ; It's the IPL port! Send out a FEH to
437 OUT 064H,AL ; reset the system.
439 HALT: HLT ; In case that trick didn't work @P1C
440 JMP HALT
; we'll just wait here until @P1C
441 ; somebody hits the big red switch.
445 ; This is the entry for an "OUT" to port 0CH. An "OUT" to this port will
446 ; reset the controller so that the next out will be to the least significant
447 ; byte of the address register. The way you set the lower 16 bits of the
448 ; address is by sending the two bytes to the same port, first the least
449 ; significant byte (LSB) and then the most significant byte (MSB). The port
450 ; knows that successive bytes to the port mean successively higher bytes of
451 ; the address. We emulate this with our DMABYTE flag. Since the user will
452 ; only be sending two bytes to an address port this flag will be toggled
453 ; between 0 and 1 indicating that the "OUT" was to the LSB if 0 and to the
454 ; MSB if 1. A write to port 0CH tells the controller that the next write
455 ; will be for the LSB. We emulate this by setting our DMABYTE flag to 0.
458 MOV DMABYTE
,0 ; Reset the byte flag
459 JMP DOOUT
; Send the "OUT" to the real port
463 ; The following entries handle the "OUT"s to the address ports for channels
464 ; 0 to 3. These are ports 00, 02, 04 and 06 respectively. As mentioned
465 ; above the address written to these ports is done in two steps. First the
466 ; least significant byte (LSB) is written, the the most significant byte (MSB)
467 ; is written. This results in a 16 bit value in the address port. Combine
468 ; this with the byte in the page port and you have a 24 bit DMA address. The
469 ; code below emulates this by putting the byte that is "OUT"ed to the LSB if
470 ; DMA BYTE is 0 and to the MSB if DMABYTE is 1. DMABYTE is toggled between 0
471 ; and 1 each time there is a write to the port. So the bytes go alternately
472 ; to the LSB and the MSB.
474 ; "OUT" is to port 00, the address port for channel 0.
477 LEA BX,DMATABLE
; Point BX to the control block for
479 JMP MOVBYTE
; Go get the byte
481 ; "OUT" is to port 02, the address port for channel 1.
484 LEA BX,DMATABLE
+(1*DMAENTRYLEN
) ; Point BX to the control block for
486 JMP MOVBYTE
; Go get the byte
488 ; "OUT" is to port 04, the address port for channel 2.
491 LEA BX,DMATABLE
+(2*DMAENTRYLEN
) ; Point BX to the control block for
493 JMP MOVBYTE
; Go get the byte
495 ; "OUT" is to port 06, the address port for channel 3.
498 LEA BX,DMATABLE
+(3*DMAENTRYLEN
) ; Point BX to the control block for
502 MOV AL,BYTE PTR SS:[BP+BP_AX
] ; Get the byte from the user's AL
503 XOR DMABYTE
,1 ; Toggle the DMABYTE flag
504 JZ MOVMSB
; Was the flag set to 1? If so, go
505 ; put the byte to the MSB
506 MOV BYTE PTR [BX+DMALSB
],AL ; Else it was 0 so put the byte in the
507 ; LSB for this channel
508 JMP OUTEXITDMA
; And exit
511 MOV BYTE PTR [BX+DMAMSB
],AL ; Put the byte in the MSB for this
513 JMP OUTEXITDMA
; And exit
517 ; This is the entry point for an "OUT" to port 18H. It has something to do
518 ; with the advanced DMA channels 4 thru 7. If the function number in the high
519 ; nibble of AL is 2, set the base register, then we'll save the advanced channel
520 ; number given in the low nibble of AL. If the function is not 2 then we will
521 ; inhibit the setting of the base register.
524 MOV AL,BYTE PTR SS:[BP+BP_AX
] ; Get the value to be "OUT"ed to 18H
525 SHR AL,4 ; Shift the function number into AL
526 CMP AL,2 ; Is this the function to set the base
528 JE SAVE_CHN
; Yup. Go save the channel number.
529 MOV DMABYTE
,3 ; Nope. Inhibit setting the base reg.
530 JMP DOOUT
; Send the "OUT" to the real port
533 MOV AL,BYTE PTR SS:[BP+BP_AX
] ; Get the value in AL again
534 AND AL,07H ; Mask off the function number leaving
536 MOV DMA_ADV_CHN
,AL ; And save it
537 JMP RESET_BYTE_PTR
; Go reset the byte flag
541 ; This is the entry for an "OUT" to port 1AH.
544 CMP DMABYTE
,3 ; Are we inhibiting setting the base
546 LJAE DOOUT
; Yes. Then just sent the "OUT" to the
548 LEA BX,DMATABLE
; Point BX to the channel control blocks
549 MOV AL,DMA_ADV_CHN
; Get the current advanced channel
550 MOV AH,DMAENTRYLEN
; and multiply by the size of a
551 MUL AH ; control block. Now AX is the index
552 ; for the current control block
553 ADD BX,AX ; Add this on to the base and BX points
554 ; to the control block
556 MOV AL,DMABYTE
; Get the byte flag
557 ADD BX,AX ; And add it on to BX
558 MOV CL,BYTE PTR SS:[BP+BP_AX
] ; Get the out value into CL
560 ; Now put it in the control block. Notice that BX is the base of the control
561 ; block plus the byte flag. Now we add on the offset for the LSB entry. A
562 ; little pondering of this code will reveal that for DMABYTE = 0 the byte in
563 ; CL goes to the LSB entry, for DMABYTE = 1 it goes to the MSB entry and for
564 ; DMABYTE = 2 it goes to the page entry.
566 MOV BYTE PTR [BX+DMALSB
],CL ; Save the byte in the control block
567 INC DMABYTE
; Increment our byte counter
568 CMP DMABYTE
,3 ; Was the page entry written?
569 LJNE OUTEXITDMA
; Nope. Let's just exit.
571 SUB BX,AX ; The page was written. Point BX back
572 ; to the start of the control block.
573 CMP CL,10H
; Does the page point to over 1M?
574 LJAE INVALID_PAGE
; Yes. Better signal an error.
576 CALL XLATE
; The page is OK. Translate the virtual
577 ; address to the real address
578 MOV AL,BYTE PTR [BX+DMALR
] ; Get the LSB of the real address
579 OUT 1
AH,AL ; "OUT" it to the port 1AH
581 MOV AL,BYTE PTR [BX+DMAMR
] ; Get the MSB of the real address
582 OUT 1
AH,AL ; "OUT" it to the port 1AH
584 MOV AL,BYTE PTR [BX+DMAPR
] ; Get the real page number
585 OUT 1
AH,AL ; Do the "OUT" to port 1AH
586 JMP OUTEXITDMA
; That's all
590 ; XLATE is a procedure to translate the virtual DMA address to the real DMA
591 ; address. It takes the virtual address that was "OUT"ed to the DMA ports
592 ; and follows it through the page tables to get the real address. It puts
593 ; the real address into the current channel control block.
597 ; Calcuate the page fram offset of the real address, the lower 12 bits.
599 MOV AX,WORD PTR [BX+DMALSB
] ; Get the virtual LSB and MSB
600 AND AH,0FH ; Wipe out the top nibble. This leaves
601 ; us with only the offset into the 4K
602 ; page frame. This real address will
603 ; have the same offset into the page
605 MOV WORD PTR [BX+DMALR
],AX ; So save this in the real LSB
607 ; Pick up page table address.
609 MOV SI,SGTBLOFF
; Point ST to the first page directory
611 DATAOV
; Get the address of the current page
613 SUB AL,AL ; Clear the access rights byte. This
614 ; makes it a real offset.
615 DATAOV
; Point SI to the page table
617 MOV AX,WORD PTR [BX+DMAMSB
] ; Get the MSB and page
618 SHR AX,4 ; Shift the top four bits off the end
619 ; of the register these four bits are
620 ; not used. We are dealing with a 24
621 ; bit address where only 20 bits are
623 SHL AX,4-2 ; Shift back 2 bits. This puts zeroes
624 ; in the high bits while converting
625 ; the address to a page table index.
626 ; Page table entries are 4 bytes long,
627 ; hence, shift left 2 bits.
628 ADD SI,AX ; Add the page table index on to the
629 ; offset of the page table. SI now
630 ; points to the correct page table
632 ADD SI,1 ; Step over the access rights byte
634 MOV AX,HUGE_PTR
; Load DS with a selector that accesses
635 MOV DS,AX ; all of memory as data
636 ADDROV
; Load the address of the page frame
638 MOV CX,SYS_PATCH_DS
; Point DS back to our data segment
641 ; Now AX contains the address of the page frame shifted right 8 bits. Remember
642 ; that we incremented SI to skip the access rights? This gave us the page
643 ; frame offset with out the lower byte. The lSB real address was already set
644 ; above, as well as the low nibble of the real MSB. AX now contains the page
645 ; and MSB of the real address. The low nibble of the MSB was already set so
646 ; we just OR on the high nibble of the MSB. Then we set the real page. Then
649 OR BYTE PTR [BX+DMAMR
],AL ; Turn on high 4 bits of the real MSB
650 MOV BYTE PTR [BX+DMAPR
],AH ; Set the real page