9. Контроллер прямого доступа к памяти

Прямой доступ к памяти (Direct Memory Access - DMA) используется для выполнения операций передачи данных непосредственно между оперативной памятью и устройствами ввода/вывода. Обычно это такие устройства, как НГМД, НМД, кассетные накопители на магнитной ленте КНМЛ (стримеры).

При использовании DMA процессор не участвует в операциях ввода/вывода, контроллер прямого доступа сам формирует все сигналы, необходимые для обмена данными с устройством. Скорость такого непосредственного обмена значительно выше, чем при традиционном вводе/выводе с использованием центрального процессора и команд INP, OUT.

Мы уже немного рассказывали о контроллере прямого доступа к памяти в третьей книге первого тома, в разделе, посвященном работе с НГМД на уровне команд ввода/вывода. Была приведена программа, использующая DMA для чтения секторов дискеты. В этом разделе мы подробнее рассмотрим порты контроллера DMA.

Распространены два типа контроллеров DMA - контроллеры для IBM PC/XT и контроллеры для IBM AT. Вначале мы расскажем о первом типе контроллеров, затем займемся контроллером DMA компьютера IBM AT.

9.1. Контроллер прямого доступа для IBM PC/XT

Контроллер прямого доступа для IBM PC/XT реализован на базе микросхемы Intel 8237A и содержит четыре канала. Эти каналы используются следующим образом:

0 обновление содержимого динамической памяти компьютера, этот канал имеет наивысший приоритет;
1 не используется;
2 адаптер накопителя на гибком магнитном диске (НГМД);
3 адаптер накопителя на магнитном диске (НМД) - этот канал имеет низший приоритет.

9.1.1. Регистры каналов DMA

Каждый канал содержит 16-разрядные регистры:

Приведем адреса регистров и их форматы для компьютеров IBM PC/XT.

Порты 00h - 07h

Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 0 - 3. Их назначение приводится в следующей таблице:

00h Запись: Базовый адрес канала 0
Чтение: Текущий адрес
01h Запись: Счетчик канала 0
Чтение: Текущий адрес
02h Запись: Базовый адрес канала 1
Чтение: Текущий адрес
03h Запись: Счетчик канала 1
Чтение: Текущий адрес
04h Запись: Базовый адрес канала 2
Чтение: Текущий адрес
05h Запись: Счетчик канала 2
Чтение: Текущий адрес
06h Запись: Базовый адрес канала 3
Чтение: Текущий адрес
07h Запись: Счетчик канала 3
Чтение: Текущий адрес

Порт 08h.

Этот порт используется при записи в качестве управляющего регистра и при чтении как регистр состояния.

Формат управляющего регистра:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-T-¬
| | | | | | | | |
LT+T+T+T+T+T+T+T-
 | | | | | | | L= 1 - использование режима память-память;
 | | | | | | |    0 - обычный режим работы;
 | | | | | | |
 | | | | | | L=== если используется режим память-память,
 | | | | | |      то 1 в этом разряде разрешает захват 
 | | | | | |      канала, 0 - запрещает;
 | | | | | |      в обычном режиме работы состояние этого
 | | | | | |      бита безразлично;
 | | | | | |
 | | | | | L===== 1 - запрет работы DMA;
 | | | | |        0 - разрешение работы DMA;
 | | | | |
 | | | | L======= 1 - использование сжатия во времени, если
 | | | |              установлен бит обычного режима работы;
 | | | |          0 - обычный режим работы;
 | | | |
 | | | L========= 1 - вращение приоритетов;
 | | |            0 - фиксированные приоритеты;
 | | |
 | | L=========== 1 - удлиненный цикл записи;
 | |              0 - нормальный цикл записи;
 | |
 | L============= 1 - используется низкий уровень для
 |                    сигнала запроса на DMA DREQ;
 |                0 - используется высокий уровень;
 |
 L=============== 1 - используется высокий уровень для
                  сигнала подтверждения DMA DACK;
                  0 - используется низкий уровень;


Обычно этот регистр инициализируется BIOS в процессе тестирования системы и впоследствии изменять режим работы контроллера DMA не требуется. Ошибки при инициализации этого порта могут привести к "зависанию" системы.

При чтении из порта 08h программа получает слово состояния контроллера DMA:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T---¬
|       |       |
LT+-+-+T+T+-+-+T-
 L==T==- L=====|= биты 0-3 устанавливаются в 1 при
    |             достижении счетчиками каналов 0-3
    |             конечных значений;
    |
    L============ биты 4-7 установлены в 1, если
                  имеется разрешение на DMA
                  соответственно, каналов 0-3.


Порт 09h.

Регистр запроса. Предназначен для организации программного (а не аппаратного) запроса на DMA. Для использования программного запроса канал должен быть запрограммирован в режиме блочной передачи.

Формат регистра:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T---¬
|         | |   |
LT+-+-+-+T+T+T+T-
 L===T===- | L=|= номер используемого канала:
     |     |        00 - канал 0;
     |     |        01 - канал 1;
     |     |        10 - канал 2;
     |     |        11 - канал 3;
     |     |
     |     L===== 0 - установить запрос;
     |            1 - сбросить запрос;
     |
     L=========== не используются.


Порт 0Ah

Регистр маски. Используется для маскирования запросов на прямой доступ для отдельных каналов:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T---¬
|         | |   |
LT+-+-+-+T+T+T+T-
 L===T===- | L=|= номер канала:
     |     |        00 - канал 0;
     |     |        01 - канал 1;
     |     |        10 - канал 2;
     |     |        11 - канал 3;
     |     |
     |     L===== 0 - установить маску;
     |            1 - сбросить маску;
     |
     L=========== не используются.


Порт 0Bh

Регистр режима. Служит для определения режимов работы каналов контроллера DMA:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T---¬
|   | | |   |   |
LT+T+T+T+T+T+T+T-
 L=| | | L=| L=|= номер канала:
   | | |   |        00 - канал 0;
   | | |   |        01 - канал 1;
   | | |   |        10 - канал 2;
   | | |   |        11 - канал 3;
   | | |   |
   | | |   L===== тип цикла DMA:
   | | |            00 - цикл проверки;
   | | |            01 - цикл записи;
   | | |            10 - цикл чтения;
   | | |            11 - запрещенная комбинация;
   | | |
   | | L========= 1 - режим автоинициализации;
   | |
   | L=========== приращение адреса:
   |                0 - инкрементирование;
   |                1 - декрементирование;
   |
   L============= режим обслуживания:
                    00 - передача по требованию;
                    01 -    одиночная передача;
                    10 -    блочная передача;
                    11 -    каскадироание.


Порт 0Ch

Сброс триггера байтов. Для загрузки внутренних 16-разрядных регистров контроллера используется последовательный вывод младшего, затем старшего байтов слова. После сброса триггера байтов можно начинать загрузку 16-разрядных регистров.

Порт 0Dh

Запись в этот порт вызывает сброс контроллера. Для дальнейшего использования контроллер должен быть заново проинициализирован.

Порт 0Eh

Сброс регистра маски. После записи в этот регистр любого значения разрешается работа всех четырех каналов прямого доступа.

Порт 0Fh

Маскирование/размаскирование каналов. С помощью этого порта можно выполнить одновременное маскирование или размаскирование нескольких каналов:

 7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-T-¬
|       | | | | |
LT+-+-+T+T+T+T+T-
 L==T==- | | | L= 1 - маскирование канала 0;
    |    | | |    0 - разрешение канала 0;
    |    | | |
    |    | | L=== 1 - маскирование канала 1;
    |    | |      0 - разрешение канала 1;
    |    | |
    |    | L===== 1 - маскирование канала 2;
    |    |        0 - разрешение канала 2;
    |    |
    |    L======= 1 - маскирование канала 3;
    |             0 - разрешение канала 3;
    |
    L============ не используются.



Порты 81h-8Fh

Это порты регистров страниц.

Для работы с памятью контроллер прямого доступа использует 20-разрядные физические адреса. Шестнадцать младших битов адреса необходимо записать в регистр базового адреса канала. Старшие четыре бита - биты 16-19 - должны быть записаны в соответствующие порты регистров страниц.

При инициализации регистров базового адреса и регистра страниц необходимо следить за тем, чтобы в процессе передачи данных не происходил переход за границу 64 килобайта.

Для адресации регистров страниц можно использовать следующие порты:

81h Регистр страниц канала 2
82h Регистр страниц канала 3
83h Регистр страниц канала 1

9.1.2. Инициализация канала DMA

Для инициализации канала программа должна выполнить следующие шаги:

Сразу после разрешения канал начинает передачу данных. После окончания передачи данных устройство обычно вырабатывает прерывание, которое служит признаком окончания передачи данных.

9.2. Контроллер прямого доступа для IBM AT

Контроллер DMA компьютера IBM AT совместим снизу вверх с контролером IBM PC/XT. Он состоит из двух каскадно включенных микросхем Intel 8237A-5. Второй контроллер обслуживает каналы DMA с номерами 4-7.

Приведем назначение каналов DMA для IBM AT:

0 зарезервировано;
1 управление синхронной передачей данных SDLC (Synchronous Data Link Control);
2 адаптер накопителя на гибком магнитном диске (НГМД);
3 адаптер накопителя на магнитном диске (НМД);
4 используется для каскадного соединения с первым контроллером DMA;
5-6 зарезервировано.

Другое отличие - это разрядность каналов. Каналы 0-3 являются каналами 8-битовой передачи данных, а каналы 4-7 обеспечивают 16-битовую передачу данных. В связи с этим используются все 8 битов регистров страниц. Формируется 24-битовый адрес из 16-ти младших битов адреса, записываемых в базовые регистры и 8-ми старших битов адреса, записываемых в регистры страниц.

Размер страницы составляет 128 килобайт, поэтому при передаче данных с использованием DMA не должна пересекаться граница 128 килобайт.

Приведем назначение и адреса регистров страниц контроллера для IBM AT:

81h Регистр страниц канала 2
82h Регистр страниц канала 3
83h Регистр страниц канала 1
87h Регистр страниц канала 0
89h Регистр страниц канала 6
8Bh Регистр страниц канала 5
8Ah Регистр страниц канала 7
8Fh Регенерация динамической памяти

Для 16-битовых каналов 4-7 передача данных начинается с границы слова и все адреса относятся к 16-битовым словам.

Порты 0C0h - 0DFh

Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 4-7. Их назначение приводится в следующей таблице:

0C0h Запись: Базовый адрес канала 4
Чтение: Текущий адрес
0C2h Запись: Счетчик канала 4
Чтение: Текущий адрес
0C4h Запись: Базовый адрес канала 5
Чтение: Текущий адрес
0C6h Запись: Счетчик канала 5
Чтение: Текущий адрес
0C8h Запись: Базовый адрес канала 6
Чтение: Текущий адрес
0CAh Запись: Счетчик канала 6
Чтение: Текущий адрес
0CCh Запись: Базовый адрес канала 7
Чтение: Текущий адрес
0CEh Запись: Счетчик канала 7
Чтение: Текущий адрес

Порты 0D0h-0DFh

Это управляющие порты и порты состояния второй микросхемы 8237A-5. По формату и назначению они соответствуют рассмотренным ранее для контроллера DMA компьютеров IBM PC/XT:

0D0h Управляющий регистр / регистр состояния
0D2h Регистр запроса
0D4h Регистр маски
0D6h Регистр режима
0D8h Сброс триггера байтов
0DAh Сброс контроллера
0DCh Сброс регистра маски
0DEh Маскирование/размаскирование каналов

В качестве примера использования контроллера прямого доступа к памяти приведем программу чтения сектора флоппи-диска. Мы уже описывали ее в предыдущем томе. Поэтому здесь мы не будем описывать команды контроллера НГМД и другие тонкости, имеющие отношение к работе с флоппи-дисками.

Перед началом инициализации КПДП программа должна послать в порты 0Bh и 0Ch код операции, которая будет выполняться КПДП - 46h для операции чтения и 4Ah для операции записи.

В процессе инициализации программа должна сообщить КПДП адрес буфера, куда ему следует поместить данные или откуда надо взять данные, и длину передаваемых данных в байтах.

Адрес необходимо представить в виде номера страницы и смещения. Для КПДП машины AT используется восьмибитовый номер страницы и 16-битовое смещение. Например, для адреса 23456 номер страницы - 2, смещение - 3456.

Для программирования канала 2 КПДП программа должна сначала вывести младший байт смещения в порт с адресом 4, затем вывести в этот же порт старший байт смещения и, наконец, вывести байт номера страницы в порт с адресом 81h.

Длина передаваемых данных выводится аналогично в порт с адресом 5 - сначала младший байт длины, затем старший.

После определения режима работы канала, адреса буфера и длины передаваемых данных, программа должна разрешить работу КПДП, выдав в порт с адресом 0Ch байт 2. Теперь канал прямого доступа готов к работе и будет ждать данных от контроллера НГМД.

Приведенная ниже демонстрационная программа использует несколько наиболее характерных команд контроллера НГМД. Она предназначена для работы на машине AT. Для того, чтобы она правильно работала и на машинах PC/XT, ее надо немного изменить. Изменения касаются программирования контроллера ПДП и программирования скорости передачи контроллера НГМД.

Контроллер КПДП PC/XT использует 4-битовый номер страницы буфера вместо 8-битового. Скорость передачи контроллера НГМД в машинах PC/XT не программируется, вам надо убрать соответствующие строки из программы. Еще надо обратить внимание на различное быстродействие машин AT и PC/XT и скорректировать константы в строках программы, выполняющих задержку.

Программа не проверяет, установлен ли флоппи-диск в приемный карман дисковода, поэтому перед запуском не забудьте установить диск.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include "sysp.h"

#define CYL 0

void main(void);
void fdc_out(unsigned char byte);
int  fdc_inp(void);
void int_wait(void);
void dma_init(char *);

void main(void) {

        unsigned i;
        long l;
        char buffer[512];
        char status[7], main_status;
        DPT _far *fdpt;
        FILE *sect;

        printf("\n"
                 "\nРабота с контроллером НГМД"
                 "\n  ©Фролов А., 1991"
                 "\n");


// Эта программа предназначена только для IBM AT

        if(pc_model() != 0xfc) {
                printf("Эта программа предназначена только для IBM AT\n");
                exit(-1);
        }

// Открываем файл, в который будем записывать
// содержимое самого первого сектора на дискете

        sect = fopen("!sector.dat","wb+");

// Устанавливаем указатель на таблицу
// параметров дискеты

        fdpt = get_dpt();

// Включаем мотор дисковода А:
// Перед этим разрешаем прерывания

     _enable();
        outp(0x3F2, 0x1C);

// Выполняем задержку для разгона двигателя

        for(l=0;l<200000;l++);

// Показываем содержимое регистра основного
// состояния контроллера

        printf("Мотор включен.\t\t");
        printf("Основное состояние: %02.2X\n",inp(0x3F4));

// Перед чтением сектора необходимо установить
// головку на нужную дорожку, в нашем случае это
// дорожка с номером CYL.

// Выдаем контроллеру команду "Поиск"

        fdc_out(0xf);

// Для команды "Поиск" требуется два байта параметров:
// номер головки/номер накопителя и номер дорожки.
// Мы работаем с нулевой головкой накопителя А:,
// поэтому первый параметр равен 0, второй - CYL

        fdc_out(0);
        fdc_out(CYL);

// Показываем содержимое регистра основного
// состояния контроллера

        printf("\n<<<Поиск>>> \t\t");
        printf("Основное состояние: %02.2X\n",inp(0x3F4));

// Ожидаем прерывание по завершению операции

        int_wait();

// Задержка для позиционирования головки

        for(l=0;l<20000;l++);

// Для проверки результата выполнения команды
// "Поиск" выдаем контроллеру команду
// "Чтение состояния прерывания"

// Выводим содержимое регистра состояния
// ST0  и номер дорожки после выполнения команды
// "Поиск" PCN

        fdc_out(0x8);
        printf("Состояние прерывания:\t");
        printf(" ST0: %02.2X, \t", fdc_inp());
        printf("PCN: %02.2X\n", fdc_inp());

// Для более глубокой диагностики состояния
// контроллера выдаем контроллеру команду
// "Чтение состояния накопителя", выводим
// содержимое регистра состояния ST3

        fdc_out(4);
        fdc_out(0);
        printf("Состояние накопителя:\t ST3: %02.2X\n",fdc_inp());

// Устанавливаем скорость передачи данных 500 Кбайтов/с,
// это значение может различаться для разных типов дискет

        outp(0x3F7, 0);

// Инициализация канала прямого
// доступа к памяти

        dma_init(buffer);

// Выдаем команду "Чтение данных"

        fdc_out(0x66);
        fdc_out(0x0);     // накопитель 0, головка 0

        fdc_out(CYL);     // цилиндр CYL
        fdc_out(0);       // головка 0
        fdc_out(1);       // номер сектора - 1

// Передаем контроллеру технические параметры
// дисковода, берем их из таблицы параметров дискеты.
// Это такие параметры:
//    - размер сектора;
//    - номер последнего сектора на дорожке;
//    - размер промежутка;
//    - число считываемых/записываемых байтов

        fdc_out(fdpt->sec_size);
        fdc_out(fdpt->eot);
        fdc_out(fdpt->gap_rw);
        fdc_out(fdpt->dtl);

// Ожидаем прерывание по завершению операции

        int_wait();

// Считываем и выводим на экран байты результата
// операции "Чтение данных"

        printf("\n<<<Чтение сектора>>> \n");
        printf("   Байты состояния (ST0,ST1,ST2,C,H,R,N):\n");

        for(i=0; i<7; i++) printf("%02.2X\t", (char) fdc_inp());
        printf("\n");

// Выводим содержимое считанного сектора в файл

        for(i=0; i<512; i++) fputc(buffer[i],sect);
        fclose(sect);

// Выключаем мотор

        outp(0x3F2, 0xC);
}


// Вывод байта в контроллер дисковода

void fdc_out(unsigned char parm) {

        _asm {
                mov   dx,3F4h     // Порт основного состояния
loop_fdc_out:

                in    al,dx
                test  al,80h      // Проверяем готовность
                jz loop_fdc_out   //   контроллера

                inc   dx          // Выводим байт в порт данных
                mov   al, parm    //   контроллера
                out   dx, al
        }
}

// Ввод байта из порта данных контроллера дисковода

int fdc_inp(void) {

        _asm {
                mov   dx,3F4h     // Порт основного состояния
loop_fdc_inp:
                in    al,dx
                test  al,80h      // Проверяем готовность
                jz loop_fdc_inp   //   контроллера

                inc   dx          // Введенный байт записываем
                in    al, dx      // в регистр AX
        }
}

// Ожидание прерывания от контроллера

void int_wait(void) {

// Разрешаем прерывания

        _enable();
        _asm {
                mov   ax,40h         // После прихода прерывания
                mov   es,ax          // программа обработки прерывания
                mov   bx,3Eh         // устанавливает в 1 старший бит
wait_loop:                     // байта в области данных BIOS
                mov   dl,es:[bx]     // по адресу 0040:003E.
                test  dl,80h         // Мы ждем, когда этот бит будет
                jz    wait_loop      // установлен в 1, а затем
                                                 // сбрасываем его.
                and   dl,01111111b
                mov   es:[bx],dl
        }
}

// Инициализация канала прямого доступа к памяти

void dma_init(char *buf) {

        unsigned long f_adr;
        unsigned sg, of;

// Вычисляем 24-разрядный адрес буфера для данных

        f_adr = ((unsigned long)_psp << 4)
                                + (((unsigned long)buf) & 0xffff);

// Расщепляем адрес на номер страницы
// и смещение

        sg = (f_adr >> 16) & 0xff;
        of = f_adr & 0xffff;

// На время программирования контроллера прямого
// доступа запрещаем прерывания

        _disable();

        _asm {
                mov   al,46h   // Команда чтения данных от
                                        // контроллера НГМД.

                out   12,al    // Сброс триггера-указателя байта
                                        // для работы с 16-разрядными портами.
                                        // Следующий байт, выводимый в 16-разрядный
                                        // порт будет интерпретироваться
                                        // как младший.

                out   11,al    // Установка режима контроллера ПДП

                mov   ax,of    // Смещение буфера, младший байт
                out   4,al
                mov   al,ah    // Смещение буфера, старший байт
                out   4,al

                mov   ax,sg    // Номер страницы
                out   81h,al

                mov   ax,511   // Длина передаваемых данных
                out   5,al
                mov   al,ah
                out   5,al

                mov   al,2     // Разблокировка канала 2 контроллера ПДП
                out   10,al
        }

// Инициализация контроллера закончена,
// разрешаем прерывания.

        _enable();
}


Остальные команды вы можете попробовать сами. Для получения дополнительной информации по контроллеру НГМД обратитесь к техническому руководству по IBM PC. Многое можно почерпнуть из описания микросхем дискового контроллера 765 фирмы NEC и аналогов этой микросхемы - Intel 8272A и отечественной КР1810ВГ72А.

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