| [Назад] [Далее] | ![]() |
Контроллер DMA используется для обмена данными между внешними устройствами и памятью. Он нужен в работе с жесткими дисками и дисководами, звуковыми платами и другими устройствами, работающими со значительными объемами данных. Начиная с PC AT, в компьютерах присутствуют два DMA-котроллера — 8-битный (с каналами 0, 1, 2 и 3) и 16-битный (с каналами 4, 5, 6 и 7). Канал 2 используется для обмена данными с дисководами, канал 3 — для жестких дисков, канал 4 теряется при каскадировании контроллеров, а назначение остальных каналов может варьироваться.
DMA позволяет выполнить чтение или запись блока данных, начинающегося с линейного адреса, описываемого как 20-битное число для первого DMA-контроллера и как 24-битное — для второго, то есть данные для 8-битного DMA должны располагаться в пределах первого мегабайта памяти, а для второго — в пределах первых 16 Мб. Старшие четыре бита для 20-битных адресов и старшие 8 бит для 24-битных адресов хранятся в регистрах страниц DMA, адресуемых через порты 80h – 8Fh:
порт 81h: страничный адрес для канала 2 (биты 3 – 0 = биты 19 – 16 адреса)
порт 82h: страничный адрес для канала 3 (биты 3 – 0 = биты 19 – 16 адреса)
порт 83h: страничный адрес для канала 1 (биты 3 – 0 = биты 19 – 16 адреса)
порт 87h: страничный адрес для канала 0 (биты 3 – 0 = биты 19 – 16 адреса)
порт 89h: страничный адрес для канала 6 (биты 7 – 0 = биты 23 – 17 адреса)
порт 8Ah: страничный адрес для канала 7 (биты 7 – 0 = биты 23 – 17 адреса)
порт 8Bh: страничный адрес для канала 5 (биты 7 – 0 = биты 23 – 17 адреса)
Страничный адрес определяет начало 64 Кб/128 Кб участка памяти, с которым будет работать данный канал, поэтому при передаче данных через DMA обязательно надо следить за тем, чтобы не было выхода за границы этого участка, то есть чтобы не было попытки пересечения адреса 1000h:0, 2000h:0, 3000h:0 для первого DMA или 2000h:0, 4000h:0, 6000h:0 — для второго.
Младшие 16 бит адреса записывают в следующие порты:
00h: биты 15 – 0 адреса блока данных для канала 0
01h: счетчик переданных байт канала 0
02h – 03h: аналогично для канала 1
04h – 05h: аналогично для канала 2
06h – 07h: аналогично для канала 3
(для этих портов используются две операции чтения/записи — сначала передаются биты 7 – 0, затем биты 15 – 8)
C0h: биты 8 – 1 адреса блока данных для канала 4 (бит 0 адреса всегда равен нулю)
C1h: биты 16 – 9 адреса блока данных для канала 4
C2h: младший байт счетчика переданных слов канала 4
C3h: старший байт счетчика переданных слов канала 4
C4h – C7h: аналогично для канала 5
C8h – CBh: аналогично для канала 5
CCh – CFh: аналогично для канала 5
(эти порты рассчитаны на чтение/запись целыми словами)
Каждый из этих двух DMA-контроллеров также имеет собственный набор управляющих регистров — регистры первого контроллера адресуются через порты 08h – 0Fh, а второго — через D0 – DFh:
порт 08h/D0h для чтения: регистр состояния DMA
бит 7, 6, 5, 4: установлен запрос на DMA на канале 3/7, 2/6, 1/5, 0/4
бит 3, 2, 1, 0: закончился DMA на канале 3/7, 2/6, 1/5, 0/4
порт 08h/D0h для записи: регистр команд DMA (устанавливается BIOS)
бит 7: сигнал DACK использует высокий уровень
бит 6: сигнал DREQ использует высокий уровень
бит 5: 1/0 — расширенный/задержанный цикл записи
бит 4: 1/0 — приоритеты сменяются циклически/фиксированно
бит 3: сжатие во времени
бит 2: DMA-контроллер отключен
бит 1: разрешен захват канала 0 (для режима память-память)
бит 0: включен режим память-память (канал 0 – канал 1)
порт 09h/D2h для записи: регистр запроса DMA
бит 2: 1/0 — установка/сброс запроса на DMA
биты 1 – 0: номер канала (00, 01, 10, 11 = 0/4, 1/5, 2/6, 3/7)
порт 0Ah/D4h для записи: регистр маски канала DMA
бит 2: 1/0 — установка/сброс маскирующего бита
биты 1 – 0: номер канала (00, 01, 10, 11 = 0/4, 1/5, 2/6, 3/7)
порт 0Bh/D6h для записи: регистр режима DMA
биты 7 – 6:
00 — передача по запросу
01 — одиночная передача (используется для звука)
10 — блочная передача (используется для дисков)
11 — канал занят для каскадирования
бит 5: 1/0 — адреса уменьшаются/увеличиваются
бит 4: режим автоинициализации
биты 3 – 2:
00 — проверка
01 — запись
10 — чтение
биты 1 – 0: номер канала (00, 01, 10, 11 = 0/4, 1/5, 2/6, 3/7)
порт 0Ch/D8h для записи: сброс переключателя младший/старший байт
Для чтения/записи 16-битных значений из/в 8-битные порты 00h – 08h. Следующий байт, переданный в эти порты, будет считаться младшим, следующий за ним — старшим.
порт 0Dh/DAh для записи: сброс контроллера DMA
Любая запись сюда приводит к полному сбросу DMA-контроллера, так что его надо инициализировать заново.
порт 0Dh/DAh для чтения: последний переданный байт/слово.
порт 0Eh/DCh для записи: любая запись снимает маскирующие биты со всех каналов
порт 0Fh/DEh для записи: регистр маски всех каналов:
биты 3 – 0: маскирующие биты каналов 3/7, 2/6, 1/5, 0/4
Чаще всего внешнее устройство само инициализирует передачу данных, и все, что необходимо сделать программе, — это записать адрес начала буфера в порты, соответствующие используемому каналу, длину передаваемого блока данных минус один в регистр счетчика соответствующего канала, установить нужный режим работы канала и снять маскирующий бит.
В качестве примера вернемся к программированию звуковых плат и изменим программу wavdir.asm так, чтобы она использовала DMA.
; wavdma.asm
; Пример программы, проигрывающей файл C:\WINDOWS\MEDIA\TADA.WAV
; на звуковой карте при помощи DMA
FILESPEC equ "c:\windows\media\tada.wav" ; заменить на c:\windows\tada.wav
; для старых версий Windows
SBPORT equ 220h
; SBDMA equ 1 ; процедура program_dma рассчитана только на канал 1
SBIRQ equ 5 ; только IRQ0 - IRQ7
.model tiny
.code
.186
org 100h ; СОМ-программа
start:
call dsp_reset ; инициализация DSP
jc no_blaster
mov bl,0D1h ; команда OD1h
call dsp_write ; включить звук
call open_file ; прочитать файл в буфер
call hook_sbirq ; перехватить прерывание
mov bl,40h ; команда 40h
call dsp_write ; установка скорости передачи
mov bl,0B2h ; константа для 11025Hz/Stereo
call dsp_write
call program_dma ; начать DMA-передачу данных
main_loop: ; основной цикл
cmp byte ptr finished_flag,0
je main_loop ; выход, когда байт finished_flag = 1
call restore_sbirq ; восстановить прерывание
no_blaster:
ret
old_sbirq dd ? ; адрес старого обработчика
finished_flag db 0 ; флаг окончания работы
filename db FILESPEC,0 ; имя файла
; обработчик прерывания звуковой карты
; устанавливает флаг finished_flag в 1
sbirq_handler proc far
push ax
mov byte ptr cs:finished_flag,1 ; установить флаг
mov al,20h ; послать команду EOI
out 20h,al ; в контроллер прерываний
pop ax
iret
sbirq_handler endp
; процедура dsp_reset
; сброс и инициализация DSP
dsp_reset proc near
mov dx,SBPORT+6 ; порт 226h - регистр сброса DSP
mov al,1 ; запись в него единицы
; запускает инициализацию
out dx,al
mov cx,40 ; небольшая пауза
dsploop:
in al,dx
loop dsploop
mov al,0 ; запись нуля завершает инициализацию
out dx,al ; теперь DSP готов к работе
add dx,8 ; порт 22Eh - бит 7 при чтении
; указывает на занятость
mov сх,100 ; буфера записи DSP
check_port:
in al,dx ; прочитать состояние буфера записи,
and al,80h ; если бит 7 ноль,
jz port_not_ready ; порт еще не готов,
sub dx,4 ; иначе: порт 22Аh - чтение данных из DSP
in al,dx
add dx,4 ; порт снова 22Eh
cmp al,0AAh ; проверить, что DSP возвращает 0AAh
; при чтении - это сигнал его готовности
; к работе
je good_reset
port_not_ready:
loop check_port ; повторить проверку на 0AAh 100 раз,
bad_reset:
stc ; если Sound Blaster не откликается,
ret ; вернуться с CF = 1,
good_reset:
clc ; если инициализация прошла успешно,
ret ; вернуться с CF = 0
dsp_reset endp
; процедура dsp_write
; посылает байт из BL в DSP
dsp_write proc near
mov dx,SBPORT+0Ch ; порт 22Ch - ввод данных/команд DSP
write_loop: ; подождать готовности буфера записи DSP,
in al,dx ; прочитать порт 22Ch
and al,80h ; и проверить бит 7,
jnz write_loop ; если он не ноль - подождать еще,
mov al,bl ; иначе:
out dx,al ; послать данные
ret
dsp_write endp
; процедура hook_sbirq
; перехватывает прерывание звуковой карты и разрешает его
hook_sbirq proc near
mov ax,3508h+SBIRQ ; AH = 35h, AL = номер прерывания
int 21h ; получить адрес старого обработчика
mov word ptr old_sbirq,bx ; и сохранить его
mov word ptr old_sbirq+2,es
mov ax,2508h+SBIRQ ; AH = 25h, AL = номер прерывания
mov dx,offset sbirq_handler ; установить новый обработчик
int 21h
mov cl,1
shl cl,SBIRQ
not cl ; построить битовую маску
in al,21h ; прочитать OCW1
and al,cl ; разрешить прерывание
out 21h,al ; запиать OCW1
ret
hook_sbirq endp
; процедура restore_sbirq
; восстанавливает обработчик и запрещает прерывание
restore_sbirq proc near
mov ax,3508h+SBIRQ ; AH = 25h, AL = номер прерывания
lds dx,dword ptr old_sbirq
int 21h ; восстановить обработчик
mov cl,1
shl cl,SBIRQ ; построить битовую маску
in al,21h ; прочитать OCW1
or al,cl ; запретить прерывание
out 21h,al ; записать OCW1
ret
restore_sbirq endp
; процедура open_file
; открывает файл filename и копирует звуковые данные из него,
; считая, что это - tada.wav, в буфер buffer
open_file proc near
mov ax,3D00h ; AH = 3Dh, AL = 00
mov dx,offset filename ; DS:DX - ASCIZ-строка с именем файла
int 21h ; открыть файл для чтения,
jc error_exit ; если не удалось открыть файл - выйти
mov bx,ax ; идентификатор файла в ВХ
mov ax,4200h ; АН = 42h, AL = 0
mov cx,0 ; CX:DX - новое значение указателя
mov dx,38h ; по этому адресу начинаются данные
; в tada.wav
int 21h ; переместить файловый указатель
mov ah,3Fh ; АН = 3Fh
mov cx,27459 ; это - длина данных в файле tada.wav
push ds
mov dx,ds
and dx,0F000h ; выровнять буфер на границу
; 4-килобайтной страницы
add dx,1000h ; для DMA
mov ds,dx
mov dx,0 ; DS:DX - адрес буфера
int 21h ; чтение файла
pop ds
ret
error_exit: ; если не удалось открыть файл,
mov ah,9 ; АН = 09h
mov dx,offset notopenmsg ; DS:DX = адрес сообщения об ошибке
int 21h ; вывод строки на экран
int 20h ; конец программы
; сообщение об ошибке
notopenmsg db "Ошибка при открытии файла",0Dh,0Ah,'$'
open_file endp
; процедура program_dma
; настраивает канал 1 DMA
program_dma proc near
mov al,5 ; замаскировать канал 1
out 0Ah,al
xor al,al ; обнулить счетчик
out 0Ch,al
mov al,49h ; установить режим передачи
; (используйте 59h для автоинициализации)
out 0Bh,al
push cs
pop dx
and dh,0F0h
add dh,10h ; вычислить адрес буфера
xor ax,ax
out 02h,al ; записать младшие 8 бит
out 02h,al ; записать следующие 8 бит
mov al,dh
shr al,4
out 83h,al ; записать старшие 4 бита
mov ax,27459 ; длина данных в tada.wav
dec ax ; DMA требует длину минус один
out 03h,al ; записать младшие 8 бит длины
mov al,ah
out 03h,al ; записать старшие 8 бит длины
mov al,1
out 0Ah,al ; снять маску с канала 1
mov bl,14h ; команда 14h
call dsp_write ; 8-битное простое DMA-воспроизведение
mov bx,27459 ; размер данных в tada.wav
dec bx ; минус 1
call dsp_write ; записать в DSP младшие 8 бит длины
mov bl,bh
call dsp_write ; и старшие
ret
program_dma endp
end start
В этом примере задействован обычный DMA-режим работы, в котором звуковая плата проигрывает участок данных, вызывает прерывание, и, пока обработчик прерывания подготавливает новый буфер данных, программирует DMA и звуковую плату для продолжения воспроизведения, проходит некоторое время, что может звучать как щелчок. Этого можно избежать, если воспользоваться режимом автоинициализации, который позволяет обойтись без остановок при воспроизведении.
При использовании режима DMA с автоинициализацией нужно сделать следующее: загрузить начало воспроизводимого звука в буфер длиной, например, 8 Кб и запрограммировать DMA на его передачу с автоинициализацией. Затем сообщить DSP, что проигрывается звук с автоинициализацией и размер буфера равен 4 Кб. Теперь, когда придет прерывание от звуковой платы, она не остановится и продолжит воспроизведение из вторых 4 Кб буфера, так как находится в режиме автоинициализации. Далее запишем в первые 4 Кб следующий блок данных. Когда кончится 8-килобайтный буфер, DMA начнет посылать его сначала, потому что мы его тоже запрограммировали для автоинициализации (бит 4 порта 0Bh/D6h), DSP вызовет прерывание и тоже не остановится, продолжая воспроизводить данные, которые посылает ему DMA-контроллер, а мы тем временем запишем во вторые 4 Кб буфера следующий участок проигрываемого файла и т.д.