![]() |
Программирование на аппаратном уровне
Доброго времени суток всем, кто читает первую статью из цикла “Программирование на аппаратном уровне”. Сперва, немного предистории…
Интерес к написанию драйверов возник у меня довольно давно, но то ли из-за нехватки времени, то ли из-за отсутствия необходимости я почти никогда этим не занимался. К тому же в настоящее время очень сложно найти хорошую техническую документациюю, т.к. компании обычно сами выпускают ПО для своей продукции. Большинство необходимых услуг по работе с железом предоставляла операционная система.Но потом ситуация изменилась для меня, поскольку мой друг и коллега (Фёдоров О. С. 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 если иное не оговорено заранее. Ознакомиться с этой лицензией можно на сайте www.gnu.org. В первой статье речь пойдёт о создании драйвера адаптера асинхронной последовательной связи, работающего по стандартам RS-232. Говоря человеческим языком, мы будем программировать адаптер, управляющий так называемыми COM-портами. Наиболее часто интерфейс RS-232 используют для соединения двух компьютеров по телефонной линии с использованием модема (модем = МОдулятор/ДЕМодулятор). Модулятор преобразует поступающий цифровой сигнал в аналоговый, и посылает его по телефонной линии удалённому демодулятору, который преобразует его обратно в цифровой. Информация передаётся последовательным кодом (то есть бит за битом, которые на приёмнике собираются обратно в байты) с заданной частотой (количество бит в секунду) и с фиксированным размером кадра (1 старт-бит, от 5 до 8 информационных битов, не обязательный бит контроля чётности и 1 или 2 стоп-битов) с использованием так называемого старт-стопного метода. Старт-стопный метод заключается в том, что любая передаваемая информация начинается со старт-бита (всегда равен 1), и заканчивается одним или двумя стоп-битами (всегда равны 0). Такой способ позволяет проверять правильность передачи: если приёмник ожидает получения стоп-бита, равного нулю, а вместо него получает единицу (как правило +12В), то регистрируется ошибка кадрирования (framing error). Ещё один вариант контроля ошибок заключается в использовании бита контроля чётности: непосредственно за передаваемыми данными находится бит, который определяет, чётное или не чётное число получается при сложении информационных битов. Такой способ, однако, может выявить только нечётное число ошибок, и по этому редко используется на практике. Кроме того, современные адаптеры асинхронной последовательной связи обладают возможностью аппаратной буфферизации (размер буфера - до 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 Размер 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 |
Внутренний регистр состояния модема (БА+6):
(только чтение) Благодаря этому регистру драйвер может отслеживать изменения состояния модема (или другого устройства). С помощью него можно определить сигналы на соответствующих линиях, а также наличие изменения состояния линий с момента последнего обращения к регистру. бит 0 Изменение на линии CTS бит 1 Изменение на линии DSR бит 2 Изменение на линии RI бит 3 Изменение на линии DCD бит 4 Активный сигнал на линии CTS бит 5 Активный сигнал на линии DSR бит 6 Активный сигнал на линии RI бит 7 Активный сигнал на линии DCD Перед первым использованием адаптера асинхронной последовательной связи, совместимого с 8250, нам необходимо произвести его инициализацию, указав ему следующие параметры: частоту передачи, количество информационных битов (ради которых мы всё это и затеяли), количество стоп-битов и наличие бита чётности в кадре, а для более продвинутых контроллеров (16550 UART) можно также определить размер буфера FIFO и разрешить/запретить DMA. Эту опреацию проводит BIOS при запуске компьютера, но когда мы захотим изменить параметры по умолчанию, нам придётся переинициализировать адаптер ещё раз. Детально это выглядит так: 0) Контроль ошибок 1) Устанавливаем регистр управления линией (БА+3) следующим образом (см. выше): биты 0,1 - длина слова бит 2 - число стоп-битов биты 3,4,5 - способ контроля по чётности бит 6 - 0=обычное функционирование, 1=сигнал BREAK бит 7 - 0=режим передачи/приёма данных, 1=режим выбора делителя частоты Во время инициализации бит 7 устанавливаем в еденицу, в бите 6 всегда выбираем первое. 2) Пишем в БА+0 и в БА+1 младший и старший байты делителя частоты. Делитель частоты - это число, получаемое делением магического числа 1 843 200 на желаемую скорость, помноженную на 16. Магическое число - это частота внутреннего источника синхронизации в 1843200 Гц. Делитель частоты, равный еденице соответствует максимальной частоте в 115 200 бит/с, т.к. 1843200/(115200*16) = 1. 3) Устанавливаем бит 7 регистра БА+3 в нуль. Если пропустить этот пункт, то программа не сможет передавать/принимать данные через интерфейс и разрешать/запрещать прерывания, т.к. будет считать эти данные делителем частоты. 4) При необходимости можно также включить режим буферизации (FIFO) и прямого доступа к памяти (DMA) для микросхемы 16550 UART путём изменения регистра БА+2 Код:
/*Код:
char isRS232_init(unsigned char num)Для того, что бы проверить готовность интерфейса передать данные драйверу на обработку и готовность устройства получить данные от драйвера через интерфейс RS-232 необходимо прочитать биты готовности из соответствующих портов ввода/вывода (адаптер должен быть инициализирован функцией RS232_init() перед этим). Если нужная функция возвращает значение RS232_NOERROR - можно передавать следующий байт Код:
char RS232_receive_ready(unsigned char num)Код:
char RS232_receivebyte(unsigned char num)Для инициализации не очень умного устройства можно воспользоваться схемой: 1) Выставляем сигнал на линиях DTR и RTS 2) Если надо, выставляем сигнал на линии OUT2 для разрешения генерации IRQ Схема инициализации умного, такого как модем, устройства выглядит несколько сложнее: 1) Выставляем сигнал на линии DTR 2) Ждём немного 3) Проверяем сигнал на линии DSR: если он отсутствует - модем не отвечает (или его просто нет), тогда выходим с ошибкой 1 4) Выставляем сигнал на линии DTR и RTS одновременно 5) Ждём немного 6) Если модем не ответил сигналом на линии CTS - питание включено, но он не готов к обмену информацией, тогда выходим с ошибкой 2 7) Если надо, выставляем сигнал на линии OUT2 Исходя из этих двух алгоритмов, напишем ещё четыре функции инициализации и деинициализации различных устройств, подключаемых к RS232 (хотя к самому микроконтроллеру 8250 они не относятся, они необходимы для выставления и проверки соответствующих сигналов на линиях): Код:
char RS232_modem_on(unsigned char num)В некоторых случаях (особенно, если мы используем обработчик аппаратного прерывания) нам будет необходимо контролировать ошибки передачи данных по интерфейсу. Для этого воспользуемся достаточно примитивными функциями обнаружения ошибки кадрирования RS232_framing_error() и ошибки паритета, то есть контроля по чётности/нечётности RS232_parity_error(): Код:
char RS232_framing_error(unsigned char num)Код:
char RS232_modem_ring(unsigned char num)Говоря человеческим языком, мы повесим свой обработчик на аппаратное прерывание. Про то, как это делается, можно прочитать в соответствующей документации по программированию. А в этой статье я расскажу про то, как заставить контроллер генерировать такое прерывание (IRQ). Итак, функция, устанавливающая события, при которых сгенерируется IRQ: void RS232_IRQ_init(unsigned char num, unsigned char can_receive, unsigned char can_send, unsigned char line_status_change, unsigned char modem_status_change) { // Устанавливаем события для генерации IRQ outportb(RS232_base_ports[num]+1, (can_receive?1:0)|(can_send?2:0)|(line_status_chan ge?4:0)|(modem_status_change?8:0)); /* can_receive=1 - генерировать IRQ в случае готовности получения данных контроллером can_send=1 - генерировать IRQ в случае готовности отправления данных контроллером line_status_change=1 - генерировать IRQ в случае изменения состояния линии modem_status_change=1 - генерировать IRQ в случае изменения состояния модема Если эти параметры равны нулю - прерывание не будет генерироваться по этим событиям. Если установить все параметры (кроме номера интерфейса num) в ноль - IRQ контроллером не сгенерируется. */ } Вот, собственно, и всё, что можно сказать про программирование драйвера RS-232. В заключении можно отметить то, что практически все функции возвращают коды ошибок (либо значение RS232_NOERROR, что свидетельствует об отсутствии ошибки), про которые мы не сказали ни слова. Опишем наши коды завершения в отдельнов файле rs232err.h, который подключим к основному модулю с помощью дерективы #include “rs232err.h”: Код:
/*Заметим, также, что всё взаимодействие с контроллером 8250 происходит через порты ввода/вывода с помощью функций inportb() и outportb(). Эти функции также должны быть описаны в драйвере (описание этих функций на встроенном ассемблере дано с учётом компиляции в GCC и не подходит для Borland C v3 и др.): Код:
void outportb(unsigned short port, unsigned char value)Copyright (c) Pashix, 2005-06, pashix(at)pochta.ru |
| Время: 00:27 |