[Назад] [Далее]

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

Контроллер 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:

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

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

Каждый из этих двух DMA-контроллеров также имеет собственный набор управляющих регистров — регистры первого контроллера адресуются через порты 08h – 0Fh, а второго — через D0 – DFh:

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

В качестве примера вернемся к программированию звуковых плат и изменим программу 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 Кб буфера следующий участок проигрываемого файла и т.д.


п»ї
"target=_blank><\/a>") //-->