Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Обслуживание периферийных устройств | Вперед |
Устройства на шине PCI
Архитектура PCI была разработана в качестве замены стандарту ISA с тремя основными целями: получить лучшую производительность при передаче данных между компьютером и его периферией, быть независимой от платформы, насколько это возможно, и упростить добавление и удаление периферийных устройств в системе. В настоящее время PCI широко используется в разных архитектурах: IA-32 / IA-64, Alpha, PowerPC, SPARC64 ... Самой актуальной для автора драйвера является поддержка PCI автоопределения интерфейса плат: PCI устройства настраивается автоматически во время загрузки. Затем драйвер устройства получает доступ к информации о конфигурации устройства, и производит инициализацию. Это происходит без необходимости совершать какое-либо тестирование.
Каждое периферийное устройство PCI идентифицируется по подключению такими физическими параметрами, как: номер шины, номер устройства и номер функции. Linux дополнительно вводит и поддерживает такое логическое понятие как домен PCI. Каждый домен PCI может содержать до 256 шин. Каждая шина содержит до 32 устройств, каждое устройство может быть многофункциональным и поддерживать до 8 функций. В конечном итоге, каждая функция может быть однозначно идентифицирована на аппаратном уровне 16-ти разрядным ключом. Однако, драйверам устройств в Linux, не требуется иметь дело с этими двоичными ключами, потому что они используют для работы с устройствами специальную структуру данных pci_dev.
Примечание: Часто то, что мы житейски и физически (плата PCI) понимаем как устройство, в этой системе терминологически правильно называется: функция, устройство же может содержать до 8-ми эквивалентных (по своим возможностям) функций.
Адресацию PCI устройств в своей Linux системе смотрим:
$ lspci
00:00.0 Host bridge: Intel Corporation Mobile 945GM/PM/GMS, 943/940GML and 945GT Express Memory Controller Hub (rev 03) 00:02.0 VGA compatible controller: Intel Corporation Mobile 945GM/GMS, 943/940GML Express Integrated Graphics Controller (rev 03) 00:02.1 Display controller: Intel Corporation Mobile 945GM/GMS/GME, 943/940GML Express Integrated Graphics Controller (rev 03) 00:1b.0 Audio device: Intel Corporation 82801G (ICH7 Family) High Definition Audio Controller (rev 01) 00:1c.0 PCI bridge: Intel Corporation 82801G (ICH7 Family) PCI Express Port 1 (rev 01) 00:1c.2 PCI bridge: Intel Corporation 82801G (ICH7 Family) PCI Express Port 3 (rev 01) 00:1c.3 PCI bridge: Intel Corporation 82801G (ICH7 Family) PCI Express Port 4 (rev 01) 00:1d.0 USB Controller: Intel Corporation 82801G (ICH7 Family) USB UHCI Controller #1 (rev 01) 00:1d.1 USB Controller: Intel Corporation 82801G (ICH7 Family) USB UHCI Controller #2 (rev 01) 00:1d.2 USB Controller: Intel Corporation 82801G (ICH7 Family) USB UHCI Controller #3 (rev 01) 00:1d.3 USB Controller: Intel Corporation 82801G (ICH7 Family) USB UHCI Controller #4 (rev 01) 00:1d.7 USB Controller: Intel Corporation 82801G (ICH7 Family) USB2 EHCI Controller (rev 01) 00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev e1) 00:1f.0 ISA bridge: Intel Corporation 82801GBM (ICH7-M) LPC Interface Bridge (rev 01) 00:1f.2 IDE interface: Intel Corporation 82801GBM/GHM (ICH7 Family) SATA IDE Controller (rev 01) 02:06.0 CardBus bridge: Texas Instruments PCIxx12 Cardbus Controller 02:06.1 FireWire (IEEE 1394): Texas Instruments PCIxx12 OHCI Compliant IEEE 1394 Host Controller 02:06.2 Mass storage controller: Texas Instruments 5-in-1 Multimedia Card Reader (SD/MMC/MS/MS PRO/xD) 02:06.3 SD Host controller: Texas Instruments PCIxx12 SDA Standard Compliant SD Host Controller 02:06.4 Communication controller: Texas Instruments PCIxx12 GemCore based SmartCard controller 02:0e.0 Ethernet controller: Broadcom Corporation NetXtreme BCM5788 Gigabit Ethernet (rev 03) 08:00.0 Network controller: Intel Corporation PRO/Wireless 3945ABG [Golan] Network Connection (rev 02)
Другое представление той же информации (тот же хост) можем получить так:
$ tree /sys/bus/pci/devices/
/sys/bus/pci/devices/ ├── 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0 ├── 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0 ├── 0000:00:02.1 -> ../../../devices/pci0000:00/0000:00:02.1 ├── 0000:00:1b.0 -> ../../../devices/pci0000:00/0000:00:1b.0 ├── 0000:00:1c.0 -> ../../../devices/pci0000:00/0000:00:1c.0 ├── 0000:00:1c.2 -> ../../../devices/pci0000:00/0000:00:1c.2 ├── 0000:00:1c.3 -> ../../../devices/pci0000:00/0000:00:1c.3 ├── 0000:00:1d.0 -> ../../../devices/pci0000:00/0000:00:1d.0 ├── 0000:00:1d.1 -> ../../../devices/pci0000:00/0000:00:1d.1 ├── 0000:00:1d.2 -> ../../../devices/pci0000:00/0000:00:1d.2 ├── 0000:00:1d.3 -> ../../../devices/pci0000:00/0000:00:1d.3 ├── 0000:00:1d.7 -> ../../../devices/pci0000:00/0000:00:1d.7 ├── 0000:00:1e.0 -> ../../../devices/pci0000:00/0000:00:1e.0 ├── 0000:00:1f.0 -> ../../../devices/pci0000:00/0000:00:1f.0 ├── 0000:00:1f.2 -> ../../../devices/pci0000:00/0000:00:1f.2 ├── 0000:02:06.0 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:06.0 ├── 0000:02:06.1 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:06.1 ├── 0000:02:06.2 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:06.2 ├── 0000:02:06.3 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:06.3 ├── 0000:02:06.4 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:06.4 ├── 0000:02:0e.0 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:02:0e.0 └── 0000:08:00.0 -> ../../../devices/pci0000:00/0000:00:1c.0/0000:08:00.0
Здесь отчётливо видно (слева) поля, например для контроллера VGA: 0000:00:02.0 - выделены домен (16 бит), шина (8 бит), устройство (5 бит) и функция (3 бита). Поэтому, когда мы говорим об устройстве (далее), мы имеем в виду набор: номера домена + номер шины + номер устройства + номер функции.
С другой стороны, каждое устройство по типу идентифицируется двумя индексами: индекс производителя (Vendor ID) и индекс типа устройства (Device ID). Эта пара однозначно идентифицирует тип устройства. Использование 2-х основных идентификаторов устройств PCI (Vendor ID + Device ID) глобально регламентировано, и их актуальный перечень поддерживается в файле pci.ids, последнюю по времени копию которого можно найти в нескольких местах интернет, например по URL: http://pciids.sourceforge.net/. Эти два параметра являются уникальным (среди всех устройств в мире) ключом поиска устройств, установленных на шине PCI. Для поиска (перебора устройств, установленных на шине PCI) в программном коде модуля в цикле используется итератор:
struct pci_dev * pci_get_device ( unsigned int vendor , unsigned int device, struct pci_dev *from;
- где from — это NULL при начале поиска (или возобновлении поиска с начала), или указатель устройства, найденного на предыдущем шаге поиска. Если в качестве Vendor ID и/или Device ID указана константа PCI_ANY_ID=-1, то предполагается выбор всех доступных устройств с таким идентификатором. Если искомое устройство не найдено (или больше таких устройств не находится в цикле), то очередной вызов возвратит NULL. Если возвращаемое значение не NULL, то возвращается указатель структуры описывающей устройство, и счётчик использования для устройства инкрементируется. Когда устройство удаляется (модуль выгружается) для декремента этого счётчика использования необходимо вызвать:
void pci_dev_put( struct pci_dev *dev );
После нахождения устройства, но прежде начала его использования необходимо разрешить использование устройства вызовом: pci_enable_device( struct pci_dev *dev) , часто это выполняется в функции инициализации устройства: поле probe структуры struct pci_driver (см. далее), но может выполняться и автономно в коде драйвера.
Каждое найденное устройство имеет своё пространство конфигурации, значения которого заполнены программами BIOS (или PnP OS, или BSP) — важно, что на момент загрузки модуля эта конфигурационное пространство всегда заполнено, и может только читаться (не записываться). Пространство конфигурации PCI устройства состоит из 256 байт для каждой функции устройства (для устройств PCI Express расширено до 4 Кб конфигурационного пространства для каждой функции) и стандартизированную схему регистров конфигурации. Четыре начальных байта конфигурационного пространства должны содержать уникальный ID функции (байты 0-1 — Vendor ID, байты 2-3 — Device ID), по которому драйвер идентифицирует своё устройство. Вот для сравнения начальные строки вывода команды для того же хоста (видно, через двоеточие, пары: Vendor ID — Device ID):
$ lspci -n
00:00.0 0600: 8086:27a0 (rev 03) 00:02.0 0300: 8086:27a2 (rev 03) 00:02.1 0380: 8086:27a6 (rev 03) 00:1b.0 0403: 8086:27d8 (rev 01) 00:1c.0 0604: 8086:27d0 (rev 01) 00:1c.2 0604: 8086:27d4 (rev 01) ...
Первые 64 байт конфигурационной области стандартизованы, остальные зависят от устройства. Самыми актуальными для нас являются (кроме ID описанного выше) поля по смещению:
0x10 — Base Sddress 0 0x14 — Base Sddress 1 0x18 — Base Sddress 2 0x1C — Base Sddress 3 0x20 — Base Sddress 4 0x24 — Base Sddress 5; 0x3C — IRQ Line 0x3D — IRQ Pin
Вся регистрация устройства PCI и связывание его параметров с кодом модуля происходит исключительно через значения, считанные из конфигурационного пространства устройства. Обработку конфигурационной информации (уже сформированной при установке PCI устройства) показывает модуль (архив pci.tgz) lab2_pci.ko (заимствовано из [6]):
lab2_pci.c :
#include <linux/module.h> #include <linux/pci.h> #include <linux/errno.h> #include <linux/init.h> static int __init my_init( void ) { u16 dval; char byte; int j = 0; struct pci_dev *pdev = NULL; printk( KERN_INFO "LOADING THE PCI_DEVICE_FINDER\n" ); /* either of the following looping constructs will work */ for_each_pci_dev( pdev ) { /* while ( ( pdev = pci_get_device ( PCI_ANY_ID, PCI_ANY_ID, pdev ) ) ) { */ printk( KERN_INFO "\nFOUND PCI DEVICE # j = %d, ", j++ ); printk( KERN_INFO "READING CONFIGURATION REGISTER:\n" ); printk( KERN_INFO "Bus,Device,Function=%s", pci_name( pdev ) ); pci_read_config_word( pdev, PCI_VENDOR_ID, &dval ); printk( KERN_INFO " PCI_VENDOR_ID=%x", dval ); pci_read_config_word( pdev, PCI_DEVICE_ID, &dval ); printk( KERN_INFO " PCI_DEVICE_ID=%x", dval ); pci_read_config_byte( pdev, PCI_REVISION_ID, &byte ); printk( KERN_INFO " PCI_REVISION_ID=%d", byte ); pci_read_config_byte( pdev, PCI_INTERRUPT_LINE, &byte ); printk( KERN_INFO " PCI_INTERRUPT_LINE=%d", byte ); pci_read_config_byte( pdev, PCI_LATENCY_TIMER, &byte ); printk( KERN_INFO " PCI_LATENCY_TIMER=%d", byte ); pci_read_config_word( pdev, PCI_COMMAND, &dval ); printk( KERN_INFO " PCI_COMMAND=%d\n", dval ); /* decrement the reference count and release */ pci_dev_put( pdev ); } return 0; } static void __exit my_exit( void ) { printk( KERN_INFO "UNLOADING THE PCI DEVICE FINDER\n" ); } module_init( my_init ); module_exit( my_exit ); MODULE_AUTHOR( "Jerry Cooperstein" ); MODULE_DESCRIPTION( "LDD:1.0 s_22/lab2_pci.c" ); MODULE_LICENSE( "GPL v2" );
Небольшой фрагмент результата выполнения этого модуля:
$ sudo insmod lab2_pci.ko
$ lsmod | grep lab
lab2_pci 822 0
$ dmesg | tail -n221 | head -n30
LOADING THE PCI_DEVICE_FINDER FOUND PCI DEVICE # j = 0, READING CONFIGURATION REGISTER: Bus,Device,Function=0000:00:00.0 PCI_VENDOR_ID=8086 PCI_DEVICE_ID=27a0 PCI_REVISION_ID=3 PCI_INTERRUPT_LINE=0 PCI_LATENCY_TIMER=0 PCI_COMMAND=6 FOUND PCI DEVICE # j = 1, READING CONFIGURATION REGISTER: Bus,Device,Function=0000:00:02.0 PCI_VENDOR_ID=8086 PCI_DEVICE_ID=27a2 PCI_REVISION_ID=3 PCI_INTERRUPT_LINE=10 PCI_LATENCY_TIMER=0 PCI_COMMAND=7
$ sudo rmmod lab2_pci
$ lsmod | grep lab2
$
Для использования некоторой группы устройства PCI, код модуля определяет массив описания устройств, обслуживаемых этим модулем. Каждому новому устройству в этом списке соответствует новый элемент. Последний элемент массива всегда нулевой, это и есть признак завершения списка устройств. Строки такого массива заполняются макросом PCI_DEVICE:
static struct pci_device_id i810_ids[] = { { PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1) }, { PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3) }, { PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG ) }, { PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC) }, { PCI_DEVICE( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG) }, { 0, }, };
Созданная структура pci_device_id должна быть экспортирована в пользовательское пространство, чтобы позволить системам горячего подключения и загрузки модулей знать, с какими устройствами работает данный модуль. Эту задачу решает макрос MODULE_DEVICE_TABLE:
MODULE_DEVICE_TABLE( pci, i810_ids );
Кроме доступа к области конфигурационных параметров, программный код может получить доступ к областям ввода-вывода и регионов памяти, ассоциированных с PCI устройством. Таких областей ввода-вывода может быть до 6-ти (см. формат области конфигурационных параметров выше), они индексируются значением от 0 до 5. Параметры этих регионов получаются функциями:
unsigned long pci_resource_start( struct pci_dev *dev, int bar ); unsigned long pci_resource_end( struct pci_dev *dev, int bar ); unsigned long pci_resource_len( struct pci_dev *dev, int bar ); unsigned long pci_resource_flags( struct pci_dev *dev, int bar );
- где bar во всех вызовах — это индекс региона: 0 ... 5. Первые 2 вызова возвращают начальный и конечный адрес региона ввода-вывода (pci_resource_end() возвращает последний используемый регионом адрес, а не первый адрес, следующий после этого региона.), следующий вызов — его размер, и последний — флаги. Полученные таким образом адреса областей ввода/вывода от устройства — это адреса на шине обмена (адреса шины, для некоторых архитектур - x86 из числа таких - они совпадают с физическими адресами памяти). Для использования в коде модуля они должны быть отображены в виртуальные адреса (логические), в которые отображаются страницы RAM посредством устройства управления памятью (MMU). Кроме того, в отличие от обычной памяти, часто эти области ввода/вывода не должны кэшироваться процессором и доступ не может быть оптимизирован. Доступ к памяти таких областей должен быть отмечен как «без упреждающей выборки». Всё, что относится к отображению памяти будет рассмотрено отдельно далее, в следующем разделе. Флаги PCI региона (pci_resource_flags()) определены в <linux/ioport.h>; некоторые из них:
IORESOURCE_IO, IORESOURCE_MEM — только один из этих флагов может быть установлен.
IORESOURCE_PREFETCH — определяет, допустима ли для региона упреждающая выборка.
IORESOURCE_READONLY — определяет, является ли регион памяти защищённым от записи.
Основной структурой, которую должны создать все драйверы PCI для того, чтобы быть правильно зарегистрированными в ядре, является структура (<linux/pci.h>):
struct pci_driver { struct list_head node; char *name; const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */ int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */ void (*remove) (struct pci_dev *dev);/* Device removed (NULL if not a hot-plug driver) */ int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */ int (*suspend_late) (struct pci_dev *dev, pm_message_t state); int (*resume_early) (struct pci_dev *dev); int (*resume) (struct pci_dev *dev); /* Device woken up */ void (*shutdown) (struct pci_dev *dev); struct pci_error_handlers *err_handler; struct device_driver driver; struct pci_dynids dynids; };
Где:
- name - имя драйвера, оно должно быть уникальным среди всех PCI драйверов в ядре, обычно устанавливается таким же, как и имя модуля драйвера, когда драйвер загружен в ядре, это имя появляется в /sys/bus/pci/drivers/;
- id_table - только что описанный массив записей pci_device_id;
- probe — функция обратного вызова инициализации устройства; в функции probe драйвера PCI, прежде чем драйвер сможет получить доступ к любому ресурсу устройства (область ввода/вывода или прерывание) данного PCI устройства, драйвер должен, как минимум, вызвать функцию :
int pci_enable_device( struct pci_dev *dev );
- remove — функция обратного вызова удаления устройства;
- ... и другие функции обратного вызова.
Обычно для создания правильную структуру struct pci_driver достаточно бывает определить, как минимум, поля :
static struct pci_driver own_driver = { .name = "mod_skel", .id_table = i810_ids, .probe = probe, .remove = remove, };
Теперь устройство может быть зарегистрировано в ядре:
int pci_register_driver( struct pci_driver *dev );
- вызов возвращает 0 если регистрация устройства прошла успешно.
При завершении (выгрузке) модуля выполняется обратная операция:
void pci_unregister_driver( struct pci_driver *dev );
Предыдущий раздел: | Оглавление | Следующий раздел: |
Обслуживание периферийных устройств | Подключение к линии прерывания |