home *** CD-ROM | disk | FTP | other *** search
- IDEAL
- MODEL small
-
- ;---------------------------------------------------------------------------
- ; IBM-PC(tm) compatible programmer's DMA library
- ; Version 1.0
- ;---------------------------------------------------------------------------
- ; Copyright (C) 1992, Heath I Hunnicutt
- ; Portions Copyright (C) 1986, Carnegie Mellon University
- ;---------------------------------------------------------------------------
- ; Thanks to: Gary Nemirovsky
- ;---------------------------------------------------------------------------
- ; This document is for free public distribution. It is unlawful to
- ; sell this document, or any work based substantially upon it.
- ;---------------------------------------------------------------------------
- ; This assembly code defines 3 functions that are intended for use
- ; by C programmers in code that requires access to the DMA system.
- ;
- ; DMA transfers occur asynchronously to the CPU's activity, so they
- ; are quite efficient.
- ;
- ; The general sequence for using the DMA is:
- ; int channel=1; /* DO NOT use channel 0 */
- ; dma_reset(channel);
- ; dma_setup(channel,(char far *)My_Buffer,sizeof(My_Buffer),1);
- ; /* Insert "foreground" code here. */
- ; while (dma_done!=-1)
- ; ;
- ;---------------------------------------------------------------------------
- ; Caution: There is some weird bug in dma_setup that causes crashes
- ; if the length is greater than approx. 32000.
- ; There doesn't seem to be any 'magic-number' like 32768
- ; that relates to this.
- ;---------------------------------------------------------------------------
- ; Send suggestions, questions, comments, knoweledge to:
- ; heathh@cco.caltech.edu (also @tybalt.cco.caltech.edu)
- ; (or hihunn@through.ugcs.caltech.edu -- not preferred)
- ;---------------------------------------------------------------------------
- ; PUBLIC FUNCTIONS
- ; void far dma_reset(int Channel)
- ; void far dma_setup(int Channel,char far *Buffer,int Length,int Dir)
- ; int far dma_done(int Channel)
- ;---------------------------------------------------------------------------
- ; How to assemble this code:
- ; You'll need Turbo Assembler(tm) from Borland(tm) Internationl
- ; TASM /mx /m2 dma_code
- ;---------------------------------------------------------------------------
-
-
- byte_ptr EQU 00ch ; byte pointer flip-flop
- mode EQU 00bh ; dma controller mode register
- dma_mask EQU 00ah ; dma controller mask register
-
- addr EQU 000h ; per-channel base address
- count EQU 001h ; per-channel byte count
-
- read_cmd EQU 048h ; read mode
- write_cmd EQU 044h ; write mode
- set_cmd EQU 000h ; mask set
- reset_cmd EQU 004h ; mask reset
-
- ; dma controller page register table
- ; this table maps from channel number to the i/o port number of the
- ; page register for that channel
- DATASEG
-
- page_table DW 00087h ; channel 0
- DW 00083h ; channel 1
- DW 00081h ; channel 2
- DW 00082h ; channel 3
-
- CODESEG
- MACRO zero reg
- xor reg,reg
- ENDM zero
-
- PUBLIC _dma_setup,_dma_reset,_dma_done
- ; --------------------------------------------------------------------------
- ; void far dma_setup(int Channel,char far *Buffer,int Length,int Dir)
- ;--------------------------------------------------------------------------
- ; Channel = 0-3 !Channel 0 is often reserved for memory refresh!
- ; Buffer = Address of data to play
- ; Length = Length of data to play
- ; NOTE: For some reason, certain combinations of Buffer and
- ; Length cause the system to lock up. As a rule,
- ; keep the Length < 30000 (dec), and DO NOT try to play
- ; any region of memory that might be simultaneously
- ; accessed, such as shared memory, code segments
- ; including the ROM and TSRs that hook interrupts, etc.,
- ; Dir = Direction to move bytes. 1 == Out to the BUS (TO the card)
- ; 0 == In from the BUS and cards.
- ;---------------------------------------------------------------------------
- PROC _dma_setup FAR
- ARG Channel:WORD,Buffer:DWORD,Len:WORD,Dir:WORD
- push bp
- mov bp,sp
- push ax bx cx dx si di
- pushf
-
- ;Convert seg:ofs Buffer to 20-bit physical address
- ;Assumes operating in 8086/real-mode
- mov bx,[WORD PTR Buffer]
- mov ax,[WORD PTR Buffer+2]
- mov cl,4
- rol ax,cl
- mov ch,al
- and al,0F0h
- add ax,bx
- adc ch,0
- and ch,0Fh
- mov di,ax
- ; (ch << 16) + di == The physical buffer base.
-
- ;Calculate the port to receive this address
- mov bx,[Channel]
- shl bx,1
- ;bx == Port # Channel*2
-
- ;Determine which command byte will be written later
- cmp [WORD PTR Dir],0
- jnz SHORT @@Do_Read
- mov al,write_cmd
- jmp SHORT @@Do_Mode
- @@Do_Read:
- mov al,read_cmd
- @@Do_Mode:
- mov cx,[Channel]
- add al,cl
- zero ah
- mov si,ax
- mov ax,set_cmd
- add al,cl
- mov cl,al
- ;si contains READ/WRITE command for DMA controller
- ;cl contains confirmation command for DMA controller
-
- ;-------------------------------------------------------------------------
- ; Calculations have been done ahead of time to minimize time with
- ; interrupts disabled.
- ;
- ; ch:di == physical base address
- ;
- ; cl == Confirmation command (Tells DMA we're done bothering it.)
- ;
- ; bx == I/O port Channel*2 (This is where the address is written)
- ;
- ; si == Mode command for DMA
- ;-------------------------------------------------------------------------
- cli ;Disable interrupts while mucking with DMA
-
- ;I have no idea what purpose these next three instructions serve.
- ;Without them, the procedure crashes and burns.
- mov al,0
- mov dx,byte_ptr ; reset the byte pointer to be safe
- out dx,al
-
- mov ax,di ;ax=LSW of 20-bit address
- mov dx,bx
- out dx,al ;Store LSB
- mov al,ah
- out dx,al ;Store next byte
-
- mov al,ch
- mov dx,[bx + OFFSET page_table] ;Port is the "Page index"
- out dx,al ;Store the page
-
- ;Write length to port Channel*2 + 1
- mov ax,[Len]
- mov dx,bx
- add dx,count
- out dx,al ;Write LSB
- mov al,ah
- out dx,al ;Write MSB
-
- mov ax,si ;Load pre-calculated command
- mov dx,mode
- out dx,al ;Write it to the DSP
-
- mov dx,dma_mask
- mov al,cl
- out dx,al
-
- popf
- pop di si dx cx bx ax
- pop bp
- ret
- ENDP _dma_setup
-
- ;---------------------------------------------------------------------------
- ; void far dma_reset(int Channel)
- ;---------------------------------------------------------------------------
- ; Channel = 0-4
- ; Resets the specified channel.
- ; NOTE: It is a very bad idea to reset channel 0.
- ;---------------------------------------------------------------------------
- PROC _dma_reset FAR
- ARG Channel:Word
- push bp
- mov bp,sp
- push ax dx
- mov dx,dma_mask
- mov ax,reset_cmd
- add ax,[Channel]
- out dx,al
- pop dx ax
- pop bp
- ret
- ENDP _dma_reset
-
- ;---------------------------------------------------------------------------
- ; int far dma_done(Channel)
- ;---------------------------------------------------------------------------
- ; Channel = 0-4
- ;---------------------------------------------------------------------------
- ; Returns: -1 if DMA transaction completed
- ; (Maybe it returns the number of bytes left to transfer?)
- ;---------------------------------------------------------------------------
- PROC _dma_done FAR
- ARG Channel:Word
- push bp
- mov bp,sp
- pushf
- push dx
- mov dx,[Channel]
- shl dx,1
- add dx,count
- cli
- in al,dx
- mov ah,al
- in al,dx
- xchg al,ah
- pop dx
- popf
- pop bp
- ret
- ENDP _dma_done
- END
-