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

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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 );

Предыдущий раздел: Оглавление Следующий раздел:
Обслуживание периферийных устройств   Подключение к линии прерывания