Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 8: Специальные средства отображения ввода/вывода для аппаратных средств платформы x86
Оригинал: "Device Drivers, Part 8: Accessing x86-Specific I/O-Mapped Hardware"Автор: Anil Kumar Pugalia
Дата публикации: July 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
В этой статье, которая является частью серии статей о драйверах устройств в Linux, продолжается обсуждение доступа к аппаратным средствам в Linux.
Предполагалось, что второй день занятий в лаборатории драйверов устройств Linux должен существенно отличаться от обычных занятий, связанных с программным обеспечением. Кроме изучения доступа и программирования конкретных аппаратных средств, позволяющих отображать ввод/вывод на платформе x86, новички должны также потратить усилия на чтение руководств по аппаратному обеспечению (обычно называемых спецификациями) и разобраться с тем, как их использовать при написании драйверов устройств. На предыдущем занятии, на котором изучались общие архитектурно-прозрачные принципы взаимодействия с аппаратным обеспечением, речь, наоборот, шла об отображении и доступе к отображаемым в память устройствам в Linux без учета каких-либо конкретных особенностей устройств.
Специальные возможности доступа к аппаратным средствам на платформе x86
В отличие от большинства других архитектур, на платформе x86 есть
дополнительный механизм доступа к аппаратным средствам - прямое
отображение ввода/вывода. Это схема с прямой 16-битной адресацией, в
которой для доступа не требуется использовать отображение в виртуальное
пространство. Эти адреса называются адресами портов или портами.
Поскольку это дополнительный механизм доступа, для него есть
дополнительный набор инструкций x86 (на ассемблере/в машинном коде). И
да, есть инструкции ввода inb
, inw
и inl
для чтения через порты из
устройств, отображающих ввод/вывод, соответственно 8-битовых байтов, 16-разрядных слов и 32-разрядных слов. Для вывода соответственно используются аналогичные инструкции outb
, outw
и outl
. Их эквивалентами на языке C будут следующие функции/макросы (есть в заголовке <asm/io.h>
):
u8 inb(unsigned long port); u16 inw(unsigned long port); u32 inl(unsigned long port); void outb(u8 value, unsigned long port); void outw(u16 value, unsigned long port); void outl(u32 value, unsigned long port);
Главный вопрос, который может возникнуть, касается того, для каких устройств используется такое отображение ввода/вывода и какие будут адреса портов этих устройств. Ответ довольно прост. В соответствии со стандартом x86, все эти устройства и их отображения должны быть определены заранее. На рис.1 показана часть списка таких отображений, взятых из директория /proc/ioports
. Если назвать лишь несколько устройств, то в этом списке есть устройство DMA, таймер и часы реального времени, а кроме того интерфейсы доступа через последовательный и параллельный порты и шину PCI.
Рис.1: Конкретные порты ввода/вывода на платформе x86
Простейшее: последовательный порт на платформе x86
Например, первый последовательный порт ввода/вывода всегда отображается в адреса с 0x3F8 по 0x3FF. Но что это отображение означает? Что нам с ним делать? Как это поможет нам использовать последовательный порт? Вот когда нам нужно обратиться к спецификациям на соответствующее устройство.
Управление последовательным портом осуществляется контроллером последовательного устройства, которое называется UART (Universal Asynchronous Receiver/Transmitter - Универсальный асинхронный приемник/передатчик) или, иногда, USART (Universal Synchronous/Asynchronous Receiver/Transmitter - Универсальный синхронный/асинхронный приемник/передатчик). На ПК обычно используемым устройством UART является устройство PC16550D. Спецификации для этого устройства [PDF] можно загрузить с сайта lddk.esrijan.com в составе самораспаковывающегося пакета [файл BIN] с набором документации по драйверам устройств для Linux.
Вообще говоря, где и как можно найти спецификации для таких устройств? Как правило, поиск в интернете с указанием соответствующего номера устройства должен привести вас к результату. Далее, а как можно узнать номер устройства? Просто ... взглянув на устройство. Если оно находится внутри компьютера, то откройте компьютер и посмотрите на устройство. Да, для того, чтобы писать драйвера устройств, вам, как минимум, придется это делать. Если все уже сделано, то заглянем в спецификации устройства PC16550D UART.
Авторы драйверов устройств должны разбираться в особенностях использования регистров устройства; это именно те регистры, которые им нужно программировать для использования устройства. На странице 14 спецификаций (она также показана на рис.2) приведена таблица для всех двенадцати 8-битных регистров, которые есть в устройстве UART PC16550D.
Рис.2: Регистры устройства UART PC16550D
Каждая из восьми строк таблицы соответствует определенному биту регистра. Кроме того, обратите внимание, что регистровые адреса начинаются с 0 и продолжается до 7. Интересно то, что в спецификациях всегда для регистров указываются смещения, которые нужно затем добавить к базовому адресу устройства для того, чтобы получить фактические адреса регистров.
Кто определяет базовый адрес и откуда его можно получить? Базовый адрес, если он, конечно, не является динамически настраиваемым как в случае с устройствами PCI, как правило, вполне конкретен для карты/платформы. В данном случае, например, для устройства последовательного порта на платформе x86 он задается архитектурой x86 — это именно то, откуда начинается адресация последовательного порта - 0x3F8.
Таким образом, смещения восьми регистров от 0 и до 7 точно отображаются в восемь адресов портов с 0x3F8 по 0x3FF. Таким образом, согласно описанию регистров, это фактические адреса для чтения или записи, через которые можно, для выполнения нужных операций с последовательным портом, читать и писать в соответствующие регистры последовательного порта.
Все смещения регистров последовательного порта и все битовые маски
регистров определяются в заголовке <linux/serial_reg.h>
. Так что вместо того, чтобы жестко кодировать значения, взятые из спецификаций, можно вместо этого воспользоваться соответствующими макросами. Эти макросы используются в коде, который приводится ниже; также используется следующее определение:
#define SERIAL_PORT_BASE 0x3F8
Операции с регистрами устройств:
В качестве итога изучения спецификаций PC16550D UART ниже приводится несколько примеров операций чтения и записи с регистрами последовательного порта.
Чтение и запись в "Line Control Register(LCR)":
u8 val; val = inb(SERIAL_PORT_BASE + UART_LCR /* 3 */); outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */);
Установка и сброс бита "Divisor Latch Access Bit (DLAB)" в LCR:
u8 val; val = inb(SERIAL_PORT_BASE + UART_LCR /* 3 */); /* Setting DLAB */ val |= UART_LCR_DLAB /* 0x80 */; outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */); /* Clearing DLAB */ val &= ~UART_LCR_DLAB /* 0x80 */; outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */);
Чтение и запись в "Divisor Latch"
u8 dlab; u16 val; dlab = inb(SERIAL_PORT_BASE + UART_LCR); dlab |= UART_LCR_DLAB; // Setting DLAB to access Divisor Latch outb(dlab, SERIAL_PORT_BASE + UART_LCR); val = inw(SERIAL_PORT_BASE + UART_DLL /* 0 */); outw(val, SERIAL_PORT_BASE + UART_DLL /* 0 */);
Мигающий светодиод
Чтобы получить реальный опыт работы с низкоуровневым доступом к устройствам и драйверами устройств в Linux, лучше всего воспользоваться комплектом разработки драйверов устройств для Linux - Linux device driver kit (LDDK), который уже ранее упоминался. Но для того, чтобы просто попробовать низкоуровневый доступ, можно воспользоваться мигающим светодиодом, а именно, сделайте следующее:
Подключите светодиод (LED) через резистор 330 Ом между контактом 3 (Tx) и контактом 5 (Gnd) к разъему DB9 вашего компьютера.
Подавайте и отключайте сигнал в цепи передачи (Tx) с интервалом в 500 мс; для этого с помощью команды insmod blink_led.ko
загрузите драйвер blink_led
, а затем с помощью команды rmmod blink_led
выгрузите драйвер.
Файл драйвера blink_led.ko
можно создать из файла исходного кода blink_led.c
; запустите для этого команду make и воспользуйтесь обычным для драйвера файлом Makefile
. Ниже приводится полный текст файла blink_led.c
:
#include <linux/module.h> #include <linux/version.h> #include <linux/types.h> #include <linux/delay.h> #include <asm/io.h> #include <linux/serial_reg.h> #define SERIAL_PORT_BASE 0x3F8 int __init init_module() { int i; u8 data; data = inb(SERIAL_PORT_BASE + UART_LCR); for (i = 0; i < 5; i++) { /* Pulling the Tx line low */ data |= UART_LCR_SBC; outb(data, SERIAL_PORT_BASE + UART_LCR); msleep(500); /* Defaulting the Tx line high */ data &= ~UART_LCR_SBC; outb(data, SERIAL_PORT_BASE + UART_LCR); msleep(500); } return 0; } void __exit cleanup_module() { } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>"); MODULE_DESCRIPTION("Blinking LED Hack");
Что дальше
Возможно, вы задавали вопрос, почему в этой статье нет ни слова о Светлане? Она проспала все занятия. Читайте следующую статью, если хотите узнать, почему она проспала.
К предыдущей статье | Оглавление | К следующей статье |