5.10.9. Контроллер DMA

Контроллер DMA используется для обмена данными между внешними уст­ройствами и памятью. Он нужен в работе с жесткими дисками и дисководами, звуковыми платами и другими устройствами, работающими со значительными объемами данных. Начиная с PC AT, в компьютерах присутствуют два DMA-кот-роллера - 8-битный (с каналами О, 1, 2 и 3) и 16-битный (с каналами 4, 5, 6 и 7). Канал 2 используется для обмена данными с дисководами, канал 3 - для жестких дисков, канал 4 теряется при каскадировании контроллеров, а назначение осталь­ных каналов может варьироваться.

DMA позволяет выполнить чтение или запись блока данных, начинающегося с линейного адреса, который описывается как 20-битное число для первого DMA-контроллера и как 24-битное для второго, то есть данные для 8-битного DMA должны располагаться в пределах первого мегабайта памяти, а для второго в пределах первых 16 Мб. Старшие четыре бита для 20-битных адресов и старшие 8 бит для 24-битных адресов хранятся в регистрах страниц DMA, адресуемых через порты 80h - 8Fh:

порт 81к страничный адрес для канала 2 (биты 3-0 = биты 19-16 адреса) порт 82к страничный адрес для канала 3 (биты 3-0 = биты 19-16 адреса) порт 83к страничный адрес для канала 1 (биты 3-0 = биты 19-16 адреса) порт 87к страничный адрес для канала 0 (биты 3-0 = биты 19-16 адреса) порт 89к. страничный адрес для канала 6 (биты 7-0 = биты 23-17 адреса) порт 8Ак страничный адрес для канала 7 (биты 7-0 = биты 23-17 адреса) порт 8Вк страничный адрес для канала 5 (биты 7-0 = биты 23-17 адреса)

Страничный адрес определяет начало участка памяти,

с которым будет работать данный канал, поэтому при передаче данных через DMA обязательно надо следить за тем, чтобы не было выхода за границы этого участка, то есть чтобы не было попытки пересечения адреса 1000h:0, 3000h:0 для первого DMA или 2000h:0, 4000h:0, 6000h:0 для второго.

Младшие 16 бит адреса записывают в следующие порты:

OOh: биты 15-0 адреса блока данных для канала О 01h: счетчик переданных байт канала О

- аналогично для канала 1

- аналогично для канала 2 06h -       аналогично для З

(для этих портов используются две операции чтения/записи - сначала пере­даются биты 7-0, затем биты

OCOh: биты 8-1 адреса блока данных для канала 4 (бит 0 адреса всегда равен нулю) OClh: биты 16-9 адреса блока данных для канала 4 OC2h: младший байт счетчика переданных слов канала 4 старший байт счетчика переданных слов канала 4 OC4h - 0C7h: аналогично для канала З OC8h - OCBh: аналогично для канала З OCCh - OCFh: аналогично для канала З

(эти порты рассчитаны на чтение/запись целыми словами)

Каждый из указанных двух DMA-контроллеров также имеет собственный на­бор управляющих регистров - регистры первого контроллера адресуются через порты 08h - OFh, а второго - через ODO - ODFh:

порт 08h/ODOh для чтения: регистр состояния DMA

бит 7, 6, З, 4: установлен запрос на DMA на канале 3/7, 2/6, 1Д 0/4 бит 3, 2, 1, 0: закончился DMA на канале 3/7, 2/6, 1Д 0/4

порт O8h/D0h для записи: регистр команд DMA (устанавливается BIOS) бит 7: сигнал DACK использует высокий уровень бит 6: сигнал использует высокий урівень бит 5: 1/0: расширенный/задержанный цикл записи

бит 4: 1/0: приоритеты сменяются

бит 3: сжатие во времени

бит 2: DMA-контроллер отключен

бит 1: разрешен захват канала 0 (для режима память-память) бит 0: включен режим память-память (канал 0 - канал 1) порт для записи: регистр запроса DMA

бит 2: 1/0: установка/сброс запроса на DMA

биты 1-0: номер канала (00, 01, 10, 11 = 0/4, 1/З, 2/6, 3/7)

порт OAh/OD4h для записи: регистр маски канала DMA

бит 2: 1/0: установка/сброс маскирующего бита

биты 1-0: номер канала (00, 01, 10, 11 = 0/4, 1Д 2/6, 3/7)

порт OBh/OD6h для записи: регистр режима DMA

биты 7-6: 00 - передача по запросу

01 - одиночная передача (используется для звука)

10 - блочная передача (используется для дисков)

11 - канал занят для каскадирования

бит З: 1/0: адреса уменьшаются/увеличиваются

бит 4: режим автоинициализации биты 3-2:

00 - проверка

01 - запись 10 - чтение

биты 1-0: номер канала (00, 01,10, 11 = 0/4, 1/5, 2/6, 3/7)

порт OCh/OD8h для записи: сброс переключателя младший/старший байт Для чтения/записи 16-битных значений из/в 8-битные порты OOh - 08h.

Очередной байт, переданный в эти порты, будет считаться младшим, а сле­дующий за ним - старшим. порт для записи: сброс контроллера DMA

Любая запись сюда приводит к полному сбросу DMA-контроллера, так что его надо инициализировать заново.

порт ODh/ODAh для чтения: последний переданный байт/слово.

порт OEh/ODCh для записи: любая запись снимает маскирующие биты со всех каналов

порт OFh/ODEh для записи: регистр маски всех каналов:

биты 3-0: маскирующие биты каналов 3/7, 2/6, 1/З, 0/4

Чаще всего внешнее устройство само инициализирует передачу данных, и все, что необходимо сделать программе, - это записать адрес начала буфера в порты, соответствующие используемому каналу, длину передаваемого блока данных ми­нус один в регистр счетчика нужного канала, установить режим работы канала

и снять маскирующий бит.

В качестве примера вернемся к программированию звуковых плат и изменим программу так, чтобы она использовала DMA.

; wavdma.asm

; Пример программы, проигрывающей файл C:\WINDOWS\MEDIA\TADA.WAV ; на звуковой карте при помощи DMA. FILESPEC equ "с:\windows\media\tada.wav

; Заменить на

; для старых версий Windows.

SBPORT equ 220h

; equ І

SBIRQ

start:

equ 5 .model .code .186

org

call

jc

mov

call

call

call

mov

call

mov

call call

tiny

lOOh

dsp_reset

no_blaster

bl,0D1h dsp_write

open_file hook_sbirq

bl,40h

dsp_write bl,OB2h dspjvrite program_dma

main_loop: cmp je

call

rio_blaster:

ret

old_sbirq finished_flag

filename ; Процедура

; лишь на канал 1. ; Только IRQO - IRQ7.

СОМ-программа.

Инициализация ОБР.

Команда 001 її.

Включить звук. Прочитатв файл в буфер. Перехватить   прерывание. Команда 40Ис

Установка скорости передачи. Константа для 11025Hz/Stereo.

: Начать пМА-передачу данных. ; Основной цикл.

byte ptr finished_flag,0

main_loop ; Выход,  когда байт finished_flag = ;1.

restore_sbirq

dd db db

? 0

FILESPEC, 0

Восстановить прерывание.

Адрес старого обработчика. Флаг окончания работы. Имя файла.

Обработчик прерывания звуковой

Устанавливает sbirq_handler

push

mov mov

out

pop і ret

sbirq_handler

флаг finishecbflag в 1.

proc far

ax

byte ptr cs:finished_flag,1

al,20h 20h,al ax

endp

Установить флаг.

Послать команду ЕОІ

в контроллер прерываний.

Процедура dsp_reset. Сброс и инициализация DSP.

Программирование на уровне портов

dsp_reset

proc near

 

mov

dx,SBP0RT+6 ;

Порт 226h - регистр сброса DSP.

mov

al,l ;

Запись в него единицы запускает инициализацию.

out

dx, al

 

mov

40

Небольшая

dsploop:

 

 

in

al.dx

 

loop

dsploop

 

mov

al,0 ;

Запись нуля завершает инициализацию.

out

dx,al ;

Теперь DSP готов к работе.

add

dx,8 ;

Порт       - бит 7 при чтении указывает на занятость

mov

cx.100 ;

буфера записи DSP.

check port:

 

 

in

al.dx ;

Прочитать состояние буфера записи.

and

al,80h         ■ ;

Если бит 7 ноль,

 

port_not_ready ;

порт еще не готов.

sub

dx,4 ;

Иначе: порт        - чтение данных из DSP.

in

al, dx

 

add

dx,4 ;

Порт снова

cmp

al,0AAh ;

Проверить,  что DSP возвращает ОААп при чтении -

 

 

это сигнал о готовности к работе.

je

good_reset

 

port_not_ready:

 

 

loop

check_port

Повторить проверку на ОААп 100 раз.

bad_reset:

 

 

stc

J

Если Sound Blaster не откликается,

ret

;

вернуться с CF =

good_reset:

 

 

clc

]

Если инициализация прошла успешно,

ret

\

вернуться с CF = 0.

dsp_reset

endp

 

;   Процедура dsp_write

 

; Посылает байт

из BL в DSP

 

dsp^write

proc near

 

mov

dx,SBP0RT+0Ch

Порт       - ввод DSP.

write loop:

 

Подождать до готовности буфера записи DSP,

in

al.dx ;

прочитать порт

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+SBIR0 ;

AH = 35h, AL = номер прерывания.

int

21h ;

Получить адрес старого обработчика

mov word ptr old_sbirq, bx      ; и сохранить его.

mov word ptr old_sbirq+2,es

mov ax,2508h+SBIR0 ; АН = 25h, AL = номер прерывания.

mov dx,offset sbirq_handler    ; Установитв   новый обработчик.

int 21h

mov cl,l

Shi Cl.SBIRQ

not cl ; Построить битовую маску.

in al,21h ; Прочитатв 0CW1.

and al,cl ; Разрешить прерывание.

Out 21h,al ; Записатв 0CW1.

ret

hook_sbirq endp

; Процедура  restore_sbirq. '. ; Восстанавливает' обработчик и запрещает прерывание.

restore_sbirq proc near

mov        ax,3508h+SBIR0 ;  AH = 25h,  AL = номер прерывания.

Ids dx.dword ptr old_sbirq

int 2lh ;   Восстановитв обработчик,

mov cl ,1

shl        cl.SBIRQ ;   Построитв битовую маску.

in al,21h ;   Прочитатв 0CW1.

or • al,cl ;   Запретитв прерывание

out        21h,al ;   Записатв 0CW1.

ret

restore_sbirq endp ;   Процедура open_file.

;   Открывает файл filename и копирует звуковые данные из него,   считая, что

;  это tada.wav, в буфер buffer. open_file proc near

mov ax,3D00h ; AH = 30h, 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        сх,27459 ; Это - длина данных в файле tada.wav.

■    .   push ds

mov dx,ds

and        dx.OFOOOh ;   Выравнятв буфер на границу 4-килобэйтной страницы

add        dx, 1000h ;  для DMA.

mov ds,dx

mov dx.O ;   DS:0X - адрес буфера,

int        21h •;  Чтение файла.

pop ds ret

error_exit; ; Если не удалось открыть файл.

mov ah,9 ■ ; АН = 09h.

mov dx, offset notopenmsg DS:DX = адрес сообщения об ошибке,

int 21h ; Вывод строки на экран,

int 20h ; Конец программы.

; сообщение об ошибке

notopenmsg db        "Ошибка при открытии файла",00h,OAh,'$' .

open_file endp

;  Процедура program_dma.

; Настраивает канал 1 DMA.

program_dma proc near

mov al,5 ; Замаскировать канал 1:

out 0Ah,al

xor al.al ; Обнулить счетчик,

out OCh.al

mov al,49h ■  ; Установить режим передачи

;   (используйте 59h для автоинициализации).

out 0Bh,al

push   ^ cs

pop dx

and dh.OFOh

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,l

out OAh.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 Ы, bh

call dsp^write ; и старшие.

ret

program_dma endp

end start

Сложные приемы

В этом примере задействован обычный DMA-режим работы, в котором зву­ковая плата проигрывает участок данных, вызывает прерывание и, пока обра­ботчик прерывания подготавливает новый буфер данных, программирует DMA

и звуковую плату для продолжения воспроизведения, проходит некоторое вре­мя, что может звучать как щелчок. Этого можно избежать, если воспользоваться режимом автоинициализации, позволяющим обойтись без остановок во время воспроизведения.

При использовании режима DMA с автоинициализацией нужно сделать следу­ющее: загрузить начало воспроизводимого звука в буфер длиной, например, 8 Кб и запрограммировать DMA на его передачу с автоинициализацией. Затем! сооб­щить DSP, что проигрывается звук с автоинициализацией и размер равен 4 Кб. Далее, когда придет прерывание от звуковой платы, она не остановится и про­должит воспроизведение из вторых 4 Кб буфера, поскольку находится в режиме автоинициализации. Теперь запишем в первые 4 Кб следующий блок данных. Когда кончится буфер, DMA начнет посылать его пото-

му что мы его тоже запрограммировали для автоинициализации (бит 4 порта DSP вызовет прерывание и тоже не остановится, продолжая воспро­изводить данные, которые посылает ему DMA-контроллер, а мы тем временем за­пишем во вторые 4 Кб буфера следующий участок проигрываемого файла и т. д.