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
Date: 19 Nov 92 04:54:42 GMT
Followup-To: comp.sys.ibm.pc.programmer
Organization: BTR Public Access UNIX, MtnView CA. For info contact: info@BTR.COM

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
Xref: sparky comp.sys.ibm.pc.programmer:590 alt.msdos.programmer:2758 comp.os.msdos.programmer:10708 comp.protocols.tcp-ip.ibmpc:6367