Интерфейс PCI

Предыдущая  Содержание  Следующая V*D*V

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

 

Спецификация PCI охватывает большинство вопросов, связанных с компьютерными интерфейсами. Мы не собираемся покрыть здесь их все; в этом разделе мы в основном касаемся того, как драйвер PCI может найти своё оборудование и получить к нему доступ. Методы зондирования, обсуждаемые в разделе "Параметры модуля" в Главе 2 и "Автоопределение номера прерывания" в Главе 10, могут использоваться с устройствами PCI, но спецификация предлагает альтернативу, которая более предпочтительна, чем зондирование.

 

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

 

Шина PCI достигает лучшей производительности за счёт использования более высокой тактовой частоты, чем ISA; она работает на 25 или 33 МГц (фактическое значение зависит от частоты системы), и недавно были развернуты также 66 МГц и даже 133 МГц реализации. Кроме того, она оснащена 32-х разрядной шиной данных и в спецификацию было включено 64-х разрядное расширение. Независимость от платформы является частой целью в разработке компьютерной шины и это особенно важная особенность PCI, поскольку в мире ПК всегда доминировали стандарты интерфейсов, зависимые от процессора. В настоящее время PCI широко используется на системах IA-32, Alpha, PowerPC, SPARC64 и IA-64, а также некоторые других платформах.

 

Однако, наиболее актуальной для автора драйвера является поддержка PCI автоопределения интерфейса плат. PCI устройства безджамперные (в отличие от большинства старой периферии) и настраивается автоматически во время загрузки. Затем драйвер устройства должен быть в состоянии получить доступ к информации о конфигурации в устройстве с целью завершения инициализации. Это происходит без необходимости совершать какое-либо тестирование.

Адресация в PCI

Каждое периферийное устройство PCI идентифицируется номером шины, номером устройства и номером функции. Спецификация PCI позволяет одной системе содержать до 256 шин, но из-за того, что 256 шин не является достаточным для многих больших систем, Linux теперь поддерживает домены PCI. Каждый домен PCI может содержать до 256 шин. Каждая шина содержит до 32 устройств и каждое устройство может быть многофункциональной платой (такой, как аудио-устройство с сопровождающим приводом CD-ROM) с максимум восемью функциями. Поэтому каждая функция может быть идентифицирована на аппаратном уровне 16-ти разрядным адресом, или ключом. Однако, драйверам устройств, написанным для Linux, не требуется иметь дело с этими двоичными адресами, потому что они используют для работы с устройствами специальную структуру данных, названную pci_dev.

 

Последние рабочие станции имеют по крайней мере две шины PCI. Подключение более одной шины в одной системе выполняется с помощью мостов, периферии PCI специального назначения, задачей которой является объединение двух шин. Общая схема системы PCI представляет собой дерево, где каждая шина связана с шиной верхнего уровня, вплоть до шины 0 в корне дерева. Система карт ПК CardBus (стандарт шины для PCMCIA) также подключена к системе PCI через мосты. Типичная система PCI представлена на Рисунке 12-1, где  подсвечены различные мосты.

 

Рисунок 12-1. Схема типичной системы PCI

Рисунок 12-1. Схема типичной системы PCI

 

16-ти разрядные аппаратные адреса, связанные с периферийными устройствами PCI, чаще всего скрыты в объекте struct pci_dev, но иногда всё ещё видимы, особенно когда используются списки устройств. Одной из таких ситуаций является вывод lspci (часть пакета pciutils, доступного в большинстве дистрибутивов) и расположение информации в /proc/pci и /proc/bus/pci. Представление устройств PCI в sysfs также показывает эту схему адресации с добавлением информации о домене PCI. (* Некоторые архитектуры также показывают информацию PCI домена в файлах /proc/pci и /proc/bus/pci.) При отображении аппаратный адрес может быть показан в виде двух значений (8-ми разрядный номер шины и 8-ми разрядные номера устройства и функции), как три значения (шина, устройство и функция), или как четыре значения (домен, шина, устройство и функция); все значения, как правило, отображаются в шестнадцатеричном виде.

 

Например, /proc/bus/pci/devices используют одно 16-ти разрядное поле (для облегчения анализа и сортировки), а /proc/bus/busnumber разделяет адрес на три поля. Следующий снимок демонстрирует, как выглядят эти адреса, показано только начало строк вывода:

 

$ lspci | cut -d: -f1-3

0000:00:00.0 Host bridge

0000:00:00.1 RAM memory

0000:00:00.2 RAM memory

0000:00:02.0 USB Controller

0000:00:04.0 Multimedia audio controller

0000:00:06.0 Bridge

0000:00:07.0 ISA bridge

0000:00:09.0 USB Controller

0000:00:09.1 USB Controller

0000:00:09.2 USB Controller

0000:00:0c.0 CardBus bridge

0000:00:0f.0 IDE interface

0000:00:10.0 Ethernet controller

0000:00:12.0 Network controller

0000:00:13.0 FireWire (IEEE 1394)

0000:00:14.0 VGA compatible controller

$ cat /proc/bus/pci/devices | cut -f1

0000

0001

0002

0010

0020

0030

0038

0048

0049

004a

0060

0078

0080

0090

0098

00a0

$ tree /sys/bus/pci/devices/

/sys/bus/pci/devices/

|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0

|-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1

|-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2

|-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0

|-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0

|-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0

|-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0

|-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0

|-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1

|-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2

|-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0

|-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0

|-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0

|-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0

|-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0

`-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0

 

Все три списка устройств отсортированы в одном порядке, поскольку lspci использует как источник информации файлы /proc. Используя  видеоконтроллер VGA в качестве примера, 0x00a0 представляется как 0000:00:14.0 при разделении на домен (16 бит), шину (8 бит), устройство (5 бит) и функцию (3 бита).

 

Аппаратная схема каждой периферийной платы отвечает на запросы, относящиеся к трём адресным пространствам: ячейкам памяти, портам ввода/вывода и регистрам конфигурации. Первые два адресных пространства являются общими для всех устройств на одной шине PCI (то есть, когда вы обращаетесь к памяти, все устройства на этой шине PCI видят этот цикл шины одновременно). Пространство конфигурации, с другой стороны, использует географическую адресацию. Запросы конфигурации в один момент времени адресуют только один слот, поэтому они никогда не конфликтуют.

 

Что же касается драйвера, области памяти и ввода/вывода доступны обычными способами через inb, readb и так далее. С другой стороны, операции по конфигурации выполняются вызовом для доступа к регистрам конфигурации определённых функций ядра. Что касается прерываний, каждый слот PCI имеет четыре контакта прерывания и каждая функция устройства может воспользоваться одной из них, не заботясь о том, как эти контакты маршрутизируются в процессор. Такая маршрутизация является обязанностью компьютерной платформы и осуществляется вне шины PCI. Так как спецификация PCI требует, чтобы линии прерывания были разделяемыми, даже процессор с ограниченным количеством линий прерывания, такой как x86, может принимать много интерфейсных плат PCI (каждая с четырьмя контактами прерываний).

 

В шине PCI пространство ввода/вывода использует 32-х разрядную шину адреса (что ведёт к 4 Гб портов ввода/вывода), в то время как пространство памяти может быть доступно или с 32-х разрядными, или 64-х разрядными адресами. 64-х разрядные адреса доступны на более новых платформах. Адреса должны быть уникальными для одного устройства, но программное обеспечение может ошибочно настроить два устройства на один адрес, делая невозможным доступ к любому из них. Но такой проблемы никогда не случится, если драйвер не будет играть с регистрами, которых не должен касаться. Хорошая новость в том, что каждая область памяти и адреса ввода/вывода, предлагаемые интерфейсной платой, могут быть переназначены с помощью операций по конфигурации. То есть встроенное программное обеспечение при загрузке системы инициализирует оборудование PCI, отображая каждую область на другой адрес во избежание конфликтов. (* На самом деле, такая конфигурация не ограничивается временем загрузки системы; например, устройства, подключаемые без выключения системы, не могут быть доступны во время загрузки и вместо этого появляются позже. Основным моментом здесь является то, что драйвер устройства не должен менять области адреса ввода/вывода или памяти.) Адреса, по которым эти регионы в настоящее время отображаются, могут быть прочитаны из конфигурационного пространства, поэтому драйвер Linux может получить доступ к своим устройствам без зондирования. После чтения регистров конфигурации драйвер может иметь безопасный доступ к своему оборудованию.

 

Пространство конфигурации PCI состоит из 256 байт для каждой функции устройства (за исключением устройств PCI Express, которые имеют 4 Кб конфигурационного пространства для каждой функции) и стандартизированную схему регистров конфигурации. Четыре байта конфигурационного пространства содержать уникальный ID функции, поэтому драйвер может определить своё устройство, глядя на заданный для такой периферии ID. (* Вы найдёте ID любого устройства в своём руководстве для оборудования. Перечень включён в файл pci.ids, часть  пакета pciutils, и исходные тексты ядра; он не претендует на полноту, это просто список наиболее известных поставщиков и устройств. Версия ядра этого файла не будет включена в будущие серии ядра.) Таким образом, каждая плата устройства адресуема географически для получения её регистров конфигурации; затем информация в этих регистрах может быть использована для выполнения обычного доступа ввода/вывода, без необходимости дальнейшей географической адресации.

 

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

 

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

Момент загрузки

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

 

При подаче питания на устройство PCI его оборудование остаётся неактивным. Другими словами, устройство реагирует только на операции по конфигурации. При включении питания устройство не имеет памяти и портов ввода/вывода, связанных с адресным пространством компьютера; все другие функции, зависящие от устройства, такие как генерация прерываний, также отключены. К счастью, все материнские платы с PCI оснащены осведомлённым о PCI встроенным программным обеспечением, называемым BIOS, NVRAM, или PROM, в зависимости от платформы. Встроенное программное обеспечение обеспечивает доступ к адресному пространству конфигурации устройства чтением и записью регистров контроллера PCI.

 

Во время загрузки системы встроенное программное обеспечение (или ядро Linux, если так настроено) выполняет операции по конфигурации с каждой периферией PCI для того, чтобы выделить безопасное место для каждого адресуемого региона, предлагаемого ей. К тому времени, когда драйвер устройства обращается к устройству, его области памяти и ввода/вывода уже отображены в адресное пространство процессора. Драйвер может изменить это значение по умолчанию, но он никогда не должен этого делать.

 

Как предложено, пользователь может посмотреть список устройств PCI и регистры конфигурации устройства читая /proc/bus/pci/devices и /proc/bus/pci/*/*. Первый из них является текстовым файлом с (шестнадцатеричной) информацией об устройстве, а последние являются бинарными файлами, которые показывают снимки регистров конфигурации каждого устройства, один файл на устройство. Отдельный каталог PCI устройства в дереве sysfs может быть найден в /sys/bus/pci/devices. Каталог PCI устройства содержит несколько различных файлов:

 

$ tree /sys/bus/pci/devices/0000:00:10.0

/sys/bus/pci/devices/0000:00:10.0

|-- class

|-- config

|-- detach_state

|-- device

|-- irq

|-- power

|   `-- state

|-- resource

|-- subsystem_device

|-- subsystem_vendor

`-- vendor

 

Файл конфигурации представляет собой бинарный файл, который позволяет считать из устройства сырую информации о конфигурации PCI (подобно обеспечиваемой /proc/bus/pci/*/*.) Каждый из файлов vendor, device, subsystem_device, subsystem_vendor и class ссылается на определённые значения этого PCI устройства (все PCI устройства предоставляют эту информацию.) Файл irq показывает текущее прерывание, назначенное для этого PCI устройства, и файл resource показывает текущие ресурсы памяти, выделенные этим устройством.

Регистры конфигурации и инициализация

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

 

Рисунок 12-2. Стандартизированные конфигурационные регистры PCI

Рисунок 12-2. Стандартизированные конфигурационные регистры PCI

 

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

 

Интересно отметить, что регистры PCI всегда little-endian (сначала младший). Хотя стандарт разрабатывался, чтобы быть независимым от архитектуры, разработчики PCI иногда показывают склонность в сторону среды ПК. Автор драйвера должен быть аккуратен с порядком байтов при доступе к многобайтовым регистрам конфигурации; код, который работает на ПК, может не работать на других платформах. Разработчики Linux позаботились о проблеме порядка байт (смотрите следующий раздел "Доступ к пространству конфигурации"), но об этом необходимо помнить. Если вам когда-нибудь понадобиться преобразовать данные от порядка на платформе в порядок PCI, или наоборот, вы можете прибегнуть к функциям, определённым в <asm/byteorder.h>, введённым в Главе 11, зная, что порядок байт в PCI little-endian.

 

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

 

Устройство идентифицируют три или пять регистров PCI: vendorID, deviceID и class являются теми тремя, которые используются всегда. Каждый производитель PCI присваивает собственные значения этим регистрам, предназначенным только для чтения, и драйвер может использовать их для поиска устройства. Кроме того, с целью дальнейшего различия похожих устройств поставщик иногда устанавливает поля subsystem vendorID и subsystem deviceID.

 

Давайте посмотрим на эти регистры более подробно:

 

vendorID

Этот 16-ти разрядный регистр идентифицирует изготовителя оборудования. Например, каждое устройство Intel отмаркировано одним и тем же числом поставщика, 0x8086. Существует глобальный реестр таких чисел, который ведёт PCI Special Interest Group, и производители должны обратиться туда, чтобы получить уникальный номер.

 

deviceID

Это другой 16-ти разрядный регистр, выбранный производителем; для ID устройства не требуется никакой официальной регистрации. Этот ID, как правило, используется в паре с ID поставщика, создавая уникальный 32-х разрядный идентификатор аппаратного устройства. Мы используем слово сигнатура для обращения к ID поставщика и ID устройства в паре. Драйвер устройства обычно полагается на сигнатуру, чтобы определить своё устройство; вы можете найти, какое значение искать, в руководстве оборудования для целевого устройства.

 

class

Каждое периферийное устройство относится к class (классу). Регистр class является 24-х разрядным значением, чьи старшие 8 бит идентифицируют "базовый класс" (или группу). Например, "ethernet" и "token ring" являются двумя классами, принадлежащими к группе "network", а классы "serial" и "parallel" относятся к группе "communication". Некоторые драйверы могут поддерживать несколько аналогичных устройств, каждое из них имеет свою сигнатуру, однако все они принадлежат к одному классу; эти драйверы могут рассчитывать на регистр class для определения своих периферийных устройства, как показано ниже.

 

subsystem vendorID

subsystem deviceID

Эти поля могут быть использованы для дальнейшей идентификации устройства. Если такая микросхема является обычным чипом интерфейса для локальной (на плате) шины, она часто используется в нескольких совершенно разных ролях и драйвер должен определить фактическое устройство для общения. Идентификаторы subsystem (подсистема) и используются для этой цели.

 

Используя эти разные идентификаторы, драйвер PCI может сказать ядру, какие виды устройств он поддерживает. Чтобы определить список различных типов устройств PCI, которые поддерживает драйвер, используется структура struct pci_device_id. Эта структура содержит следующие поля:

 

__u32 vendor;

__u32 device;

Они определяют идентификаторы поставщика PCI и устройства. Если драйвер может работать с любым идентификатором поставщика или устройства, для этих полей должно быть использовано значение PCI_ANY_ID.

 

__u32 subvendor;

__u32 subdevice;

Эти определяют идентификаторы поставщика PCI подсистемы и подсистемы устройства для устройства. Если драйвер может обрабатывать любой тип ID подсистемы, для этих полей должно быть использовано значение PCI_ANY_ID.

 

__u32 class;

__u32 class_mask;

Эти два значения позволяют драйверу определить, какой тип устройства PCI класса он поддерживает. Разные классы устройств PCI (VGA контроллер является одним из примеров) описаны в спецификации PCI. Если драйвер может обрабатывать любой класс, для этих полей должно быть использовано значение 0.

 

kernel_ulong_t driver_data;

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

 

Существуют две вспомогательные макроса, которые должны использоваться для инициализации структуры struct pci_device_id:

 

PCI_DEVICE(vendor, device)

Этот создаёт структуру pci_device_id, которая соответствует только заданным идентификаторам поставщика и устройства. Макрос устанавливает поля структуры subvendor и subdevice в PCI_ANY_ID.

 

PCI_DEVICE_CLASS(device_class, device_class_mask)

Этот создаёт структуру pci_device_id, которая соответствует определённому классу PCI.

 

Пример использования этих макросов для определения типа устройств, которые поддерживает драйвер, может быть найден в следующих файлах ядра:

 

drivers/usb/host/ehci-hcd.c:

 

static const struct pci_device_id pci_ids[ ] = { {

    /* работаем с любым контроллером USB 2.0 EHCI */

    PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x20), ~0),

    .driver_data = (unsigned long) &ehci_driver,

    },

    { /* последняя: все нули */ }

};

 

drivers/i2c/busses/i2c-i810.c:

 

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 с пустой структурой, проинициализированной нулями в качестве последнего значения в списке. Этот массив идентификаторов используется в структуре pci_driver (описанной ниже) и используется также, чтобы указать пользовательскому пространству, какие устройства поддерживает этот специфический драйвер.

MODULE_DEVICE_TABLE

Структура pci_device_id должна быть экспортирована в пользовательское пространство, чтобы позволить системам горячего подключения и загрузки модулей узнать, с какими устройствами работает модуль. Эту задачу решает макрос MODULE_DEVICE_TABLE. Пример:

 

MODULE_DEVICE_TABLE(pci, i810_ids);

 

Этот оператор создаёт локальную переменную, называемую __mod_pci_device_table, которая указывает на список struct pci_device_id. Позже, в процессе сборки ядра, программа depmod просматривает все модули на символ __mod_pci_device_table. Если этот символ найден, она вынимает данные из модуля и добавляет их в файл /lib/modules/KERNEL_VERSION/modules.pcimap. После завершения работы depmod, все PCI устройства, поддерживаемые модулем в ядре, вместе с именами их модулей, перечисляются этом в файле. Когда ядро сообщает системе горячего подключения, что было найдено новое устройство PCI, система горячего подключения использует файл modules.pcimap, чтобы найти для загрузки правильный драйвер.

Регистрация PCI драйвера

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

 

const char *name;

Имя драйвера. Оно должно быть уникальным среди всех PCI драйверов в ядре и обычно устанавливается таким же, как и имя модуля драйвера. Когда драйвер находится в ядре, оно появляется в sysfs в /sys/bus/pci/drivers/.

 

const struct pci_device_id *id_table;

Указатель на таблицу struct pci_device_id, описанную ранее в этой главе.

 

int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);

Указатель на зондирующую функцию в драйвере PCI. Эта функция вызывается ядром PCI, когда оно имеет struct pci_dev и думает, что этот драйвер хочет контролировать её. Указатель на struct pci_device_id, который используется ядром PCI, чтобы сделать это решение, также передаётся в эту функцию. Если драйверу PCI требуется передать ей struct pci_dev, он должен правильно проинициализировать устройство и вернуть 0. Если драйвер не хочет заявлять устройство или произошла ошибка, он должен вернуть отрицательное значение ошибки. Подробнее об этой функции далее в этой главе.

 

void (*remove) (struct pci_dev *dev);

Указатель на функцию, которую вызывает ядро PCI, когда struct pci_dev удаляется из системы, или когда PCI драйвер выгружается из ядра. Подробнее об этой функции далее в этой главе.

 

int (*suspend) (struct pci_dev *dev, u32 state);

Указатель на функцию, которую вызывает ядро PCI, когда struct pci_dev в настоящее время приостановлена. Состояние приостановки передаётся в переменной state. Эта функция не является обязательной; драйвер не обязан предоставлять её.

 

int (*resume) (struct pci_dev *dev);

Указатель на функцию, которую вызывает ядро PCI, когда struct pci_dev возобновляется. Она всегда вызывается после того, как была вызвана suspend. Эта функция не является обязательной; драйвер не обязан предоставлять её.

 

Таким образом, чтобы создать правильную структуру struct pci_driver должны быть проинициализированы только четыре поля :

 

static struct pci_driver pci_driver = {

    .name = "pci_skel",

    .id_table = ids,

    .probe = probe,

    .remove = remove,

};

 

Чтобы зарегистрировать struct pci_driver в ядре PCI, выполняется вызов pci_register_driver с указателем на struct pci_driver. Это традиционно делается в коде инициализации модуля PCI драйвера:

 

static int __init pci_skel_init(void)

{

    return pci_register_driver(&pci_driver);

}

 

Обратите внимание, что функция pci_register_driver возвращает или отрицательное число ошибки, или 0, если всё было успешно зарегистрировано. Она не возвращает количество устройств, которые были связаны с драйвером или номер ошибки, если нет устройств, связанных с драйвером. Это является изменением от предыдущих ядер к релизу версии 2.6 и было сделано из-за следующих ситуаций:

 

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

Ядро версии 2.6 позволяет новым идентификаторам PCI быть динамически выделенными для драйвера после того, как он был загружен. Это делается через файл new_id, который создаётся во всех каталогах PCI драйверов в sysfs. Это очень полезно, если используется новое устройство, о котором ядро пока ещё не знает. Пользователь может записать PCI ID значения в файл new_id, а затем драйвер связывается с новым устройством. Если бы драйверу не была разрешена загрузка, пока устройство не присутствует в системе, этот интерфейс бы не был в состоянии работать.

 

Когда PCI драйвер должен быть выгружен, необходимо разрегистрировать struct pci_driver в ядре. Это делается с помощью вызова pci_unregister_driver. Когда происходит этот вызов, любые PCI устройства, которые в настоящее время связаны с этим драйвером, удаляются, и перед возвращением функции pci_unregister_driver вызывается функция remove этого драйвера PCI.

 

static void __exit pci_skel_exit(void)

{

    pci_unregister_driver(&pci_driver);

}

Старый способ зондирования PCI

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

 

Если способность находить определённое устройства PCI действительно необходима, доступны следующие функции:

 

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

Эта функция сканирует список устройств PCI, присутствующих в системе в настоящее время, и если входные параметры соответствуют указанным идентификаторам vendor и device, она увеличивает счётчик ссылок на найденную переменную struct pci_dev и возвращает его вызывающему. Это предотвращает исчезновение структуры без предварительного уведомления и гарантирует, что ядро не выдаст Oops. После того, как драйвер завершает работу со struct pci_dev, возвращённой функцией, он должен вызвать функцию pci_dev_put для правильного обратного уменьшения счётчика использования, чтобы разрешить ядру очистить устройство, если оно удаляется.

Чтобы завладеть несколькими устройствами с одинаковой сигнатурой, используется аргумент from; аргумент должен указывать на последнее найденное устройство, чтобы поиск мог продолжаться, вместо перезапуска с начала списка. Чтобы найти первое устройство, from определяется как NULL. Если не найдено (больше) устройств, возвращается NULL.

Пример правильного использования этой функции:

 

struct pci_dev *dev;

dev = pci_get_device(PCI_VENDOR_FOO, PCI_DEVICE_FOO, NULL);

if (dev) {

    /* Используем это PCI устройство */

    ...

    pci_dev_put(dev);

}

 

struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device,

                     unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from);

Эта функция работает подобно pci_get_device, но она позволяет указать для поиска устройства идентификаторы поставщика подсистемы и подсистемы устройства.

Эта функция не может быть вызвана из контекста прерывания. Если это случилось, в системный журнал печатается предупреждение.

 

struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn);

Эта функция выполняет поиск списка устройств PCI в системе на заданную struct pci_bus для указанного устройства и номера функции устройства PCI. Если обнаружено соответствующее устройство, его счётчик ссылок увеличивается и возвращается указатель на него. Когда вызывающий закончил обращение к struct pci_dev, он должен вызвать pci_dev_put.

 

Все эти функции не могут быть вызваны из контекста прерывания. Если это случилось, в системный журнал печатается предупреждение.

Разрешение устройства PCI

В функции probe драйвера PCI, прежде чем драйвер сможет получить доступ к любому ресурсу устройства (область ввода/вывода или прерывание) данного PCI устройства, драйвер должен вызвать функцию pci_enable_device:

 

int pci_enable_device(struct pci_dev *dev);

Эта функция фактически разрешает устройство. Она будит устройство и в некоторых случаях также задаёт ему линию прерывания и области ввода/вывода. Это происходит, например, с устройствами CardBus (которые были сделаны полностью эквивалентными PCI на уровне драйвера).

Доступ в пространство конфигурации

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

 

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

 

Что же касается драйвера, доступ в конфигурационное пространство можно получить через 8-ми разрядные, 16-ти разрядные или 32-х разрядные передачи данных. Прототипы соответствующих функций в <linux/pci.h>:

 

int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);

int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);

int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);

Прочитать один, два или четыре байта из конфигурационного пространства устройства, идентифицируемого dev. Аргумент where является байтовым смещением от начала конфигурационного пространства. Значение, извлекаемое из конфигурационного пространства, возвращается через указатель val и возвращаемое значение функции является кодом ошибки. Функции word и dword преобразуют только что прочитанное значение из little-endian (сначала младший) в родной для процессора порядок байтов, так что нет необходимости заботится о порядке байтов.

 

int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);

int pci_write_config_word(struct pci_dev *dev, int where, u16 val);

int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

Записывают один, два или четыре байта в конфигурационное пространство. Устройство идентифицируется dev, как обычно, а записываемое значение передаётся как val. Функции word и dword преобразуют значение в little-endian перед записью на периферийное устройство.

 

Все предыдущие функции реализованы в виде встраиваемых функций, которые на самом деле вызывают следующие функции. Не стесняйтесь использовать эти функции взамен вышеприведённых в случае, если в какой-то момент времени драйвер не имеет доступа к struct pci_dev:

 

int pci_bus_read_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val);

int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val);

int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val);

Подобны функциям pci_read_, но вместо struct pci_dev * необходимы переменные struct pci_bus * и devfn.

 

int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 val);

int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 val);

int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 val);

Подобны функциям pci_write_, но вместо struct pci_dev * необходимы переменные struct pci_bus * и devfn.

 

Лучшим способом адресации переменных конфигурации с помощью функций pci_read_ является использование символических имён, определённых в <linux/pci.h>. Например, следующая небольшая функция получает идентификатор ревизии устройства, передавая его символическое имя в pci_read_config_byte:

 

static unsigned char skel_get_revision(struct pci_dev *dev)

{

    u8 revision;

 

    pci_read_config_byte(dev, PCI_REVISION_ID, &revision);

    return revision;

}

Доступ к пространствам ввода/вывода и памяти

PCI устройство реализует до шести областей адресов ввода/вывода. Каждый регион состоит или из адресов памяти или из адресов ввода/вывода. Большинство устройств содержат свои регистры ввода/вывода в областях памяти, потому что это наиболее разумный подход (как описано в разделе "Порты ввода/вывода и память ввода/вывода" в Главе 9). Однако, в отличие от обычной памяти, регистры ввода/вывода не должны кэшироваться процессором, поскольку каждый доступ может иметь побочные эффекты. PCI устройство, которое реализует регистры ввода/вывода как область памяти, отмечает различие значением бита “memory-is-prefetchable” ("память с упреждающей выборкой") в его регистре конфигурации. (* Информация живёт в одном из младших битов базового адреса регистров PCI. Эти биты определены в <linux/pci.h>.) Если область память обозначена как "с упреждением", процессор может кэшировать её содержимое и выполнять с ним все виды оптимизации; доступ к памяти без упреждающей выборки, с другой стороны, не может быть оптимизирован, так как каждый доступ может иметь побочные эффекты, так же как и с портами ввода/вывода. Периферия, которая связывает свои регистры управления с диапазоном адресов памяти, декларирует этот диапазон как "без упреждения", тогда как что-то вроде видео-памяти на плате PCI является "упреждающим". В этом разделе мы используем слово регион, чтобы сослаться на то, что общее адресное пространство ввода/вывода является отображаемым на память или на порты.

 

Интерфейсная плата сообщает размер и текущее расположение своих регионов используя регистры конфигурации, эти шесть 32-х разрядных регистров показаны на Рисунке 12-2, их символические имена от PCI_BASE_ADDRESS_0 до PCI_BASE_ADDRESS_5. Так как пространство ввода/вывода, определяемое PCI, является 32-х разрядным адресным пространством, для памяти и ввода/вывода имеет смысл использовать тот же интерфейс конфигурации. Если устройство использует 64-х разрядную шину адреса, оно может декларировать регионы в 64-х разрядном пространстве памяти с помощью двух последовательных регистров PCI_BASE_ADDRESS для каждого региона, младшими битами вперёд. Одно устройство может предлагать одновременно и 32-х разрядные и 64-х разрядные регионы.

 

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

 

unsigned long pci_resource_start(struct pci_dev *dev, int bar);

Функция возвращает первый адрес (адрес памяти или номер порта ввода/вывода), связанный с одним из шести регионов ввода/вывода PCI. Регион выбирается целым числом bar (регистр базового адреса) в диапазоне 0 - 5 (включительно).

 

unsigned long pci_resource_end(struct pci_dev *dev, int bar);

Функция возвращает последний адрес, который является частью региона ввода/вывода значения bar. Заметим, что это последний используемый адрес, а не первый адрес после этого региона.

 

unsigned long pci_resource_flags(struct pci_dev *dev, int bar);

Эта функция возвращает флаги, связанные с этим ресурсом.

 

Флаги ресурсов, используемые для определения некоторых особенностей отдельного ресурса. Для ресурсов PCI, связанных регионами ввода/вывода PCI, эта информация извлекается из регистров базовых адресов, но может прийти из других мест для ресурсов, не связанных с PCI устройствами.

 

Все ресурсные флаги определены в <linux/ioport.h>; наиболее важными являются:

 

IORESOURCE_IO

IORESOURCE_MEM

Если связанный регион ввода/вывода существует, один и только один из этих флагов установлен.

 

IORESOURCE_PREFETCH

IORESOURCE_READONLY

Эти флаги определяют, является ли область памяти упреждающей и/или защищённой от записи. Последний флаг для ресурсов PCI никогда не устанавливается.

 

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

PCI прерывания

Что касается прерывания, PCI прост в обращении. Ко времени загрузки Linux встроенное программное обеспечение компьютера уже присвоило уникальный номер прерывания устройству и драйверу просто необходимо его использовать. Номер прерывания хранится в регистре конфигурации 60 (PCI_INTERRUPT_LINE), который имеет размер в один байт. Это позволяет иметь 256 линий прерываний, но фактический предел зависит от используемого процессора. Драйверу нет необходимости беспокоиться о проверке номера прерывания, так как значение, найденное в PCI_INTERRUPT_LINE, гарантированно будет правильным.

 

Если устройство не поддерживает прерывания, регистр 61 (PCI_INTERRUPT_PIN) равен 0; в противном случае он не равен нулю. Однако, поскольку драйвер знает, является ли его устройство управляемым прерыванием или нет, читать PCI_INTERRUPT_PIN обычно необходимости нет.

 

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

 

result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);

if (result) {

    /* имеем дело с ошибкой */

}

 

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

 

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

 

Регистр конфигурации, доступный только для чтения и расположенный по PCI_INTERRUPT_PIN, используется, чтобы сообщить компьютеру, какой единственный контакт действительно используются. Стоит вспомнить, что каждая плата устройства может содержать до восьми устройств; каждое устройство использует один контакт прерывания и сообщает о нём в собственном регистре конфигурации. Разные устройства на одной плате могут использовать разные контакты прерывания или совместно использовать одно.

 

Регистр PCI_INTERRUPT_LINE, с другой стороны, является читаемым/записываемым. Когда компьютер загружается, встроенное программное обеспечение сканирует PCI устройства и устанавливает этот регистр для каждого устройства в соответствии с тем, какой контакт прерывания подключен к этому PCI слоту. Значение присваивается встроенным программным обеспечением, потому что только оно знает, как материнская плата маршрутизирует разные контакты прерывания в процессор. Однако, для драйвера устройства регистр PCI_INTERRUPT_LINE является только читаемым. Интересно, что последние версии ядра Linux при некоторых обстоятельствах могут назначить линии прерывания не прибегая к BIOS.

Аппаратные абстракции

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

 

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

 

Таким образом, соответствующая структура для доступа к конфигурационному регистру включает в себя только два поля:

 

struct pci_ops {

    int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);

    int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);

};

 

Структура определена в <linux/pci.h> и используется в drivers/pci/pci.c, где определяются фактические функции для общего доступа.

 

Эти две функции, которые воздействуют на конфигурационное пространство PCI, имеют больше накладных расходов, чем разыменование указателя; они используют каскадные указатели связи из-за высокой объектной ориентированности кода, но накладные расходы не является проблемой в операциях, которые выполняются довольно редко и никогда не являются критичными к скорости. Фактическая реализация из pci_read_config_byte(dev, where, val), например, преобразуется в:

 

dev->bus->ops->read(bus, devfn, where, 8, val);

 

Разные шины PCI в системе обнаруживаются при загрузке системы и вот тогда создаются объекты struct pci_bus и связываются с их функциями, в том числе, поле ops.

 

Реализация аппаратной абстракции через структуры данных "аппаратные операции" является типичной в ядре Linux. Одним из важных примеров является структура данных struct alpha_machine_vector. Она определена в <asm-alpha/machvec.h> и заботится обо всём, что может изменяться между разными компьютерами на основе Alpha.

Предыдущая  Содержание  Следующая