DMA Access
Executioner

			  DMA Access
			  ----------
			 by Executioner

   This article will be about how to use the DMA controller.  DMA stands for
Direct Memory Access, which is a piece of hardware that allows memory access
without the CPU being involved.  This is how disk access is handled, because if
the processor had to handle each byte of data transferred from the drive, the
system would slow down to a crawl.  The DMA is capable of a maximum of 1.6 MB/s
transfer rate.

   The DMA normally uses the Intel 8237 DMA controller.  This is what's used in
the majority of PC compatibles.  Those PS/2 systems which use the Micro Channel
architecture use a variant which is register compatible but which adds several
features which will not be discussed due to their lack of portability.

   If you are planning on having your virus infect PC/XT systems, you should not
use the DMA for memory-to-memory transfers, because they use channel 0 of the
DMA controller for generating the memory refresh pulse.  In later models,
channel 0 is free for use by the applications.

   Access to the DMA is via ports 0-F.  The page registers for the DMA are
accessed via ports 81h-8Fh.  The page registers hold the most significant bits
of the 20-bit address, while the address register holds the lower 16 bits.
Details are shown below:

0 read	- channel 0 - current address
  write - channel 0 - base address
1 read	- channel 0 - current byte count
  write - channel 0 - base byte count
2 read	- channel 1  "
  write -
3 read	-
  write -
4 read	- channel 2  "
  write -
5 read	-
  write -
6 read	- channel 3  "
  write -
7 read	-
  write -
8 read	- returns contents of status register
          The status register is used to determine that status of current DMA
          jobs.  The upper 4 bits are set if there is a job being processed,
          going from channel 3 to 0, left to right.  The lower 4 bits are set if
          the job has been completed.  This port must be read in order to clear
          the terminal bits.
  write - sets command register
          This should not be changed by the application programmer.  In the BIOS
          initialization, a 4 is output to this port to disable the DMA and a 0
          is written to enable it.
9 write - unused
A write - single mask register bit
	  the lower 4 bits, going from left to right, enable or disable
	  channels 3 to 0, respectively.
B write - set mode register
	  7 - 0
	  6 - 1
	  5 - direction - 1=decrement, 0=increment
	  4 - autoinitialization - 1=enabled, 0=disabled
	  3-2 - operation - 1=write, 2=read
	  1-0 - channel #
C write - clear flip-flop
D read	- not used
  write - clear all
E write - unmask all channels
F write - mask all channels

   The page registers are as follows:

Port	Channel    Use

87h	0	   application
83h	1	   application
81h	2	   diskette
82h	3	   hard disk
8Bh	5	   used in AT computers for accessing 16 megs of memory
89h	6	   they also handle words, so transfers can be 128k
8Ah	7	   "
8Fh	none	   memory refresh

   Note that if you try 2 port accesses consecutively, the DMA hardware will not
have time to recover.  To fix this, put some NOPs or a JMP SHORT $+2 after the
port access.

   The main use for this that I can see is a bizarre method of moving yourself
resident.  I imagine it would be difficult to debug a piece of code using the
DMA to do its work.

   Now for an example of doing something with the DMA.

dseg	segment
data	db	0ffffh dup (?)
dseg	ends

cseg	segment
        assume  cs:cseg
        org     0
main:
; convert the segment in DS to a 20-bit flat address in BX:AX
	mov	ax, seg dseg
	sub	bx, bx
	mov	cx, 4
rotate:
	sal	ax, 1
	rcl	bx, 1
	loop	rotate
	mov	cx, 0ffffh	; move 64k
	call	dma
	mov	ax, 4c00h
	int	21h

; takes address in BX:AX, CX=bytes to move

dma:
	out	12, al		; value of al unimportant - reset flipflop
	out	2, al		; low byte of address
	mov	al, ah
	out	2, al		; high byte of address
	mov	ax, cx
	out	3, al		; low byte of count
	mov	al, ah
	out	3, al		; high byte of count
	mov	ax, bx
	out	83h, al 	; high 4 bits of address -> page
	mov	al, 01000101b
	out	11, al		; set mode
	mov	al, 00001100b	; unmask channel 1, and start xfer
	out	10, al
get_status:
	in	al, 8		; get status
	test	al, 2		; check if channel 1 is done yet
	jz	get_status
	mov	al, 1110b	; mask channel 1
	out	10, al
	retn

cseg	ends
	end	start
