IDEAL Model medium ;+---------------------------------------------------------------------------+ ;| IBM-PC(tm) compatible programmer's DMA library | ;| Version 1.1 | ;+---------------------------------------------------------------------------+ ;| Copyright (C) 1992, Heath I Hunnicutt | ;+---------------------------------------------------------------------------+ ;| 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; | ;| if (dma_reset(channel)) | ;| abort(); | ;| if (dma_setup(channel,(char far *)My_Buffer,sizeof(My_Buffer),1)) | ;| abort(); | ;| /* Insert "foreground" code here. */ | ;| while (dma_done(channel)!=-1) { | ;| if (dma_errno) | ;| abort(); | ;| } | ;+---------------------------------------------------------------------------+ ;| 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 | ;| int far dma_reset(int Channel) | ;| int far dma_setup(int Channel,char far *Buffer,unsigned Length,int Dir) | ;| int far dma_done(int Channel) | ;+---------------------------------------------------------------------------+ ;| PUBLIC DATA | ;| int far dma_errno | ;| char far *dma_errlist[] | ;+---------------------------------------------------------------------------+ ;| How to assemble this code: | ;| You'll need Turbo Assembler(tm) from Borland(tm) Internationl | ;| TASM /mx /m2 dma_code | ;+---------------------------------------------------------------------------+ ;| HISTORY: | ;| Ver 1.0 - Initial Release | ;| Ver 1.1 - Error checking and reporting added to all functions | ;| dma_setup(..) should never crash your system now. | ;+---------------------------------------------------------------------------+ Status EQU 08h ;DMAC status port (read) \ same port Command EQU 08h ;DMAC command port (write) / (read/write) ;STATUS/COMMAND BYTE: ("*" represents defaults) ; [ 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ] ; Bit 0: Memory-to-memory transfer 0 => disable* ; 1 => enable ; 1: "Don't Care" if mem-to-mem disabled (Bit 0==0)* ; Channel 0 address hold 0 => disable ; 1 => enable ; 2: Controller enable 0 => enable* ; 1 => disable ; 3: "Don't Care" if mem-to-mem enabled (Bit 0==1) ; Timing 0 => Normal? ; 1 => Compressed? ; 4: Priority 0 => Fixed? ; 1 => Rotating ; 5: "Don't care" if compressed timing (Bit 3==1) ; Write selection 0 => Late ; 1 => Extended ; 6: DREQ sense active 0 => High ; 1 => Low ; 7: DACK sense active 0 => Low ; 1 => High Request EQU 09h ;DMAC channel request (write-only) ;REQUEST BYTE: ; [ 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 ] ; \__________________/ | \_____/ ; Don't care | | ; | +------+ 00 = Select channel 0 ; | | 01 = Select channel 1 ; | | 10 = Select channel 2 ; | + 11 = Select channel 3 ; +---+ 0 = Reset request bit ; + 1 = Set request bit DMA_Mask EQU 0Ah ;DMAC DMA_Mask (write-only) Mode EQU 0Bh ;DMAC mode (read/write) byte_ptr EQU 00ch ; byte pointer flip-flop 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 ; DMA_Mask set reset_cmd EQU 004h ; DMA_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 ; "Extra" messages are for future compatability with the Virtual DMA ; specification. DMA_E0 DB 0 DMA_E1 DB "Region not in contiguous memory.",0 DMA_E2 DB "Region crossed a physical alignment boundary.",0 DMA_E3 DB "Unable to lock pages.",0 DMA_E4 DB "No buffer available.",0 DMA_E5 DB "Region too large for buffer.",0 DMA_E6 DB "Buffer currently in use.",0 DMA_E7 DB "Invalid memory region.",0 DMA_E8 DB "Region was not locked.",0 DMA_E9 DB "Number of physical pages greater than table length.",0 DMA_EA DB "Ivalid buffer ID.",0 DMA_EB DB "Copy out of buffer range.",0 DMA_EC DB "Invalid DMA channel number.",0 ; Use DD for far pointers ;char far *dma_errlist[] ;_dma_errlist DD DMA_E0, DMA_E1, DMA_E2, DMA_E3, DMA_E4, DMA_E5, DMA_E6, DMA_E7, DMA_E8, DMA_E9, DMA_EA, DMA_EB, DMA_EC ; Use DW for near pointers ;char *dma_errlist[] _dma_errlist DW DMA_E0, DMA_E1, DMA_E2, DMA_E3, DMA_E4, DMA_E5, DMA_E6, DMA_E7, DMA_E8, DMA_E9, DMA_EA, DMA_EB, DMA_EC ;int _dma_errno _dma_errno DW 0 PUBLIC _dma_errlist,_dma_errno CODESEG MACRO zero reg xor reg,reg ENDM zero PUBLIC _dma_setup,_dma_reset,_dma_done ;+---------------------------------------------------------------------------+ ;| int far dma_setup(int Channel,char far *Buffer,unsigned Length,int Dir) | ;| ------------------------------------------------------------------------- | ;| Channel = 0-3 !Channel 0 is often reserved for memory refresh! | ;| Buffer = Address of data to transfer | ;| Length = Length of data to transfer | ;| Dir = Direction to move bytes. 1 == Out to the BUS (TO the card) | ;| 0 == In from the BUS and cards. | ;| ------------------------------------------------------------------------- | ;| Returns: 0 if no errors (dma_errno == 0) | ;| -1 if errors occured (dma_errno set to indicate error.) | ;+---------------------------------------------------------------------------+ PROC _dma_setup FAR ARG Channel:WORD,Buffer:DWORD,Len:WORD,Dir:WORD push bp mov bp,sp push bx cx dx si di pushf mov [_dma_errno],0 ;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] cmp bx,3 jbe @@OkChannel mov [_dma_errno],0Ch mov ax,-1 jmp @@ExitPt @@OkChannel: 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: push cx mov cx,[Channel] add al,cl zero ah mov si,ax mov ax,set_cmd add al,cl pop cx 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 ;------------------------------------------------------------------------- mov ax,di ;Let's check the address to see if we add ax,[Len] ;span a page boundary with our length jnc @@BoundaryOk ;Do we? mov [_dma_errno],2 ; y: Error #2 mov ax,-1 ; Return -1 jmp @@ExitPt ; See ya... @@BoundaryOk: ; n: Continue with action cli ;Disable interrupts while mucking with DMA ;The "byte pointer" is also known as the LSB/MSB flip flop. ;By writing any value to it, the DMA controller registers are prepared ;to accept the address and length values LSB first. mov dx,byte_ptr ;Reset byte pointer Flip/flop out dx,al ;All we have to do is write to it mov ax,di ;ax=LSW of 20-bit address mov dx,bx ;dx=DMAC Base Address port out dx,al ;Store LSB mov al,ah out dx,al ;Store next byte mov al,ch ;al=Page number mov dx,[bx + OFFSET page_table] ;dx=Port is the "Page index" out dx,al ;Store the page ;Write length to port Channel*2 + 1 mov ax,[Len] mov dx,bx ;dx=DMAC Base Adress port inc dx ;dx=DMAC Count port (1 after Base address) out dx,al ;Write LSB of Length mov al,ah out dx,al ;Write MSB mov ax,si ;Load pre-calculated mode mov dx,Mode ;dx=DMAC mode register out dx,al ;Write it to the DSP mov dx,DMA_Mask ;dx=DMAX DMA_Mask register mov al,cl ;al=pre-calulated DMA_Mask value out dx,al ;Write DMA_Mask mov ax,0 ;Return with no error @@ExitPt: ;Restore stack and return popf pop di si dx cx bx pop bp ret ENDP _dma_setup ;+---------------------------------------------------------------------------+ ;| int far dma_reset(int Channel) | ;| ------------------------------------------------------------------------- | ;| Channel = 0-3 | ;| Resets the specified channel. | ;| ------------------------------------------------------------------------- | ;| Returns 0 if Ok, -1 and sets dma_errno on error | ;+---------------------------------------------------------------------------+ PROC _dma_reset FAR ARG Channel:Word push bp mov bp,sp push dx mov [_dma_errno],0 cmp [Channel],3 jbe @@OkChannel mov [_dma_errno],0Ch mov ax,-1 jmp @@Exit_Pt @@OkChannel: mov dx,DMA_Mask mov ax,reset_cmd add ax,[Channel] out dx,al mov ax,0 @@Exit_Pt: pop dx 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?) | ;| dma_errno == 0 if no error, otherwise equals error number | ;+---------------------------------------------------------------------------+ PROC _dma_done FAR ARG Channel:Word push bp mov bp,sp pushf push dx cmp [Channel],3 jbe @@OkChannel mov ax,-1 mov [_dma_errno],0Ch jmp @@Exit_Pt @@OkChannel: mov dx,[Channel] shl dx,1 add dx,count cli in al,dx mov ah,al in al,dx xchg al,ah @@Exit_Pt: pop dx popf pop bp ret ENDP _dma_done END