; =============================================================================
;
; Litos - DMA channel
;
; =============================================================================
; NOTES:
; - DMA0-3 is 8-bit controller 1, max. 64 KB, must not cross 64K boundary
; - DMA4 channel is used as cascade (cannot be used)
; - DMA5-7 is 16=bit controller 2, max. 128 KB, must not cross 128K boundary
; - transfers are limited to lower 16 MB of physical memory (use DMAMemGet)
;
; Prepare DMA transfer:
; - Allocate memory block using DMAMemGet (max. 64 KB).
; - Allocate DMA channel using DMAAlloc or DMAInstall. Before it you can verify
; channel number with DMAGetBus function and check if with DMACheck.
;
; Start transfer using DMAStart or with following steps:
; - Disable interrupts and lock DMA controller using DMALOCK.
; - Set DMA mode (transfer direction) using DMASetMode with modes DMAMODE_READ
; or DMAMODE_WRITE.
; - Set transfer address using DMASetAddr (align it to word).
; - Set transfer size using DMASetSize (align it to word).
; - Start DMA transfer with DMAEnable.
; - Unlock DMA controller using DMAUNLOCK and return interrupt flag.
;
; Test transfer using DMATest or with following steps:
; - Disable interrupts and lock DMA controller using DMALOCK.
; - Check if transfer have finished using DMARemain.
; - Unlock DMA controller using DMAUNLOCK and return interrupt flag.
;
; Stop transfer using DMAStop or with following steps:
; - After DMA finish transfer, disable interrupts and lock contr. with DMALOCK.
; - Disable channel with DMADisable.
; - Unlock DMA controller using DMAUNLOCK and return interrupt flag.
;
; Default DMA assignments on PC/AT:
; DMA 2: floppy disk controller
; DMA 3: ECP printer port (LPT1)
; DMA 4: cascade to slave DMA controller
; =============================================================================
CODE_SECTION 32
DMA_CHANNELS EQU 8 ; number of DMA channels
; ------------- DMA modes
DMAMODE_VERIFY EQU B6 ; DMA verify
DMAMODE_READ EQU B2+B6 ; DMA read (read, increment, single)
DMAMODE_WRITE EQU B3+B6 ; DMA write (write, increment, single)
DMAMODE_CASCADE EQU B6+B7 ; DMA cascade
DMAMODE_INIT EQU B4 ; DMA autoinit
; -----------------------------------------------------------------------------
; Lock/unlock DMA
; -----------------------------------------------------------------------------
; NOTES: Use macro DMALOCK to lock, DMAUNLOCK to unlock.
; -----------------------------------------------------------------------------
; ------------- DMA lock function
%ifdef SMP
LOCK_LockFnc DMALock,Lock8237 ; declare DMA lock function
%endif
; ------------- Macro - call DMA lock function
%macro DMALOCK 0
%ifdef SMP
call DMALock ; call DMA lock function
%endif
%endmacro
; ------------- Macro - DMA unlock
%macro DMAUNLOCK 0
%ifdef SMP
LOCK_Unlock Lock8237 ; unlock 8237
%endif
%endmacro
; -----------------------------------------------------------------------------
; Get DMA channel bus width (and check DMA channel number validity)
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; OUTPUT: CY = invalid channel number (EAX = 0)
; EAX = width of DMA channel bus (8 or 16, 0 on CY)
; -----------------------------------------------------------------------------
; ------------- Check for DMA channel number validity
DMAGetBus: cmp al,DMA_CHANNELS ; check DMA channel number
jb DMAGetBus2 ; DMA channel number is valid
xor eax,eax ; EAX <- 0, invalid channel number
stc ; set error flag
ret
; ------------- Get DMA channel number width
DMAGetBus2: cmp al,4 ; is it 16-bit DMA bus?
mov al,8 ; 8-bit DMA bus
jb DMAGetBus4 ; it is 8-bit DMA bus
mov al,16 ; 16-bit DMA bus
DMAGetBus4: movzx eax,al ; EAX <- DMA channel bus width
clc ; clear error flag
ret
; -----------------------------------------------------------------------------
; Allocate DMA channel
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; OUTPUT: CY = DMA channel is already in use
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
DMAAlloc: push eax ; push EAX
movzx eax,al ; EAX <- DMA number
bts [DMAMap],eax ; test and set usage bit
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Install DMA handler
; -----------------------------------------------------------------------------
; INPUT: AL = recommended best (or last) DMA channel number
; EDX = mask of usable DMA channels (1=enabled)
; OUTPUT: EAX = DMA channel number (0 to 7 or -1 on error)
; CY = cannot install DMA handler (no free DMA entry, try later)
; -----------------------------------------------------------------------------
; ------------- Try to allocate recommended best DMA channel
DMAInstall: movzx eax,al ; EAX <- recommended DMA channel
cmp al,DMA_CHANNELS ; check channel number
jae DMAInstall2 ; invalid channel number
bts [DMAMap],eax ; test and set usage bit
jnc DMAInstall9 ; recommended DMA channel is OK
; ------------- Try to allocate some other DMA channel
DMAInstall2: xor eax,eax ; EAX <- 0, first DMA channel
DMAInstall4: bt edx,eax ; is this channel allowed?
jnc DMAInstall6 ; channel is not allowed
bts [DMAMap],eax ; test and set usage bit
jnc DMAInstall9 ; DMA channel is OK
DMAInstall6: inc eax ; increase DMA channel
cmp al,DMA_CHANNELS ; is next channel valid?
jb DMAInstall4 ; check next channel
; ------------- Error, no suitable DMA channel found
xor eax,eax ; EAX <- 0
dec eax ; EAX <- -1
stc ; set error flag
DMAInstall9: ret
; -----------------------------------------------------------------------------
; Free DMA channel
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
DMAFree: push eax ; push EAX
movzx eax,al ; EAX <- DMA number
btr [DMAMap],eax ; reset usage bit
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Check if DMA channel is used
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; OUTPUT: CY = DMA channel is used
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
DMACheck: push eax ; push EAX
movzx eax,al ; EAX <- DMA number
btc [DMAMap],eax ; test usage bit
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Enabled DMA channel
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
; ------------- Check if it is controller 2
DMAEnable: cmp al,4 ; is it controller 2?
jae DMAEnable2 ; it is controller 2
; ------------- Enable DMA channel on controller 1
out 0ah,al ; enable DMA channel on controller 1
ret
; ------------- Enable DMA channel on controller 2
DMAEnable2: sub al,4 ; Al <- relative DMA channel number
out 0d4h,al ; enable DMA channel on controller 2
add al,4 ; AL <- return DMA channel number
ret
; -----------------------------------------------------------------------------
; Disable DMA channel
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
; ------------- Check if it is controller 2
DMADisable: cmp al,4 ; is it controller 2?
jae DMADisable2 ; it is controller 2
; ------------- Disable DMA channel on controller 1
add al,B2 ; set disable flag
out 0ah,al ; disable DMA channel on controller 1
sub al,B2 ; return DMA channel number
ret
; ------------- Disable DMA channel on controller 2 (bit 2 is set)
DMADisable2: out 0d4h,al ; disable DMA channel on controller 2
ret
; -----------------------------------------------------------------------------
; Clear DMA channel flip-flop
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
; ------------- Check if it is controller 2
DMAClearFF: cmp al,4 ; is it controller 2?
jae DMAClearFF2 ; controller 2
; ------------- Clear DMA channel flip-flop on controller 1
out 0ch,al ; clear DMA channel flip-flop on ctrl 1
ret
; ------------- Clear DMA channel flip-flop on controller 2
DMAClearFF2: out 0d8h,al ; clear DMA channel flip-flop on ctrl 2
ret
; -----------------------------------------------------------------------------
; Set DMA channel mode
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; BL = DMA channel mode
; B0-B1: must be 0
; B2-B3: transfer mode
; 00=verify
; 01=read from device (write to memory)
; 10=write to device (read from memory)
; B4: 1=autoinitialisation enabled
; B5: 0=increment address, 1=decrement address
; B6-B7: 00=demand mode
; 01=single mode
; 10=block transfer
; 11=cascade mode
; preset modes: DMAMODE_VERIFY - verify
; DMAMODE_READ - DMA read from device
; DMAMODE_WRITE - DMA write to device
; DMAMODE_CASCADE - DMA cascade
; DMAMODE_INIT - DMA autoinit
; NOTES: AL not checked for validity and DMA lock not locked.
; -----------------------------------------------------------------------------
; ------------- Check if it is controller 2
DMASetMode: push eax ; push EAX
cmp al,4 ; is it controller 2?
jae DMASetMode2 ; controller 2
; ------------- Set DMA channel mode on controller 1
or al,bl ; AL <- channel + mode
out 0bh,al ; set DMA mode on controller 1
pop eax ; pop EAX
ret
; ------------- Set DMA channel mode on controller 2
DMASetMode2: sub al,4 ; AL <- relative DMA channel number
or al,bl ; AL <- channel + mode
out 0d6h,al ; set DMA mode on controller 2
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Set DMA channel page
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; EDX = DMA address (physical address)
; NOTES: AL not checked for validity and DMA lock not locked.
; Only address bits 16-23 (DMA0-3) or 17-23 (DMA4-7) are used.
; -----------------------------------------------------------------------------
; ------------- Push registers
DMASetPage: push eax ; push EAX
push edx ; push EDX
; ------------- Prepare DMA address
shr edx,16 ; DL <- page number
cmp al,4 ; is it controller 1?
jb DMASetPage2 ; it is controller 1
and dl,~B0 ; clear bit 16 for controller 2
; ------------- Prepare port address
DMASetPage2: movzx eax,al ; EAX <- DMA channel
mov al,[eax+DMAPagePort] ; AL <- address (here AH = 0)
; ------------- Set DMA channel page
xchg eax,edx ; DX <- port, AL <- page
out dx,al ; set DMA channel page
; ------------- Pop registers
pop edx ; pop EDX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Set DMA transfer address (offset and page)
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; EDX = DMA address (physical address, only bits 0-23 are used)
; NOTES: AL not checked for validity and DMA lock not locked.
; DMA transfer cannot cross 64K (or 128K for DMA4-7) boundary.
; DMA5 to DMA7 address must be word aligned.
; Transfers are limited to lower 16 MB of physical memory.
; -----------------------------------------------------------------------------
; ------------- Set DMA channel page
DMASetAddr: call DMASetPage ; set DMA channel page
; ------------- Clear DMA channel flip-flop
call DMAClearFF ; clear DMA channel flip-flop
; ------------- Push registers
push eax ; push EAX
push edx ; push EDX
; ------------- Prepare port for controller 1 (00h, 02h, 04h, 06h)
movzx eax,al ; EAX <- DMA channel number
shl eax,1 ; AX <- channel number*2
cmp al,4*2 ; is it DMA0 to DMA3?
jb DMASetAddr2 ; it is DMA0 to DMA3
; ------------- Prepare port for controller 2 (0C0h, 0C4h, 0C8h, 0CCh)
shl eax,1 ; AX <- channel number*4
add al,0c0h-4*4 ; AL <- port
shr edx,1 ; convert address to words
; ------------- Set transfer address
DMASetAddr2: xchg eax,edx ; DX <- port, EAX <- address
out dx,al ; set transfer address LOW
mov al,ah ; AL <- transfer address HIGH
out dx,al ; set transfer address HIGH
; ------------- Pop registers
pop edx ; pop EDX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Set DMA transfer size
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; ECX = DMA transfer size (cannot be 0; B0 is ignored for DMA4-7)
; NOTES: AL not checked for validity and DMA lock not locked.
; DMA transfer cannot cross 64K (or 128K for DMA4-7) boundary.
; Maximal transfer size is 64K for DMA0-3 and 128K for DMA4-7.
; -----------------------------------------------------------------------------
; ------------- Clear DMA channel flip-flop
DMASetSize: call DMAClearFF ; clear DMA channel flip-flop
; ------------- Push registers
push eax ; push EAX
push ecx ; push ECX
push edx ; push EDX
; ------------- Prepare port for controller 1 (01h, 03h, 05h, 07h)
movzx eax,al ; EAX <- DMA channel number
shl eax,1 ; AX <- channel number*2
inc eax ; EAX <- channel number*2 + 1
cmp al,4*2+1 ; is it DMA0 to DMA3?
jb DMASetSize2 ; it is DMA0 to DMA3
; ------------- Prepare port for controller 2 (0C2h, 0C6h, 0CAh, 0CEh)
shl eax,1 ; AX <- channel number*4 + 2
add al,0c0h-4*4 ; AL <- port
shr ecx,1 ; convert size to words
; ------------- Set transfer size
DMASetSize2: dec ecx ; correct size (=last transfered item)
xchg eax,edx ; DX <- port
xchg eax,ecx ; AX <- size
out dx,al ; set transfer size LOW
mov al,ah ; AL <- transfer size HIGH
out dx,al ; set transfer size HIGH
; ------------- Pop registers
pop edx ; pop EDX
pop ecx ; pop ECX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Get DMA remaining transfer size
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; OUTPUT: ECX = remaining bytes to transfer (0 if transfer finished)
; NOTES: AL not checked for validity and DMA lock not locked.
; If called before the channel has been used, it may return 1.
; -----------------------------------------------------------------------------
; ------------- Clear DMA channel flip-flop
DMARemain: call DMAClearFF ; clear DMA channel flip-flop
; ------------- Push registers
push eax ; push EAX
push edx ; push EDX
; ------------- Prepare port for controller 1 (01h, 03h, 05h, 07h)
movzx eax,al ; EAX <- DMA channel number
shl eax,1 ; AX <- channel number*2
inc eax ; EAX <- channel number*2 + 1
cmp al,4*2+1 ; is it DMA0 to DMA3?
jb DMARemain2 ; it is DMA0 to DMA3
; ------------- Prepare port for controller 2 (0C2h, 0C6h, 0CAh, 0CEh)
shl eax,1 ; AX <- channel number*4 + 2
add al,0c0h-4*4 ; AL <- port
; ------------- Get transfer size
DMARemain2: xchg eax,edx ; DX <- port
xor eax,eax ; EAX <- 0
in al,dx ; get transfer size LOW
xchg eax,ecx ; ECX <- transfer size LOW
in al,dx ; get transfer size HIGH
mov ch,al ; CH <- transfer size HIGH
inc ecx ; correction
; ------------- Pop registers
pop edx ; pop EDX
pop eax ; pop EAX
; ------------- Convert to words for DMA4 - DMA7
cmp al,4 ; is it DMA4 to DMA7?
jb DMARemain4 ; it is DMA0 to DMA3
shl ecx,1 ; convert to words
DMARemain4: ret
; -----------------------------------------------------------------------------
; Start DMA transfer
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; BL = DMA mode: 0=read, 1=write, 2=verify
; ECX = DMA transfer size (cannot be 0; B0 is ignored for DMA4-7)
; EDX = DMA address (physical address, only bits 0-23 are used)
; NOTES: AL not checked for validity.
; DMA transfer cannot cross 64K (or 128K for DMA4-7) boundary.
; Maximal transfer size is 64K for DMA0-3 and 128K for DMA4-7.
; DMA5 to DMA7 address must be word aligned.
; Transfers are limited to lower 16 MB of physical memory.
; It locks and unlocks DMA lock.
; -----------------------------------------------------------------------------
; ------------- Disable interrupts
DMAStart: pushf ; push flags
cli ; disable interrupts
; ------------- Lock DMA lock
DMALOCK ; lock DMA controller
; ------------- Set DMA mode
push ebx ; push EBX
cmp bl,1 ; write mode?
mov bl,DMAMODE_READ ; BL <- DMA read mode
jb DMAStart2 ; read mode
mov bl,DMAMODE_WRITE; BL <- DMA write mode
je DMAStart2 ; write mode
mov bl,DMAMODE_VERIFY ; BL <- DMA verify mode
DMAStart2: call DMASetMode ; set DMA mode
pop ebx ; pop EBX
; ------------- Set transfer address
call DMASetAddr ; set DMA address
; ------------- Set transfer size
call DMASetSize ; set DMA size
; ------------- Start DMA transfer
call DMAEnable ; start DMA transfer
; ------------- Unlock DMA lock
DMAUNLOCK ; unlock DMA controller
; ------------- Pop flags (to enable interrupts)
popf ; pop flags
ret
; -----------------------------------------------------------------------------
; Test if DMA transfer is still running
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; OUTPUT: CY = DMA transfer is still running
; NOTES: AL not checked for validity.
; It locks and unlocks DMA lock.
; -----------------------------------------------------------------------------
; ------------- Push registers and disable interrupts
DMATest: push ecx ; push ECX
pushf ; push flags
cli ; disable interrupts
; ------------- Lock DMA lock
DMALOCK ; lock DMA controller
; ------------- Get remaining transfer size (-> ECX)
call DMARemain ; get remaining transfer size
; ------------- Unlock DMA lock
DMAUNLOCK ; unlock DMA controller
; ------------- Pop flags (to enable interrupts) and pop registers
popf ; pop flags
cmp ecx,byte 1 ; check if transfer finished
cmc ; CY = transfer not finished yet
pop ecx ; pop ECX
ret
; -----------------------------------------------------------------------------
; Stop DMA transfer
; -----------------------------------------------------------------------------
; INPUT: AL = DMA channel number (0 to 7)
; NOTES: AL not checked for validity.
; It locks and unlocks DMA lock.
; It saves flags.
; -----------------------------------------------------------------------------
; ------------- Disable interrupts
DMAStop: pushf ; push flags
cli ; disable interrupts
; ------------- Lock DMA lock
DMALOCK ; lock DMA controller
; ------------- Stop DMA transfer
call DMADisable ; stop DMA transfer
; ------------- Unlock DMA lock
DMAUNLOCK ; unlock DMA controller
; ------------- Pop flags (to enable interrupts)
popf ; pop flags
ret
; -----------------------------------------------------------------------------
; Data
; -----------------------------------------------------------------------------
DATA_SECTION
; ------------- 8237 DMA controller lock
%ifdef SMP
align 4, db 0
Lock8237: SPINLOCK ; 8237 DMA controller lock
%endif
; ------------- DMA page ports
DMAPagePort: db 87h ; DMA 0
db 83h ; DMA 1
db 81h ; DMA 2
db 82h ; DMA 3
db 80h ; DMA 4
db 8bh ; DMA 5
db 89h ; DMA 6
db 8ah ; DMA 7
; ------------- Bitmap of engaged DMA channels (1=channel is used)
align 4, db 0
DMAMap: dd B4 ; DMA 4 reserved for cascade
|