Article View: comp.sys.ibm.pc.programmer
Article #1214Re: DMA-- *HOW?* (long)
From: Charles M. Heard heard
Date: Thu, 19 Nov 1992 04:54
Date: Thu, 19 Nov 1992 04:54
507 lines
23630 bytes
23630 bytes
In article <im14u2c.721730126@camelot> im14u2c@camelot.bradley.edu
(Joseph Zbiciak) writes:
> I would like to know HOW to program the DMA controller to do such
> aweful tasks as moving a screen-size hunk of memory out to the
> display, or repeatedly "sampling" an input port to a bank of
> memory.
This posting is relatively long owing to the number of requests for this
information. It discusses the following topics:
1.) The difference between fly-by and two-cycle transfers, and why it is
usually a waste of time to use a DMA for memory-to-memory moves.
2.) Sample DMA routines that were actually used to download program overlays
and upload measurement results between a PC/XT compatible computer board and
an expansion card with an on-board processor doing data acquisition.
----------------------------------------------------------------------------
1.) Fly-by and two-cycle transfers --
In the PC/XT/AT the 8237 DMA controller is ordinarily set up to transfer data
between an I/O mapped peripheral and main memory. The PC/AT actually has two
DMA controllers, one of which does eight-bit transfers and one of which does
sixteen-bit transfers. These transfers are called single-cycle or fly-by
transfers because a memory read and an I/O write (for a READ TRANSFER
operation) or an I/O read and a memory write are both performed in the same
bus cycle and the data is not stored within the DMA but rather "flies by" on
the computer bus. This is accomplished in the following way. The peripheral
indicates that it is ready to send or receive a byte (or word) of data by
asserting a DMA request line which is attached to the DREQ input pin of one
of the DMA channels. The DMA chip then asserts the CPU's hold request (HREQ)
line. At the next opportunity (typically when it has finished whatever bus
operation that is in progress) the CPU asserts hold acknowledge (HACK) and
tri-states its bus control lines (i.e. relinquishes control of the bus). The
DMA, upon sensing this, asserts the DMA acknowledge (DACK) line of the highest
priority unmasked channel, and after a predictable amount of delay will place
a memory address on the bus (determined by the contents of the channel's
address register) and will assert -MEMRD (memory read) and -IOWR (I/O write)
(for a read transfer) or -IORD (I/O read) and -MEMWR simultaneously. It does
not drive the data bus -- that is up to the memory or peripheral. In effect
the DMA is just a traffic cop. After the operation is complete, the address
register mey be incremented, decremented, or held, and the count register is
decremented. When the latter expires, the channel is masked and no further
transfers take place until it is reprogrammed.
Note that the DACK signal is used in place of an I/O address to select the
peripheral device which will participate in the transfer. One can see that
this will not work if the peripheral device is the video memory, for example,
because one MUST use an address to select the byte or word to be accessed,
and the DMA cannot put two addresses on the bus simultaneously (and get
sensible results, anyway!). For this reason memory-to-memory transfers
are usually implemented as two-cycle transfers. In the 8237 this is
accomplished by linking channels 0 and 1 together, and using one channel
to perform a memory read and store the data in a temporary register; the
other channel writes the data from the temporary register to a different
memory address during a subsequent bus cycle. Since memories usually
don't have any way to assert a DREQ line, the transfer is typically
initiated by the CPU setting a request bit in one of the DMA controller's
internal registers. Such a transfer will run until completion (only one
DMA channel's count register is actually used), and the CPU will be in a
bus hold state for the duration. Unless the CPU has a cache (as do all
80486 PC's and some 80386 PC's) it will just idle while this is happening.
The 80X86 family block move instruction, however, will generally do a
memory-to-memory move just as fast as the DMA can -- after all, this is
limited mainly by memory speed, and the same number of bus cycles are run
(i.e., one memory read and one memory write). And this is a LOT easier
to program than fiddling with the 8237's linked channel programming. Even
the more advanced DMA devices (such as the Intel 82380 integrated peripheral,
which mates with the 80386-DX) that can do a two-cycle transfer with a
single channel are typically not any faster than the CPU at doing block
moves and are much more troublesome to program.
LESSON 1: UNLESS YOU HAVE A CACHE, YOU MIGHT AS WELL USE A BLOCK MOVE
INSTRUCTION FOR SUCH TASKS AS MOVING A SCREEN-SIZED CHUNK OF
DATA FROM MAIN MEMORY TO VIDEO DISPLAY ADAPTER MEMORY.
---------------------------------------------------------------------------
2.) Sample DMA routines that were actually used to download program overlays
and upload measurement results between a PC/XT compatible computer board and
an expansion card with an on-board processor doing data acquisition -- this
is an application for fly-by transfers where the DMA is actually helpful.
The slave board in the example below has a Zilog Z8038 Z-FIO fifo with which
it transfers data to or from the PC/XT host, and it produces or consumes data
at a rather slow rate relative to the bus speed. Rather than interrupting the
CPU whenever a byte is needed (or available) it pays to let the DMA steal a
bus cycle every now and then rather than enduring the interrupt overhead;
and anyway it's a fly-by transfer so it's faster than programmed block I/O
instructions would be. The DREQ and DACK lines are hooked up to the FIO,
and we set up the DMA for single transfers (i.e., it transfers one byte
then gives up the bus again).
In order to make the program a little more comprehensible, here are the
bit definitions for the 8237 command and mode registers. I/O address
equates are in the source (author's note: these equates are little more
than documentation as they were added to the source ex post facto -- the
routines use literals, unfortunately).
DMA_CMD_REG record DACK_SENSE:1, ; active high or low
& DREQ_SENSE:1, ; " "
& WRT_SEL:1, ;normal or late write
& PRIO_SEL:1, ;rotating or fixed
& TIMING_SEL:1, ;compressed or normal
& DEV_ENA:1, ;disable/enable HREQ to CPU
& CH0_ADR_HOLD:1, ;address hold (memory-to-memory xfr)
& MM_ENA:1 ;use CH0/CH1 for memory-to-memory xfr
DMA_MODE_REG record MODE_SEL:2, ;demand, single, block, or cascade
& ADDR_INCR:1, ;increment or decrement address reg.
& AUTO_INIT:1, ;restart or mask chnl when cnt expires
& XFER_TYPE:2, ;read, write, or verify xfr
& CHNL_SEL:2 ;0, 1, 2, or 3 (binary encoded)
Note that the commands to mask single channels put the channel number in the
two LSB's (bits 0 & 1) as in the mode register and the mask bit in bit 2.
See the code below for more details. It is also STRONGLY recommended that
you get the 8237 data sheet from Intel or from Harris Semiconductor.
PAGE ,132
NAME DMA_XFER
TITLE DMA Channel 1 Init'zation Routines
;***************************************************************
;* DOS SEGMENT ORDER *
;***************************************************************
DOSSEG
;***************************************************************
;* MEMORY MODEL *
;***************************************************************
.MODEL small
;***************************************************************
;* DATA SEGMENT *
;***************************************************************
.DATA
STATS_SIZE EQU 640+32 ; 16*20*2+32
ALARM_SIZE EQU 576+32 ; 16*18*2+32
XFR_SIZE EQU STATS_SIZE+ALARM_SIZE+2
EVEN
_DMA_BUFFER DB 1A00H DUP (?)
DMA_BUF_SEG DW ?
DMA_BUF_START DW ?
DMA_BUF_END DW ?
OV_BUF1_PTR DW ?
OV_BUF2_PTR DW ?
EXPORT_DMA_PTR DW ?
; ---------- equates related to 8237 DMA
DMA equ 00H ;base for DMA channel address & count registers
DMA_STATUS_PORT equ 08H ;read status of DMA channels
DMA_CMD_PORT equ 08H ;write global init'zation commands
DMA_RQST_PORT equ 09H ;software request port -- not used
DMA_MASK_PORT equ 0AH ;write single mask bit
DMA_MODE_PORT equ 0BH ;write channel mode registers
DMA_CLFL_PORT equ 0CH ;clear first/last flip-flop
DMA_MCLR_PORT equ 0DH ;master clear (equiv to hdw reset)
DMA_MSK_ALL_CH equ 0FH ;write all mask register bits
DMA_PAGE equ 80H ;DMA page registers base address
; ---------- record definitions for 8237 internal registers
DMA_CMD_REG record DACK_SENSE:1,DREQ_SENSE:1,WRT_SEL:1,PRIO_SEL:1,TIMING_SEL:1,DEV_ENA:1,CH0_ADR_HOLD:1,MM_ENA:1
DMA_MODE_REG record MODE_SEL:2,ADDR_INCR:1,AUTO_INIT:1,XFER_TYPE:2,CHNL_SEL:2
PAGE
;***************************************************************
;* CODE SEGMENT *
;***************************************************************
.CODE
;***************************************************************
;* *
;* PUBLICS *
;* *
;***************************************************************
PUBLIC _SETUP_DMA_FOR_STATS
PUBLIC _SETUP_DMA_FOR_OVLOAD
PUBLIC _START_DMA_FIFO_TXFER
PUBLIC _SETUP_EXPORT_DMA
PUBLIC _START_EXPORT_DMA
PUBLIC _SHUT_OFF_DMA
PAGE
;***************************************************************
;* *
;* NAME: _SETUP_DMA_FOR_STATS *
;* *
;* FUNCTION: To setup DMA channel 1 for transfer of stats *
;* blocks from TMS FIFO into a circular buffer. *
;* *
;* INPUT: None *
;* *
;* OUTPUT: None *
;* *
;***************************************************************
_SETUP_DMA_FOR_STATS PROC NEAR
; partition dma transfer buffer so that we get at least 4 256-byte blocks
; that don't cross 64K boundary
PUSH DI
PUSH BP
MOV BP,SP
CALL _SHUT_OFF_DMA
MOV AX,DS ;convert DMA buffer address to 20-bit value
SUB DX,DX
MOV DL,AH
MOV CL,4
SHR DX,CL
SHL AX,CL
ADD AX,OFFSET DGROUP:_DMA_BUFFER
ADC DX,0 ;DX:AX=20-bit address of DMA buffer
MOV DMA_BUF_SEG,DX ;save segment part (in "compressed" format )
MOV DMA_BUF_START,AX; and provisionally save offset part
NEG AX ;AX=amount of memory left in this 64K segment
CMP AX, XFR_SIZE ;less than size of stats array ?
JA SETUP_DMA_S2 ;jump if not
MOV DMA_BUF_START,0 ;start at offset 0
INC DMA_BUF_SEG ; in next 64K segment
PAGE
SETUP_DMA_S2:
MOV AX,DMA_BUF_START
ADD AX, XFR_SIZE
MOV DMA_BUF_END,AX ;save end address of dma buffer
MOV AX,DMA_BUF_SEG ;fill buffer with all 0's
MOV CL,4
ROR AX,CL
MOV ES,AX
MOV DI,DMA_BUF_START
CLD
MOV AX,0H
MOV CX, XFR_SIZE ; Number of bytes
SHR CX, 1 ; Number of words
REP STOSW
; set up DMA to continuously transfer data out of TMS FIFO to the DMA buffer
CLI ;interrupts off
MOV AL,55H ;set mode: single,auto-inc,I/O -> mem on Ch. 1
OUT DMA+11,AL
OUT DMA+12,AL ;set first/last F/F
MOV AX,DMA_BUF_START
OUT DMA+2,AL ;output start address (low byte)
MOV AL,AH
OUT DMA+2,AL ;and high byte
MOV AX,DMA_BUF_SEG
AND AX,0FH
OUT DMA_PAGE+3,AL ;output high 4 bits of 20-bit addr to page reg
MOV AX, XFR_SIZE ;count output to DMA should be ONE LESS than
; ACTUAL COUNT !!!!!!
DEC AX
OUT DMA+3,AL ;output byte count (low byte)
MOV AL,AH
OUT DMA+3,AL ;and high byte
MOV AL,1 ;unmask DMA channel 1
OUT DMA+10,AL
STI
MOV SP,BP
POP BP
POP DI
RET
_SETUP_DMA_FOR_STATS ENDP
PAGE
;***************************************************************
;* *
;* NAME: _SHUT_OFF_DMA *
;* *
;* FUNCTION: To shut off DMA channel 1 *
;* *
;* INPUT: None *
;* *
;* OUTPUT: None *
;* *
;***************************************************************
_SHUT_OFF_DMA PROC NEAR
CLI
MOV AL,5 ;mask DMA channel 1
OUT DMA+10,AL
STI
RET
_SHUT_OFF_DMA ENDP
PAGE
;***************************************************************
;* *
;* NAME: _SETUP_DMA_FOR_OVLOAD *
;* *
;* FUNCTION: To setup DMA channel 1 for loading overlay *
;* to the TMS through the FIFO. *
;* *
;* INPUT: None *
;* *
;* OUTPUT: None *
;* *
;* NOTE: We use the same transfer buffer that is used *
;* for statistics blocks transfer. This makes stats *
;* collection and overlay load mutually exclusive activi- *
;* ties. *
;***************************************************************
_SETUP_DMA_FOR_OVLOAD PROC NEAR
; partition dma transfer buffer so that we get at least 2 128-byte blocks
; that don't cross 64K boundary
PUSH DI
PUSH BP
MOV BP,SP
CALL _SHUT_OFF_DMA
MOV AX,DS ;convert DMA buffer address to 20-bit value
SUB DX,DX
MOV DL,AH
MOV CL,4
SHR DX,CL
SHL AX,CL
ADD AX,OFFSET DGROUP:_DMA_BUFFER
ADC DX,0 ;DX:AX=20-bit address of DMA buffer
MOV DMA_BUF_SEG,DX ;save segment part (in "compressed" format )
MOV DMA_BUF_START,AX; and provisionally save offset part
NEG AX ;AX=amount of memory left in this 64K segment
CMP AX,100H ;less than 256 bytes (2 128-byte blocks) ?
JA SETUP_DMA_O2 ;jump if not
MOV DMA_BUF_START,0 ;start at offset 0
INC DMA_BUF_SEG ; in next 64K segment
SETUP_DMA_O2:
MOV AX,DMA_BUF_START
MOV OV_BUF1_PTR,AX ;save start pointer as overlay buffer 1 ptr
ADD AX,80H
MOV OV_BUF2_PTR,AX ;save overlay buffer 2 ptr
PAGE
MOV AX,DMA_BUF_SEG ;fill buffer with all 0's
MOV CL,4
ROR AX,CL
MOV ES,AX
MOV DI,DMA_BUF_START
CLD
MOV AX,0
MOV CX,100H/2
REP STOSW
; set up DMA for transfer data out of DMA buffer to the TMS FIFO
CLI ;interrupts off
MOV AL,49H ;set mode: single,no auto,inc,mem->I/O on Ch. 1
OUT DMA+11,AL
MOV AX,DMA_BUF_SEG
AND AX,0FH
OUT DMA_PAGE+3,AL ;output high 4 bits of 20-bit addr to page reg
STI
MOV SP,BP
POP BP
POP DI
RET
_SETUP_DMA_FOR_OVLOAD ENDP
PAGE
;***************************************************************
;* *
;* NAME: _START_DMA_FIFO_TXFER *
;* *
;* FUNCTION: To start DMA-FIFO transfer. *
;* *
;* INPUT: [BP+4] = Buffer # (0 or 1) *
;* *
;* OUTPUT: None *
;* *
;* NOTE: DMA should have been previously setup in *
;* _SETUP_DMA_FOR_OVLOAD. FIFO should be held CLEAR *
;* while DMA is being reprogrammed. *
;***************************************************************
_START_DMA_FIFO_TXFER PROC NEAR
PUSH BP
MOV BP,SP
CALL _SHUT_OFF_DMA
CLI ;interrupts off
MOV AX,OV_BUF1_PTR ;decide which overlay buffer to use
CMP WORD PTR [BP+4],0
JE START_DMA2
MOV AX,OV_BUF2_PTR
START_DMA2:
OUT DMA+12,AL ;set first/last F/F
OUT DMA+2,AL ;output start address (low byte)
MOV AL,AH
OUT DMA+2,AL ;and high byte
MOV AX,80H-1 ;count output to DMA should be ONE LESS than
; ACTUAL COUNT !!!!!!
OUT DMA+3,AL ;output byte count (low byte)
MOV AL,AH
OUT DMA+3,AL ;and high byte
MOV AL,1 ;unmask DMA channel 1
OUT DMA+10,AL
STI
MOV SP,BP
POP BP
RET
_START_DMA_FIFO_TXFER ENDP
PAGE
;***************************************************************
;* *
;* NAME: _SETUP_EXPORT_DMA *
;* *
;* FUNCTION: To setup DMA channel 1 for export to 186 *
;* through the FIFO. *
;* *
;* INPUT: None *
;* *
;* OUTPUT: None *
;* *
;* NOTE: We use the same transfer buffer that is used *
;* for statistics blocks transfer. This makes stats *
;* collection and DMA export mutually exclusive activi- *
;* ties. *
;***************************************************************
_SETUP_EXPORT_DMA PROC NEAR
; partition dma transfer buffer so that we get at least 2 128-byte blocks
; that don't cross 64K boundary
PUSH DI
PUSH BP
MOV BP,SP
CALL _SHUT_OFF_DMA
MOV AX,DS ;convert DMA buffer address to 20-bit value
SUB DX,DX
MOV DL,AH
MOV CL,4
SHR DX,CL
SHL AX,CL
ADD AX,OFFSET DGROUP:_DMA_BUFFER
ADC DX,0 ;DX:AX=20-bit address of DMA buffer
MOV DMA_BUF_SEG,DX ;save segment part (in "compressed" format )
MOV DMA_BUF_START,AX; and provisionally save offset part
MOV EXPORT_DMA_PTR,AX ;save start pointer
;
; set up DMA for transfer data out of DMA buffer to the 186 FIFO
;
CLI ;interrupts off
MOV AL,49H ;set mode: single,no auto,inc,mem->I/O on Ch. 1
OUT DMA+11,AL
MOV AX,DMA_BUF_SEG
AND AX,0FH
OUT DMA_PAGE+3,AL ;output high 4 bits of 20-bit addr to page reg
STI
MOV SP,BP
POP BP
POP DI
RET
_SETUP_EXPORT_DMA ENDP
PAGE
;***************************************************************
;* *
;* NAME: _START_EXPORT_DMA *
;* *
;* FUNCTION: To start Export DMA-FIFO transfer. *
;* *
;* INPUT: [BP+4] = Number of bytes *
;* *
;* OUTPUT: None *
;* *
;* NOTE: DMA should have been previously setup in *
;* _SETUP_EXPORT_DMA. FIFO should be held CLEAR *
;* while DMA is being reprogrammed. *
;***************************************************************
_START_EXPORT_DMA PROC NEAR
PUSH BP
MOV BP,SP
CALL _SHUT_OFF_DMA
CLI ;interrupts off
MOV AX,EXPORT_DMA_PTR ;Get Export pointer buffer to use
OUT DMA+12,AL ;set first/last F/F
OUT DMA+2,AL ;output start address (low byte)
MOV AL,AH
OUT DMA+2,AL ;and high byte
MOV AX, WORD PTR [BP+4] ;count output to DMA should be ONE LESS than
DEC AX ; ACTUAL COUNT !!!!!!
OUT DMA+3,AL ;output byte count (low byte)
MOV AL,AH
OUT DMA+3,AL ;and high byte
MOV AL,1 ;unmask DMA channel 1
OUT DMA+10,AL
STI
MOV SP,BP
POP BP
RET
_START_EXPORT_DMA ENDP
END
Technical Details
Message-ID:
<8332@public.BTR.COM>Path:
arc1-ca.pugleaf.net!sparky!uunet!cs.utexas.edu!wupost!sdd.hp.com!sgiblab!public!heardReferences:
<im14u2c.721884439@camelot> <1992Nov17.084050.9545@cs.joensuu.fi> <1992Nov17.162107.15828@athena.cs.uga.edu>Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!sparky!uunet!cs.utexas.edu!wupost!sdd.hp.com!sgiblab!public!heard From: heard@public.BTR.COM (Charles M. Heard heard@btr.com) Newsgroups: comp.sys.ibm.pc.programmer,alt.msdos.programmer Subject: Re: DMA-- *HOW?* (long) Summary: Discussion of two-cycle vs fly-by plus sample working code Keywords: DMA Message-ID: <8332@public.BTR.COM> Date: 19 Nov 92 04:54:42 GMT References: <im14u2c.721884439@camelot> <1992Nov17.084050.9545@cs.joensuu.fi> <1992Nov17.162107.15828@athena.cs.uga.edu> Followup-To: comp.sys.ibm.pc.programmer Organization: BTR Public Access UNIX, MtnView CA. For info contact: info@BTR.COM Lines: 507