Наши партнеры

UnixForum





Библиотека сайта 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");

Что дальше

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


К предыдущей статье Оглавление К следующей статье