[Previous] [Next]

Основные сведения об аппаратном обеспечении

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

Автоматическое распознавание и конфигурирование

За каждым аппаратным устройством, подключенным к компьютеру, закрепляются системные ресурсы, которые могут включать:

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

Для преодоления этих проблем были введены новые принципы построения шинных архитектур, которые бы поддерживали автоматическое распознавание и конфигурирования устройств. Автоматическое распознавание должно происходить в момент загрузки/перезагрузки системы или непосредственно в момент подключения устройства к компьютеру ("горячее" подключение). Возможности операционной системы, соответственно, должны быть таковы, чтобы соответствующее сопроводительное программное обеспечение вступало в работу без общей перезагрузки системы.

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

Регистры устройств

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

Доступ к регистрам устройства достигается в результате выполнения инструкций доступа к портам ввода/вывода (port address) или обращения к определенным адресам в адресном пространстве оперативной памяти (memory-mapped address), что и интерпретируются системой как доступ к аппаратным регистрам.

Простые устройства (такие, как стандартный интерфейс параллельного порта, см. таблицу 5.1 — не путать с регистрами устройств, которые могут подключаться извне к параллельному порту!) имеют небольшое число ассоциированных регистров. В то же время, сложное аппаратное обеспечение (например, графические адаптеры) может иметь значительно больше регистров. Число и назначение регистров определяется разработчиками аппаратного обеспечения и должно быть полно и однозначно описано в документации. Однако зачастую такая однозначность так и остается недостижимой мечтой, а разработчику драйвера приходится определять реальное назначение нужных битов в устройствах используя случайно добытый тестовый программный пример неизвестного автора или метод собственных проб и собственных ошибок. Более того, часто выясняется, что биты, объявленные в документации как "зарезервированные" (reserved), вовсе не являются тем безобидным предметом, о котором не следует и беспокоиться.

Таблица 5.1. Регистры интерфейса стандартного параллельного порта (SPP)

Смещение Доступ Регистр Описание
0

Read/Write

Data (DR) Байт данных, передаваемый через параллельный порт
1 Read only Биты 0-1
Бит 2

Бит 3
Бит 4
Бит 5
Бит 6
Бит 7
Status (SR)

PIRQ

ERROR#
SELECT
OUT_OF_PAPER ACK#
BUSY#
Текущее состояние порта
Зарезервированы
0 — прерывание было запрошено портом (т.е. если сигнал ACK# вызвал прерывание)
0 — произошла ошибка
1 — принтер выбран (включен)
1 — в принтере отсутствует бумага
отображает состояния линии Ack#
0 — принтер занят (1 - разрешение на вывод очередного байта)
2 Read/Write
Бит 0
Бит 1
Бит 2
Бит 3
Бит 4
Биты 5-7
Control (CR)
STROBE#
AUTO_LF
INIT#
SELECT_IN#
ENABLE_INT
Команды, посылаемые в порт
1 — строб передачи данных в/из порта
1 — автоматическая подача строки
0 — инициализировать принтер
1 — выбрать принтер
1 — разрешает прерывания по спаду сигнала на линии ACK#
зарезервированы

Коварность отдельных устройств проявляется еще и в том (например, в случае с адаптерами EGA и VGA), что смысл ассоциированного регистра во второй операции доступа к нему определяется значением данных, отправленных в этот регистр во время первой операции доступа. Пусть у нас имеется X — указатель на регистр в "терминах" адресов памяти, и сначала мы сделали *X=1 и после этого получили n=*X — число конных матросов на лекции А.Блока. Но если мы сделали *X=2, то можем получить n=*X — температуру, при которой следует готовить сапоги всмятку, то есть совершенно другие по смыслу значения.

Доступ к регистрам устройств

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

Как правило, регистры следуют друг за другом в своем адресном пространстве. Следовательно, для начала, необходим адрес первого из них. К сожалению, значение термина 'адрес' сильно варьируется при использовании его относительно виртуальных адресных пространств на различных платформах.

Пример старой DOS программы (назовем ee Abc), выполняющей вывод алфавита на экран в текстовом режиме, построен как раз на том, что видимая область первой текстовой страницы DOS экрана "совмещалась" с оперативной памятью и начиналась с адреса B800:0000 (что в переводе на современный... где-то... 0xB8000).

#include <dos.h>
int main(void)
{
	int i;
	unsigned int far *screen;  // <- адрес начала видимой 1-й страницы
   // Собираем адрес из сегмента (0xB800) и смещения:
	screen = (unsigned int *) MK_FP(0xB800, 0);
	for (i = 0; i < 26; i++)
		screen[i] = 0x0F00 + ('a' + i);  // <- Вывод одного символа
	return 0;
} 

В результате работы этого кода (его следует собрать DOS компилятором) в верхней строке экрана появится строка букв о 'а' до 'z'. (Здесь 0x0F — байт означающий, что символы будут белыми на черном фоне.)

Если говорить в терминах ассемблера, то инструкции для доступа к пространству памяти — это MOV (load/store для других типов ассемблера), а для доступа к пространству ввода/вывода используются инструкции типа IN или OUT.

Пространство ввода/вывода

В некоторых реализациях процессорных архитектур доступ к регистрам устройств осуществляется при помощи специальных команд процессора — инструкций ввода/вывода. Они ссылаются на специальные наборы выводов процессора и определяют отдельное шинно-адресное пространство для устройств ввода/вывода. Адреса на этих шинах широко известны как порты (ports) и не имеют никакого отношения к адресации памяти. В архитектуре Intel x86 адресное пространство ввода/вывода имеет размер 64 КБ (16 разрядов), а в языке ассемблера определено две инструкции для чтения и записи в этом пространстве: 'IN' и 'OUT' (точнее, две группы инструкций, внутри которых различие имеет место по разрядности считываемых/записываемых данных).

Поскольку при создании драйвера следует избегать привязки к аппаратной платформе, Microsoft рекомендует избегать и использования реальных инструкций IN/OUT. Вместо этого следует использовать макроопределения HAL. Соответствие между традиционными инструкциям DOS/Windows ассемблера и макроопределениями HAL приводится в таблице 5.2.

Таблица 5.2. Макроопределения HAL для доступа к портам ввода/вывода

Ассемблер х86 Аналог HAL Описание
IN AL,DX
IN AL,port
READ_PORT_UCHAR Чтение 1 байта из порта ввода/вывода
IN AX,DX
IN AX,port
READ_PORT_USHORT Чтение 16-ти разрядного слова из порта ввода/вывода
IN EAX,DX
IN EAX,port
READ_PORT_ULONG Чтение 32-х разрядного слова из порта ввода/вывода
INSB READ_PORT_BUFFER_UCHAR Чтение массива байт из порта ввода/вывода
INSW READ_PORT_BUFFER_USHORT Чтение массива 16-ти разрядных слов из порта ввода/вывода
INSD READ_PORT_BUFFER_ULONG Чтение массива 32-х разрядных слов из порта ввода/вывода
OUT DX,AL
OUT port,AL
WRITE_PORT_UCHAR Запись 1 байта в порт ввода/вывода
OUT DX,AX
OUT port,AX
WRITE_PORT_USHORT Запись 16-ти разрядного слова в порт ввода/вывода
OUT DX,EAX
OUT port,EAX
WRITE_PORT_ULONG Запись 32-х разрядного слова в порт ввода/вывода
OUTSB WRITE_PORT_BUFFER_UCHAR Запись массива байт в порт ввода/вывода
OUTSW WRITE_PORT_BUFFER_USHORT Запись массива 16-ти разрядных слов в порт ввода/вывода
OUTSD WRITE_PORT_BUFFER_ULONG Запись массива 32-х разрядных слов в порт ввода/вывода

Доступ через адресацию в памяти

Далеко не все создатели процессоров находили целесообразным организацию "портового" доступа к регистрам устройств через адресное пространство ввода/вывода. В альтернативном подходе доступ к регистрам устройств осуществлялся путем обращения к определенным адресам в пространстве памяти (memory address space). В пример можно привести архитектуру PDP-11 Unibus, где вовсе не было портов ввода/вывода и инструкций процессора для работы с ними: все регистры всех устройств имели свое место в общем пространстве адресации памяти и реагировали на обычную инструкцию доступа к памяти. В некоторых случаях (например, для видеоадаптеров в архитектуре Intel x86) допускаются оба способа доступа сразу: и через пространство ввода/вывода, и через адресное пространство

Таблица 5.3. Макроопределения HAL для доступа к регистрам устройств через адресацию в памяти.

Поля Описание
READ_REGISTER_XXX Чтение одного значения из регистра ввода/вывода
WRITE_REGISTER_XXX Запись одного значения в регистр ввода/вывода
READ_REGISTER_BUFFER_XXX Чтение массива значений из последовательного набора регистров ввода/вывода
WRITE_REGISTER_BUFFER_XXX

Запись массива значений в набор из следующих друг за другом регистров ввода/вывода

Как и в предыдущем случае, определены макросы HAL для доступа к таким memory mapped (то есть с доступом посредством адресации в памяти) регистрам, что описывается в таблице 5.3 (XXX принимает значения UCHAR, USHORT или ULONG). Так как эти макроопределения по содержанию отличаются от HAL макроопределений для операций с портами ввода/вывода, драйверный код должен быть разработан так, чтобы компиляция для разных платформ (с разными методами доступа к регистрам) проходила корректно. Хорошим приемом является составление таких макроопределений условной компиляции, которые указывали бы на один из нужных HAL макросов в зависимости от ключей компиляции.