|
|
|||||||||||||||||||||||||||||
|
Драйверы устройствИсточник: rus-linux Anil Kumar Pugalia; Перевод: Н.Ромоданов
Часть 8: Специальные средства отображения ввода/вывода для аппаратных средств платформы x86Оригинал: "Device Drivers, Part 8: Accessing x86-Specific I/O-Mapped Hardware" В этой статье, которая является частью серии статей о драйверах устройств в 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. Вообще говоря, где и как можно найти спецификации для таких устройств? Как правило, поиск в интернете с указанием соответствующего номера устройства должен привести вас к результату. Далее, а как можно узнать номер устройства? Просто ... взглянув на устройство. Если оно находится внутри компьютера, то откройте компьютер и посмотрите на устройство. Да, для того, чтобы писать драйвера устройств, вам, как минимум, придется это делать. Если все уже сделано, то заглянем в спецификации устройства 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 Что дальшеВозможно, вы задавали вопрос, почему в этой статье нет ни слова о Светлане? Она проспала все занятия. Читайте следующую статью, если хотите узнать, почему она проспала. Ссылки по теме
|
|