Создание драйвера устройства.

14/03/96 10:19 driver.lek
Роберт Журден "Справочник программиста на компьютере фирмы IBM".
Росс М. Гринберг "МЕТОДИКА СОЗДАНИЯ И ОТЛАДКИ ДРАЙВЕРА ПЕРИФЕРИЙНОГО УСТРОЙСТВА ДЛЯ ОПЕРАЦИОННОЙ СИСТЕМЫ MS-DOS"
электронный журнал НПО ГОРСИСТЕМОТЕХНИКА Софтпанорама No.1, 1990 Составитель Н.Н.Безруков

Создание драйвера устройства.

Драйвер устройства это специальная программа, которая управляет обменом с периферийным устройством, таким как принтер или дисковый накопитель. После установки драйвер становится частью ОС. Устанавливаемые драйверы устройств достаточно сложны. Устройству может быть присвоено имя, например, SERIALPR для последовательного принтера, и затем это устройство может быть открыто для доступа из любого языка. При этом пользователь имеет возможность доступа к устройству на уровне операционной системы и может просто ввести команду COPY A:MY_FILE SERIALPR:, чтобы скопировать содержимое файла на принтер. Устанавливаемые драйверы устройств целесообразно писать на языке ассемблера.

Они могут обслуживать два типа устройств: СИМВОЛЬНЫЕ и БЛОЧНЫЕ. Эти имена описывают единицы, которыми устройство обрабатывает данные. Обычно драйверы блочных устройств обслуживают дисковые накопители, а драйверы символьных - все остальное, начиная от последовательных принтеров и кончая роботами.

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

Символьные драйверы могут работать в ПОДГОТОВЛЕННОМ и НЕПОДГОТОВЛЕННОМ режимах; режимы отличаются способностью драйвера "помнить" некоторые особенности обработанных ранее байт.

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

Программа драйвера устройства разбивается на три части:

  1. заголовок драйвера, который именует устройство и содержит информацию об остальных частях драйвера;

  2. стратегия драйвера, которая хранит информацию об области данных, создаваемой MS DOS, которая называетя ОБЛАСТЬЮ ЗАПРОСА;

  3. обработчик прерывания устройства, который и содержит код, управляющий устройством.

Создание заголовка драйвера.

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

При написании драйвера указывается ORG 0, либо вообще ничего. Драйвер должен быть описан как далекая (far) процедура.

ПРИМЕЧАНИЕ: В нижеприведенном примере приведен начальный код для драйвера устройства с именем DEVICE12. Оно заменяет стандартное устройство AUX, используемое MS DOS, принимая вывод функции 4 прерывания 21H. Весь драйвер устройства состоит из кода этого раздела вместе с кодом, приведенном в следующих двух разделах; необходимо поместить их подряд один за другим, чтобы получить полную программу.

Драйвер устройства должен начинаться с заголовка драйвера. Он имеет длину 18 байтов, разделенных на 5 полей:

ПЕРВОЕ ПОЛЕ (DD) всегда содержит значение -1 (FFFFFFFFh), и когда MS DOS загружает драйвер, то оно заменяется на стартовый адрес следующего драйвера. Таким образом, система может искать следующий драйвер по цепочке. У последнего загруженного драйвера в этом поле остается значение -1.

ВТОРОЕ ПОЛЕ (DW) это область атрибутов драйвера. Назначение некоторых битов этого слова:

ТРЕТЬЕ и ЧЕТВЕРТОЕ (DW) поля содержат смещения для процедур реализации стратегии и обработки прерывания.

ПЯТОЕ поле содержит имя устройства. Имя содержит 8 символов и оно должно быть выравнено по левому краю с завершающими пробелами.

Пример заголовка драйвера:

CSEG SEGMENT PUBLIC 'CODE' ;устанавливаем кодовый сегмент

ORG 0 ;эта строка необязательна

ASSUME CS:CSEG,DS:CSEG,ES:CSEG

DEVICE12 PROC FAR ;драйвер это далекая процедура

DD 0FFFFFFFFH ;адрес следующего драйвера

DW 8000H ;байт атрибутов

DW DEV_STATEGY ;адрес процедуры стратегии

DW DEV_INTERRUPT ;адрес процедуры прерывания

DB 'AUX ' ;имя устройство (дополненное пробелами)

Создание стратегии устройства.

Программа стратегий вызывается, когда драйвер устанавливается при загрузке операционной системы, а также при каждом сгенерированном операционной системой запросе на ввод/вывод.

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

Она имеет две функции:

  1. Служит областью данных для внутренних операций системы.

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

Размер заголовка запроса может меняться, в зависимости от типа сделанного запроса к драйверу (напр. инициализация, вывод данных или возврат статуса).

Однако первые 13 байт заголовка всегда одни и те же. Их формат таков:

  1. Длина заголовка запроса (DB).

  2. Код устройства (DB). Определяет номер для блочных устройств.

  3. Код команды (DB). Здесь хранится номер последней посланной драйверу команды (коды стандартные, смотри ниже).

  4. Статус (DW). Статус устанавливается каждый раз при вызове драйвера. Если установлен бит 15, то в младших восьми битах находится код ошибки.

  5. Резервная область (8 байтов). Используется MS DOS.

  6. Дополнительные строки - данные необходимые для работы драйвера (переменной длины).

При вызове процедуры стратегии устройства регистры ES:BX указывают на вновь созданный заголовок запроса и процедуре нужно просто скопировать их, чтобы впоследствии он мог быть обнаружен при обращении к драйверу. Адреса смещения и сегмента заголовка помещаются в две переменные.

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

Пример стратегии:

DEV_STRATEGY: MOV CS:KEEP_ES,ES

MOV CS:KEEP_BX,BX

RET

KEEP_CS DW ?

KEEP_BX DW ?

Создание обработчика прерывания устройства.

Имеется несколько типов функций, которые должен выполнять драйвер устройства. Когда драйвер вызывается функцией DOS то код функции драйвера (не функции DOS!) помещается в однобайтное поле по смещению 2 в заголовке запроса. Затем управление передается процедуре обработки прерывания драйвера, адрес которой определяется при просмотре заголовка драйвера. Эта процедура в первую очередь должна восстановить ES:BX, с тем чтобы они указывали на заголовок запроса, а затем прочитать кодовый номер команды.

По этому коду процедура обработки прерывания вызывает нужную процедуру, которая выполнит требуемую функцию. Процедура обычно ищется с помощью 13-словной таблицы, содержащей смещения для 13 типов функций.

Функции всегда перечисляются в следующем порядке:

0 . INITIALIZE (инициализация)

1 . CHECK_MEDIA (проверка среды - возвращает информацию о текущем состоянии системы)

2 . MAKE_BPB (построить блок параметров BPB - вызывается при создании или изменениии среды)

3 . IOCTL_IN (чтение информации о состоянии драйвера и значении его параметров)

4 . INPUT_DATA (ввод данных)

5 . NONDESTRUCT_IN (чтение без удаления - читается очередной символ, не удаляя его из входного буфера)

6 . INPUT_STATUS (статус ввода - проверка на наличие символов в буфере)

7 . CLEAR_INPUT (очистка ввода - очищается входной буфер)

8 . OUTPUT_DATA (вывод данных)

9 . OUTPUT_VERIFY (проверка вывода - вывести символы с последующим чтением)

A . OUTPUT_STATUS (статус вывода - проверка наличия символов в буфере)

B . CLEAR_OUTPUT (очистка вывода - очищает выхоной буфер)

C . IOCTL_OUT (изменеие текущего состояния драйвера и его параметров)

D . Открыть устройство (только для DOS 3.x). Инициализировать устройство.

E . Закрыть устройство (только для DOS 3.x). Вызывается перед закрытием файлов.

F . Программа обработки перемещаемой среды (только для DOS 3.x). Только для блоковых устройств.

10 . Вывод до сигнала занятости (только для DOS 3.x).

Ненормальный вариант обычной функции вывода, допускающий "неполный" вывод. После завершения процедуры, процедура обработки прерывания завершается инструкцией RET и управление возвращается в вызывающую программу. Драйвер устройства может включать код для обработки только некоторых функций, в зависимости от устройства и требуемой степени контроля ошибок и управления устройством. Номера функций, для которых не написаны процедуры, должны завершаться выходом из драйвера без выполнения чего-либо. В этом случае надо только перед выходом установить биты 15, 8, 1 и 0 в заголовке запроса (область статуса), чтобы информировать вызывающую задачу, что была затребована несуществующая функция.

Формат слова статуса:

15 =1 была ошибка;

14..10 резерв;

9 =1 драйвер занят;

8 =1 операция выполнена;

7..0 код ошибки, если бит 15=1:

0 попытка записи на защищенное от записи устройство

1 неизвестное устройство

2 устройство не готово

3 неизвестная команда

4 ошибка проверки по контрольной сумме

5 неверная длина запроса к устройству

6 ошибка поиска

7 неизвестный носитель

8 сектор не найден

9 нет бумаги в принтере

A ошибка записи

B ошибка чтения

C общая ошибка

E зарезерверовано

F изменение среды недопустимо

Одна функция должна присутствовать во всех драйверах устройств, и это функция номер 0 - инициализация. Она автоматически выполняется при загрузке драйвера (один раз). Одна из важных задач, выполняемая этой процедурой, состоит установке адреса конца драйвера в четырех байтах, начинающихся со смещения Eh в заголовке запроса. В нижеприведенном примере конец программы отмечен меткой E_O_P:. Кроме этой задачи, процедура инициализации должна также выполнить всю необходимую для данного устройства инициализацию. При входе в блоке заголовка со смещения Eh находится адрес символа "=" выполняемой строки CONFIG.SYS, что позволяет получить строку параметров драйвера.

Какие из оставшихся функций будут включены в драйвер устройства зависит от того, что драйвер должен делать. Некоторые, такие как CHECK_MEDIA и MAKE_BPB, относятся только к блочным устройствам. Для символьных устройств наиболее важными являются две функции: INPUT_DATA и OUTPUT_DATA (эти имена несущественны - важна позиция в таблице функций, которая неизменна).

В обоих случаях заголовок запроса имеет следующую структуру:

В нижеприведенном примере используется функция вывода. Процедура, выполняющая вывод получает из заголовка запроса адрес буфера, в котором находятся выводимые данные (смещение Eh). Она также считывает число байтов, которое надо вывести (смещение 12h). Когда процедура завершит вывод данных, то она установит слово статуса в заголовке запроса (смещение 3) и возвратит управление. Если операция успешна, то надо установить бит 8 слова статуса.

В данном примере приведена общая форма процедуры обработки прерывания, не включая реального кода, управляющего устройством:

;---инициализация обработчика прерывания устройства

DEV_INTERRUPT: PUSH ES ;сохраняем регистры

PUSH DS

PUSH AX

PUSH BX

PUSH CX

PUSH DX

PUSH SI

PUSH DI

PUSH BP

MOV AX,CS:KEEP_ES ;ES:BX указывают на заголовок запроса

MOV ES,AX ;

MOV BX,CS:KEEP_BX ;

MOV AL,ES:[BX]+2 ;получаем код команды из заголовка

SHL AL,1 ;умножаем на 2 (т.к. таблица словная)

SUB AH,AH ;обнуляем AH

LEA DI,FUNCTIONS ;DI указывает на смещение до таблицы

ADD DI,AX ;добавляем смещение в таблице

JMP WORD PTR [DI] ;переходим на адрес из таблицы

FUNCTIONS LABEL WORD ;это таблица функций

DW INITIALIZE

DW CHECK_MEDIA

DW MAKE_BPB

DW IOCTL_IN

DW INPUT_DATA

DW NONDESTRUCT_IN

DW INPUT_STATUS

DW CLEAR_INPUT

DW OUTPUT_DATA

DW OUTPUT_VERIFY

DW OUTPUT_STATUS

DW CLEAR_OUTPUT

DW IOCTL_OUT

;---выход из драйвера, если функция не поддерживается

CHECK_MEDIA:

MAKE_BPB:

IOCTL_IN:

INPUT_DATA:

NONDESTRUCT_IN:

INPUT_STATUS:

CLEAR_INPUT:

OUTPUT_VERIFY:

OUTPUT_STATUS:

CLEAR_OUTPUT:

IOCTL_OUT:

OR ES:WORD PTR [BX]+3,8103H ;модифицируем статус

JMP QUIT

;---процедуры для двух поддерживаемых кодов

INITIALIZE: LEA AX,E_O_P ;смещение конца программы в AX

MOV ES:WORD PTR [BX]+14,AX ;помещаем его в заголовок

MOV ES:WORD PTR [BX]+16,CS ;

(здесь идет инициализация устройства)

JMP QUIT

OUTPUT_DATA: MOV CL,ES:[BX]+18 ;получаем число символов

CBW CX ;CX используем как счетчик

MOV AX,ES:[BX]+16 ;получаем адрес буфера данных

MOV DS,AX ;

MOV DX,ES:[BX]+14 ;

(здесь идут операции по выводу)

JMP QUIT

;---выходим, модифицируя байт статуса в заголовке запроса

QUIT: OR ES:WORD PTR [BX]+3,100H ;устанавливаем бит 8

POP BP ;восстанавливаем регистры

POP DI ;

POP SI ;

POP DX ;

POP CX ;

POP BX ;

POP AX ;

POP DS ;

POP ES ;

RET

E_O_P: ;метка конца программы

DEVICE12 ENDP

CSEG ENDS

END DEVICE12

 

Доступ к драйверу устройства.

Драйвер устройства устанавливается путем включения имени готовой программы в файл конфигурации системы. Необходимо поместить в файл CONFIG.SYS строку DEVICE = DEVICE12.COM. Затем перезагрузить систему для установки драйвера. Если машина не будет загружаться, то скорее всего имеется ошибка в коде инициализации драйвера. После того как драйвер установлен, для доступа к нему используются обычные функции MS DOS прерывания 21H. Какие функции можно использовать зависит от того, заменяет ли устройство стандартное устройство DOS или оно добавляется как совершенно новое.

Для замены стандартного устройства, неодходимо назвать драйвер любым стандартным именем. Если устройство не заменяет одно из стандартных устройств MS DOS, то можно открыть устройство с помощью одной из функций для открытия файла. Чтобы быть уверенным, что по ошибке не будет открыт дисковый файл, необходимо поместить номер файла в BX, 0 - в AL, посде чего выполнить функцию 44H прерывания 21H.

Это функция IOCTL и если бит 7 значения, возвращаемого в DL установлен, то драйвер устройства загружен. IOCTL требует, чтобы в байте атрибутов драйвера была соответствующая установка битов и чтобы по крайней мере основные процедуры обработки IOCTL имелись в процедуре обработчика прерывания драйвера. Функция IOCTL имеет 8 подфункций, пронумерованных от 0 до 7, при этом соответствующий кодовый номер помещается в AL при вызове функции:

0 Возвратить информацию об устройстве в DX

1 Установить информацию об устройстве, используя DL (DH=0)

2 Считать CX байтов от драйвера устройства через управлящий канал и поместить их начиная с DS:DX

3 Записать CX байтов в драйвер устройства через управляющий канал, взяв их начиная с DS:DX

4 То же, что и 2, но использовать номер накопителя в BL, где 0 = накопитель по умолчанию, 1 = A и т.д.

5 То же, что и 3, но использовать номер накопителя как в 5

6 Получить статус ввода

7 Получить статус вывода

 

В ответ возвращается различная информация, в зависимости от того, какая функция вызвана. Для подфункций 0 и 1 значение битов регистра DX следующее (при условии, что бит 7 = 1, что означает, что доступ получен к устройству, а не к файлу):

0 1 = устройство консольного ввода

1 1 = устройство консольного вывода

2 1 = нулевое устройство

3 1 = устройство часы

4 резерв

5 1 = нет проверки на Ctrl-Z, 0 = есть проверка на Ctrl-Z

6 1 = не конец файла, 0 = конец файла

7 1 = устройство, 0 = дисковый файл

8-13 резерв

14 1 = если можно использовать подфункции 2 и 3, 0 = нельзя

15 резерв

Подфункции 2-5 позволяют программе и устройству обмениваться произвольными управляющими строками. Это позволяет передавать управляющие сообщения отдельно от основного потока данных, что существенно упрощает дело. При возврате AX будет содержать число переданных байтов. Подфункции 6-7 позволяют программе проверить, готово ли устройство для ввода или вывода. Для устройств в AL возвращается FF, если устройство готово и 0, если нет. При использовании с открытым файлом (бит 7 = 0) в AL возвращается FF до тех пор, пока не будет достигнут конец файла.

Обнаружение и анализ ошибок устройства.

Устройства могут ошибаться по одной из трех причин:

1 Устройство может быть физически повреждено или находиться не в том состоянии.

2 Может быть плохим программное обеспечение, управляющее устройством.

3 Программа может послать устройству недопустимый запрос (например, попытка писать на накопитель, где находится дискета защищенная от записи).

MS DOS обнаруживает и анализирует большинство таких ошибок и обеспечивает возможности для восстановления.

Иногда драйверы устройств содержат такие серьезные ошибки, что программа просто не может продолжаться, пока они не будут исправлены. Когда такие ошибки происходят, то система вызывает обработчик критических ошибок. Он может вступать в действие как для стандартных устройств, так и для установленных драйверов. Пользователь наиболее часто сталкивается с ним, когда пытается произвести дисковую операцию с дисководом, у которого открыта дверца. В этом случае появляется сообщение: "Not ready error reading drive A - Abort, Retry, Ignore?"

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

Вектор прерывания 24H указывает на стандартную процедуру MS DOS, но Вы можете перенаправить вектор на свою процедуру. При вызове этой процедуры старший бит AH содержит 0 если ошибка произошла на блочном устройстве и 1, если на символьном. BP:SI указывают на заголовок драйвера виновного устройства, который может дать дополнительную информацию (имя). Обработчик критичеких ошибок помещает код ошибки длиной в слово в DI. Вот кодовые номера некоторых ошибок:

Код Проблема

0 попытка писать на диск, защищенный от записи

1 неизвестное устройство

2 накопитель не готов

3 неизвестная команда

4 ошибка обмена данными

5 неверная длина запроса

6 ошибка поиска

7 неизвестный тип носителя

8 сектор не найден

9 нет бумаги в принтере

A ошибка при записи

B ошибка при чтении

C общая ошибка

В случае дисковой ошибки AL содержит номер накопителя, на котором произошла ошибка, а биты 2-0 AH индицируют тип ошибки:

0 устанавливается, если ошибка произошла во время операции записи, и сбрасывается - если при чтении.

2..1 содержат информацию о том, в каком месте диска произошла ошибка, давая:

00 - для начальных секторов DOS,

01 - для FAT,

10 - для каталога,

11 - для всего остального диска.

Имеется три способа, которыми программа может восстановиться после критической ошибки:

1 Можно попросить пользователя устранить причину ошибки (например, закрыть дверцу накопителя), после чего система предоставит устройству возможность повторить операцию.

2 Управление может быть возвращено инструкции, следующей за INT 21H, которая сделала попытку обратиться к драйверу.

3 Программа может завершиться и вернуть управление системе.

Ваша процедура обработки ошибок может восстановить ситуацию, выдав инструкцию IRET, после того, как она поместила в AL:

0 чтобы игнорировать ошибку,

1 чтобы повторить операцию,

2 чтобы завершить программу.

Если Вы хотите, чтобы Ваша процедура провела восстановление сама, то она должна восстановить регистры выполняемой программы из стека, а затем удалить со стека все, кроме последних трех слов. После этого инструкция IRET возвратит управление программе, хотя сама система останется в нестабильном состоянии до тех пор, пока она не сделает вызов функции с номером большим, чем 12. Вот конфигурация стека (начиная сверху до низа) когда вызывается обработчик критических ошибок:


Stay-at-home