Программирование на аппаратном уровне
Доброго времени суток всем, кто читает первую статью из цикла “Программирование на аппаратном уровне”. Сперва, немного предистории…
Интерес к написанию драйверов возник у меня довольно давно, но то ли из-за нехватки времени, то ли из-за отсутствия необходимости я почти никогда этим не занимался. К тому же в настоящее время очень сложно найти хорошую техническую документациюю, т.к. компании обычно сами выпускают ПО для своей продукции. Большинство необходимых услуг по работе с железом предоставляла Операционная система (ОС).Но потом ситуация изменилась для меня, поскольку мой друг и коллега (Фёдоров О. С. aka Legos) предложил мне подключиться к работе над его новой операционной системой FOS (её он так изначально назвал по своим инициалам =)), которую он начал писать не задолго до этого. FOS - это 32-битная многозадачная Операционная система (ОС), распространяющаяся под лицензией GNU Public License v2). На тот момент, 03.2005, она включала в себя драйвер для работы с файловой системой FAT12 (только для чтения), драйвер флоппи-дисковода и пока ещё плохо работающую реализацию многозадачности. FOS - это не клон Linux-а. FOS писалась абсолютно с нуля, и, на настоящий момент поддерживается двумя людьми: мной и Legos-ом. В этой серии статей я постараюсь показать на примере методы программирования драйверов устройств и других компонентов ядра для операционных систем.
Перед написанием любой программы, в том числе и драйвера, необходимо выбрать подходящий язык программирования. Во всех последующих исходных текстах программ я буду пользоваться языками C и C++, а также языком Assembler. C/C++ дают необходимую гибкость и удобство при написании программы, а с помощью Assembler-а можно добиться максимальной скорости работы и минимального размера кода. На самом деле я допускаю использование и других языков программирования для реализации нашей цели, таких как Pascal, но в этом случае мы лишаем себя этих двух преимуществ. Что касается компиляторов, основным средством разработки будет gcc (есть версии под Windows, Linux и Unix), и ещё иногда Borland C++ v3.1 под DOS. Все примеры, распространающиеся вместе с этой серией статей разработаны под лицензией GNU Public License v2 если иное не оговорено заранее. Ознакомиться с этой лицензией можно на сайте wwwgnu.org.
В первой статье речь пойдёт о создании драйвера адаптера асинхронной последовательной связи, работающего по стандартам RS-232. Говоря человеческим языком, мы будем программировать адаптер, управляющий так называемыми COM-портами. Наиболее часто интерфейс RS-232 используют для соединения двух компьютеров по телефонной линии с использованием модема (модем = МОдулятор/ДЕМодулятор). Модулятор преобразует поступающий цифровой сигнал в аналоговый, и посылает его по телефонной линии удалённому демодулятору, который преобразует его обратно в цифровой. информация (info) передаётся последовательным кодом (то есть бит за битом, которые на приёмнике собираются обратно в байты) с заданной частотой (количество бит в секунду) и с фиксированным размером кадра (1 старт-бит, от 5 до 8 информационных битов, не обязательный бит контроля чётности и 1 или 2 стоп-битов) с использованием так называемого старт-стопного метода. Старт-стопный метод заключается в том, что любая передаваемая информация (info) начинается со старт-бита (всегда равен 1), и заканчивается одним или двумя стоп-битами (всегда равны 0). Такой способ позволяет проверять правильность передачи: если приёмник ожидает получения стоп-бита, равного нулю, а вместо него получает единицу (как правило +12В), то регистрируется ошибка кадрирования (framing error). Ещё один вариант контроля ошибок заключается в использовании бита контроля чётности: непосредственно за передаваемыми данными находится бит, который определяет, чётное или не чётное число получается при сложении информационных битов. Такой способ, однако, может выявить только нечётное число ошибок, и по этому редко используется на практике. Кроме того, современные адаптеры асинхронной последовательной связи обладают возможностью аппаратной буфферизации (Размер (Size) буфера - до 16 байт) и возможностью использования DMA (Direct Memory Access - Прямой доступ к памяти) для уменьшения нагрузки на CPU.
Стандарт RS-232C описывает 25 линий, хотя на практике используется значительно меньшее количество (по крайней мере при работе с модемом): 2 линии для передачи битов данных (TXD и RXD: от адаптера и к адаптеру), 5 линий для определения готовности (DSR, CTS, DCD, DTR и RTS: готовность подключённого устройства и готовность самого интерфейиса RS-232 передавать данные), индикатор вызова (RI) и логический нуль (GND). На самом деле для организации передачи данных через RS-232 было бы достаточно всего трёх линий: TXD, RXD и GND, но тогда бы нам пришлось передавать данные “в слепую”, не дожидаясь поддтверждения, да и не зная вообще, слышит ли нас приёмник.
Используемые сигналы интерфейса RS-232:
TXD Передача данных к устройству
RTS Request To Send - Информирует устройство (модем) о том, что компьютер хочет передать данные
DTR Data Terminal Ready - Информирует устройство о том, что компьютер готов к обмену информацией
RXD Приём данных от устройства
CTS Clear To Send - Устройство (модем) готово к приёму данных
DSR Data Set Ready - Устройство (модем) включено
DCD Data Carrier Detected - Устройство хочет передать информацию в компьютер
RI Ring Indicator - Индикатор вызова (”звонок”)
GND Ground - Логический нуль
Теперь перейдём к практике. В нашу задачу входит написание драйвера для микросхемы 8250, который в последствии будет использован как компонент ядра FOS, DOS, либо для любой другой ОС. На самом деле, взаимодействие с интерфейсом RS-232 нам могут предоставить функции BIOSа через прерывание 0×14, но во-первых они имеют некоторые ограничения, а во-вторых нам нужно заставить наш драйвер работать в защищённом режиме процессора (ну… ето для FOS). Будем всё писать в ручную, используя механизм портов ввода/вывода. Базовым адресом (БА) мы будем называть номер первого порта для доступа к устройству. Для первого интерфейса RS-232 (то есть COM1 в DOS или ttyS0 в Linux) базовый адрес обычно равен 0×03F8.
Список внутренних регистров микросхемы 8250 и 16550 UART:
БА + 0 Если включен режим передачи/приёма данных (бит 7 регистра управления линией равен 0): регистр ДАННЫХ передатчика/приёмника
Если режим передачи/приёма данных выключен: младший байт ДЕЛИТЕЛЯ ЧАСТОТЫ, только для записи
БА + 1 Если включен режим передачи/приёма данных: регистр РАЗРЕШЕНИЯ ПРЕРЫВАНИЙ, только запись
Если режим передачи/приёма данных выключен: старший байт ДЕЛИТЕЛЯ ЧАСТОТЫ, только для записи
БА + 2 Для чтения: регистр ИДЕНТИФИКАЦИИ ПРЕРЫВАНИЯ
Для записи: установка параметров FIFO и DMA, только для микросхемы 16550 UART
БА + 3 Регистр УПРАВЛЕНИЯ ЛИНИЕЙ
БА + 4 Регистр УПРАВЛЕНИЯ МОДЕМОМ
БА + 5 Регистр СОСТОЯНИЯ ЛИНИИ, только чтение
БА + 6 Регистр СОСТОЯНИЯ МОДЕМА, только чтение
Внутренний регистр разрешения прерываний (БА+1):
(только запись)
Этот регистр позволяет включить режим генерации прерывания при изменении состояния адаптера. Использование такого аппаратного прерывания (IRQ3 - для COM2, COM4 … ; IRQ4 - для COM1, COM3 …) бывает полезно, когда у нас нет возможности постоянно опрашивать RS-232, например, с целью определения готовность передачи/приёма следующего байта. Этот регистр доступен тогда и только тогда, когда включен режим передачи/приёма данных, то есть если бит 7 регистра управления линией (БА+3) установлен в 0
бит 0 Генерация прерывания в случае готовности принятия данных
бит 1 Генерация прерывания в случае, когда пуст регистр передачи данных
бит 2 Генерация прерывания в случае изменения регистра состояния линии (БА+5)
бит 3 Генерация прерывания в случае изменения регистра состояния модема (БА+6)
биты 4-7 Не используются в 8250 и в 16550 UART
Внутренний регистр идентификации прерываний (БА+2):
(только чтение)
С помощью этого регистра обработчик прерывания может определить причину аппаратного прерывания. Устранение причины прерывания происходит при чтении или записи из/в соответствующий регистр. Например, если прерывание произошло по причине готовности передатчика (биты 0, 1 и 2 равны 0, 0 и 1 соответственно), то при записи в этот регистр сбрасывается прерывание по этой причине.
бит 0 0 - Прерывание не обработано; 1 - Нет активных прерываний (нечего больше обрабатывать)
биты 1-2 Причина произошедшего прерывания:
00 - Изменение регистра состояния модема (БА+6)
01 - Пуст регистр данных передатчика (можно отправить ещё байт)
10 - Готовность данных в приёмном регистре (можно получить следующий байт)
11 - Изменение регистра состояния линии либо установка бита ошибки
бит 3 Только для 16550 UART: 1 - тайм-аут очереди (т.е. FIFO) приёмника
биты 4-5 Не используются в 8250 и в 16550 UART
биты 6-7 Только для 16550 UART: 00 - режим FIFO и DMA отключен; 11 - FIFO; 01 - FIFO+DMA
Следует также обратить внимание на то, что для вызова прерывания может быть несколько причин одновременно, и обработчик не должен завершать свою работу до тех пор, пока не обработает все причины, то есть пока бит 0 не будет аппаратно установлен в еденицу. При налачии нескольких причин прерывания, они будут обрабатываться согласно своему приоритету: 1) изменение в регистре состояния модема 2) готовность принятия данных 3) готовность отправки данных 4) изменение регистра состояния линии.
Внутренний регистр контроля FIFO и DMA микросхемы 16550 UART (БА+2):
(только запись)
Этот регистр не доступен для записи при использовании более старой микросхемы 8250! При помощи этого регистра драйвер устройства может задавать режим работы FIFO и DMA
бит 0 1 - Использовать FIFO; 0 - Не использовать FIFO
бит 1 1 - Очистить FIFO приёмника
бит 2 1 - Очистить FIFO передатчика
бит 3 Режим DMA
биты 4-5 Не используются в 16550 UART
биты 6-7 Размер (Size) FIFO: 00 - 1 байт; 01 - 4 байта; 10 - 8 байт; 11 - 16 байт
Внутренний регистр управления линией (БА+3):
Используется для задания параметров передачи данных, установки сигнала BREAK (приостановка передачи) и для выбора содержимого в регистрах БА+0 и БА+1
биты 0-1 Длина слова данных: 00 - 5 бит; 01 - 6 бит; 10 - 7 бит; 11 - 8 бит
бит 2 Число стоп-битов: 0 - 1 стоп-бит; 1 - 2 стоп-бита
биты 3-5 Способ контроля по чётности:
xx0 - отсутствие бита контроля чётности
001 - бит формируется по чётному паритету
011 - бит формируется по нечётному паритету
101 - бит контроля равен 1
111 - бит контроля равен 0
бит 6 0 - обычное функционирование; 1 - сигнал BREAK
бит 7 Переключение содержимого в портах БА+0 и БА+1 (0 - доступ к регистру данных и регистру разрешения прерываний; 1 - доступ к делителю частоты)
Внутренний регистр управления модемом (БА+4):
бит 0 Установка сигнала на линии DTR - оповещение о готовности терминала данных (в компьютере включено питание и он готов к обмену информацией)
бит 1 Установка сигнала на линии RTS - запрос на передачу (компьютер хочет передать данные в линию)
бит 2 Определяемый пользователем запрос прерывания (OUT1)
бит 3 Разрешает IRQ от адаптера (OUT2), необходимо установить для генерации адаптером IRQ
бит 4 Включение loopback для диагностики адаптера
биты 5-7 Не используются в 8250 и в 16550 UART
Внутренний регистр состояния линии (БА+5):
(только чтение)
Благодаря этому регистру драйвер может отслеживать изменения состояния интерфейса
бит 0 Готовность принятия данных (можно прочитать данные из БА+0)
бит 1 Ошибка переопределения данных (были получены новые данные, в следствии чего старые были утеряны)
бит 2 Ошибка паритета (вероятно, данные были искажены при передаче)
бит 3 Ошибка кадрирования (рассинхронизация)
бит 4 Принят BREAK
бит 5 Буферный регистр передатчика пуст
бит 6 Сдвиговый регистр передатчика пуст
бит 7 Не используются в 8250 и в 16550 UART, всегда 0