Обслуживание периферии в коде модулей ядра: Часть 51. Взаимодействие с PCI-устройствами

Comments

В предыдущей части цикла были рассмотрены способы идентификации PCI-устройств и их адресации. Теперь мы воспользуемся этой информацией в программном коде модуля.

Отображение PCI в код модуля

Первейшей задачей модуля-драйвера является привязка VID:DID идентификатора устройства к его адресу в терминах <шина>:<устройство>.<функция>. Код модуля выполняет поиск устройств, установленных на шине PCI, путём циклического перебора всех установленных устройств по определённым критериям. Для поиска в программном коде модуля в цикле используется итератор:

struct pci_dev *pci_get_device( unsigned int vendor,
            ....................unsigned int device, struct pci_dev *from );

Параметр from — это NULL на начальном шаге поиска или указатель устройства, найденного на предыдущем шаге, для последующих выполняемых шагов. Если в качестве VID и/или DID указана специальная константа с именем PCI_ANY_ID (численно равная -1), то предполагается перебор всех доступных устройств в данной позиции. Если искомое устройство найти не удалось, то очередной вызов возвратит NULL. Если же возвращаемое значение не равно NULL, то в нём содержится очередной указатель на структуру struct pci_dev*, описывающую устройство, а счётчик использования для этого устройства инкрементируется. При выгрузке модуля, когда устройство нужно удалить, для декремента этого счётчика следует вызвать:

void pci_dev_put( struct pci_dev *dev );

После нахождения устройства, но до начала его использования необходимо разрешить использование устройства вызовом: pci_enable_device( struct pci_dev *dev ). Это действие может выполняться в функции инициализации устройства в поле probe структуры struct pci_driver или автономно в коде драйвера.

Каждое найденное устройство обладает собственным пространством конфигурации. На момент загрузки модуля это конфигурационное пространство всегда заполнено, и может только считываться. В предыдущей статье уже упоминались важнейшие для разработчика параметры, и их смещения от начала конфигурационного пространства. Так, в этой области указывается уникальный ID функции (байты 0-1 —Vendor ID, байты 2-3 —Device ID), по которому драйвер идентифицирует своё устройство.

0x00 — VID
0x02 — DID

Первые 64 байта конфигурационной области стандартизованы, остальные являются специфическими для устройства. Наиболее важными являются поля по смещению:

0x10 — Base Address 0
0x14 — Base Address 1
0x18 — Base Address 2
0x1C — Base Address 3
0x20 — Base Address 4
0x24 — Base Address 5;
0x3C — IRQ Line
0x3D — IRQ Pin

Вся регистрация PCI-устройства и связывание его параметров с кодом модуля должны происходить исключительно через значения, считанные из конфигурационного пространства устройства. В листинге 1 показан пример обработки конфигурационной информации в модуле lab2_pci.ko. Полный код примера можно найти в архиве pci.tgz в разделе "Материалы для скачивания" или в 31-ой главе книги "Writing Linux Device Drivers" Джерри Купперстайна (Jerry Cooperstein)

Листинг 1. Чтение конфигурационных параметров.
#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" );

Рассмотрим этот код подробно, чтобы сформулировать ряд утверждений:

  1. поскольку операция перечисления PCI-устройств выполняется часто, то для неё сконструирован специальный макрос for_each_pci_dev(), исходный код которого приведён в комментариях;
  2. конфигурационные параметры никогда не читаются напрямую по смещениям, для этого используются inline-вызовы pci_read_config_byte(), pci_read_config_word() и другие, определенные в файле linux/pci.h;
  3. конкретный вид считываемого конфигурационного параметра задаётся символьными константами вида PCI_* (определены там же), указываемыми как 2-й параметр макроса;
  4. результат (значение конфигурационного параметра) возвращается в 3-ем параметре макроса.

Теперь можно рассмотреть небольшой начальный фрагмент выполнения модуля lab2_pci.ko:

$ sudo insmod lab2_pci.ko
$ lsmod | grep lab
lab2_pci                 822  0
$ dmesg | tail -n221
...
[22719.450070] FOUND PCI DEVICE # j = 5,
[22719.450071] READING CONFIGURATION REGISTER:
[22719.450072] Bus,Device,Function=0000:00:04.0
[22719.450082]  PCI_VENDOR_ID=80ee
[22719.450092]  PCI_DEVICE_ID=cafe
[22719.450102]  PCI_REVISION_ID=0
[22719.450111]  PCI_INTERRUPT_LINE=9
[22719.450121]  PCI_LATENCY_TIMER=0
[22719.450130]  PCI_COMMAND=7
...

Мы уже научились выполнять поиск PCI-устройства в системе и извлекать его параметры из области конфигурации, поэтому можно перейти к описанию обобщённой схемы взаимодействия с устройством в коде модуля-драйвера.

Для использования некоторой группы устройств (функций!) 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, },
};

Часто такой массив будет содержать всего два элемента: элемент, описывающий устройство-функцию, обслуживаемое модулем, и завершающий нулевой элемент. Созданная структура struct pci_device_id должна быть экспортирована в пользовательское пространство, чтобы системы горячего подключения и загрузки модулей (sysfs, udev и т.д.) смогли узнать, с какими устройствами работает данный модуль. Эту задачу решает макрос MODULE_DEVICE_TABLE:

MODULE_DEVICE_TABLE( pci, i810_ids );

Кроме доступа к области конфигурационных параметров, программный код должен получить доступ к областям ввода-вывода или регионам памяти , ассоциированных с PCI-устройством. Таких областей ввода-вывода / регионов памяти может быть до 6-ти (см. формат области конфигурационных параметров выше: Base Address 0...5), они индексируются значением от 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.

Полученные таким образом адреса областей ввода/вывода устройства — это адреса на шине обмена (для некоторых архитектур (например, x86) адреса шины совпадают с физическими адресами памяти). Для использования в коде модуля они должны быть отображены в виртуальные адреса (логические), в которые отображаются страницы RAM посредством устройства управления памятью (MMU). Кроме того, в отличие от обычной памяти, эти области ввода/вывода не должны кэшироваться процессором, а доступ к ним не может быть оптимизирован (доступ к таких областям памяти должен быть отмечен как «без упреждающей выборки»). К отображению памяти мы ещё вернемся в конце статьи, но заметим, что все необходимые действия (в описываемом контексте) выполняются довольно формально (единообразно). Ниже перечислены некоторые из флагов, возвращаемых вызовом pci_resource_flags() (их полный список можно найти в файле <linux/ioport.h>):

  • IORESOURCE_IO, IORESOURCE_MEM— (только один из этих флагов может быть установлен) указывают, относятся ли адреса к пространству ввода-вывода или к пространству памяти (в архитектурах, отображающих ввод-вывод на память);
  • IORESOURCE_PREFETCH— определяет, допустима ли для региона упреждающая выборка;
  • IORESOURCE_READONLY— определяет, является ли регион памяти защищённым от записи.

Целевое назначение любого из 6-ти областей ввода-вывода и их общее число являются специфическими для каждого типа устройства.

Для регистрации в ядре все драйверы PCI-устройств должны заполнить структуру pci_driver, объявленную в файле <linux/pci.h>:

struct pci_driver {
  struct list_head node;
  char *name;
  const struct pci_device_id *id_table;
  int  (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
  void (*remove) (struct pci_dev *dev);
  int  (*suspend) (struct pci_dev *dev, pm_message_t state);
  int  (*resume) (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— функция обратного вызова, используемая для инициализации устройства;
  • remove— функция обратного вызова при удалении устройства;
  • suspend— функция менеджера энергосохранения, вызываемая когда устройство уходит в пассивное состояние (засыпает);
  • resume— функция менеджера энергосохранения, вызываемая при пробуждении устройства.

Обычно, для создания правильно заполненной структуры 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 );

На этом регистрация PCI-устройства в системе завершается, и оно считается готовым к работе.

Заключение

В данной статье был рассмотрен процесс регистрации реального PCI-устройства в коде драйвера. Представленное описание процесса подходит для большинства устройств, при этом для каждого устройства, конечно, используются его специфические значения. Поэтому полученной информации будет вполне достаточно для написания кода, выполняющего инициализацию драйвера.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=930791
ArticleTitle=Обслуживание периферии в коде модулей ядра: Часть 51. Взаимодействие с PCI-устройствами
publish-date=05212013