понедельник, 11 апреля 2011 г.

12.7. Внешние шины


Внешние шины 
Одной из самых последних записей в области интерфейса шин является целый класс внешний шин. Он включает в себя USB, FireWire, и IEEE1284 (внешняя шина на основе параллельного порта). Эти интерфейсы немного похожи на старые и не очень внешние технологии, такие как PCMCIA/CardBus и даже SCSI.
Концептуально эти шины не являются ни полнофункциональными интерфейсными шинами (какой является PCI), ни молчаливыми каналами связи (такими, как последовательные порты). Трудно классифицировать программное обеспечение, которому необходимо использовать их возможности, как это обычно делается делением на два уровня: драйвер для аппаратного контроллера (подобно драйверам для адаптеров PCI SCSI или PCI контроллеров, представленных в разделе "Интерфейс PCI") и драйвер для определённого "клиентского" устройства (подобно тому, как sd.c обрабатывает обычные диски SCSI и так называемые PCI драйверы, имеющие дело с картами, подключаемыми к шине).


12.7. External Buses

12.7. External Buses

One of the most recent entries in the field of interface buses is the whole class of external buses. This includes USB, FireWire, and IEEE1284 (parallel-port-based external bus). These interfaces are somewhat similar to older and not-so-external technology, such as PCMCIA/CardBus and even SCSI.

Conceptually, these buses are neither full-featured interface buses (like PCI is) nor dumb communication channels (like the serial ports are). It's hard to classify the software that is needed to exploit their features, as it's usually split into two levels: the driver for the hardware controller (like drivers for PCI SCSI adaptors or PCI controllers introduced in the Section 12.1) and the driver for the specific "client" device (like sd.c handles generic SCSI disks and so-called PCI drivers deal with cards plugged in the bus).

12.6. NuBus


NuBus


Другой интересной, но почти забытой интерфейсной шиной является NuBus. Она существует на старых компьютерах Mac (тех, которые используют семейство процессоров M68k).

Все такие шины являются отображёнными на память (как и всё с M68k) и такие устройства адресуются только географически. Это хорошо и типично для Apple, как что много более старый Apple II уже имел аналогичную схему шины. Что плохо, так это то, что почти невозможно найти документацию по Nubus, благодаря политики закрытия всего, которой Apple всегда следовал со своими компьютерами Mac (и в отличие от предыдущих Apple II, исходный код которого и схемы были доступны за небольшую плату).

Файл drivers/nubus/nubus.c включает в себя почти всё, что мы знаем об этой шине и это интересно почитать; он показывает, насколько сложное реверсивное проектирование должны были сделать разработчики.

12.6. NuBus

12.6. NuBus

Another interesting, but nearly forgotten, interface bus is NuBus. It is found on older Mac computers (those with the M68k family of CPUs).

All of the bus is memory-mapped (like everything with the M68k), and the devices are only geographically addressed. This is good and typical of Apple, as the much older Apple II already had a similar bus layout. What is bad is that it's almost impossible to find documentation on NuBus, due to the close-everything policy Apple has always followed with its Mac computers (and unlike the previous Apple
II, whose source code and schematics were available at little cost).

The file drivers/nubus/nubus.c includes almost everything we know about this bus, and it's interesting reading; it shows how much hard reverse engineering developers had to do.

12.5. SBus


SBus


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

SBus является вполне современной разработкой, хотя она была в ходу в течение долгого времени. Она предназначена для процессорно-независимых (хотя её используют только компьютеры SPARC) и оптимизирована для периферийных плат ввода/вывода. Другими словами, вы не можете подключить дополнительную оперативную память в слоты SBus (платы расширения памяти уже давно забыты даже в мире ISA, а PCI не поддерживает их совсем). Такая оптимизация предназначена для упрощения разработки и аппаратных средств и системного программного обеспечения за счёт некоторого дополнительного усложнения в материнской плате.

Такое смещение в сторону ввода/вывода приводит к использованию в периферии виртуальных адресов для передачи данных, минуя, таким образом, необходимость выделять непрерывный буфер DMA. Материнская плата отвечает за декодирование виртуальных адресов и связи их с физическими адресами. Это требует подключения к шине MMU (memory management unit, блок управления памяти); набор микросхем, отвечающих за эту задачу, называется IOMMU. Хотя это что-то более сложное, чем использование на интерфейсной шине физических адресов, эта конструкция существенно упрощается тем фактом, что процессоры SPARC всегда разрабатывались с поддержкой отдельного от основного ядра процессора ядра MMU (физически или, по крайней мере, концептуально). Собственно, этот конструкторский выбор является общим для других разработок мощных процессоров и выгодно полный. Ещё одной особенностью этой шины является то, что платы устройств используют обширную географическую адресацию, так что нет необходимости реализации дешифратора адреса в каждой периферии или решения конфликтов адресации.

Периферия SBus использует язык Форт в их ПЗУ для самостоятельной инициализации. Форт был выбран потому, что интерпретатор является простым и, следовательно, может быть легко реализован в прошивке любой компьютерной системы. Кроме того, спецификация SBus описывает процесс загрузки, так что совместимые устройства ввода/вывода легко устанавливаются в систему и распознаются во время загрузки системы. Это было большим шагом для поддержки мультиплатформенных устройств; это совершенно другой от ПК мир, где мы привыкли использовать ISA. Однако она не стала успешной по ряду коммерческих причин.

Хотя текущие версии ядра предлагают совершенно полнофункциональную поддержку устройств SBus, эта шина используется в наши дни так мало, что не стоит подробного описания здесь. Заинтересованные читатели могут взглянуть на исходные файлы в arch/sparc/kernel и arch/sparc/mm.

12.5. SBus

12.5. SBus

While most computers nowadays are equipped with a PCI or ISA interface bus, most older SPARC-based workstations use SBus to connect their peripherals.
SBus is quite an advanced design, although it has been around for a long time. It is meant to be processor independent (even though only SPARC computers use it) and is optimized for I/O peripheral boards. In other words, you can't plug additional RAM into SBus slots (RAM expansion boards have long been forgotten even in the ISA world, and PCI does not support them either). This optimization is meant to simplify the design of both hardware devices and system software, at the expense of some additional complexity in the motherboard.

This I/O bias of the bus results in peripherals using virtual addresses to transfer data, thus bypassing the need to allocate a contiguous DMA buffer. The motherboard is responsible for decoding the virtual addresses and mapping them to physical addresses. This requires attaching an MMU (memory management unit) to the bus; the chipset in charge of the task is called IOMMU. Although somehow more complex than using physical addresses on the interface bus, this design is greatly simplified by the fact that SPARC processors have always been designed by keeping the MMU core separate from the CPU core (either physically or at least conceptually). Actually, this design choice is shared by other smart processor designs and is beneficial overall. Another feature of this bus is that device boards exploit massive geographical addressing, so there's no need to implement an address decoder in every peripheral or to deal with address conflicts.

SBus peripherals use the Forth language in their PROMs to initialize themselves. Forth was chosen because the interpreter is lightweight and, therefore, can be easily implemented in the firmware of any computer system. In addition, the SBus specification outlines the boot process, so that compliant I/O devices fit easily into the system and are recognized at system boot. This was a great step to support multi-platform devices; it's a completely different world from the PC-centric ISA stuff we were used to. However, it didn't succeed for a variety of commercial reasons.

Although current kernel versions offer quite full-featured support for SBus devices, the bus is used so little nowadays that it's not worth covering in detail here. Interested readers can look at source files in arch/sparc/kernel and arch/sparc/mm.

12.4. Другие шины ПК


Другие шины ПК


PCI и ISA являются наиболее часто используемыми периферийными интерфейсами в мире персональных компьютеров, но они не являются единственными. Вот краткий обзор особенностей других шин, найденных на рынке ПК.
MCA
Micro Channel Architecture (MCA, микроканальная архитектура) является стандартом IBM, используемым в компьютерах PS/2 и некоторых ноутбуках. На аппаратном уровне Micro Channel имеет больше функций, чем ISA. Он поддерживает multimaster DMA (многоабонентский ПДП), 32-х разрядные адреса и линии передачи данных, разделяемые линии прерываний, а также географическую адресацию для доступа к находящимся на плате регистрам конфигурации. Такие регистры называются Programmable Option Select (POS, выбор программируемых опций), но они не имеют всех возможностей регистров PCI. Поддержка в Linux для Micro Channel включает в себя функции, которые экспортируются для модулей.

Драйвер устройства может прочитать целое значение MCA_bus, чтобы увидеть, работает ли он на компьютере с Micro Channel. В качестве макроса препроцессора так же определён макрос MCA_bus__is_a_macro. Если MCA_bus__is_a_macro не определён, то MCA_bus, являющееся целой переменной, экспортируется в код модуля. Оба символа, MCA_BUS и MCA_bus__is_a_macro определены в .
EISA
Шина Extended ISA (EISA, расширенная ISA) является 32-х разрядным расширением ISA, с совместимым интерфейсным разъёмом; платы устройств ISA могут быть подключены к разъёму EISA. Дополнительные провода разведены под контактами ISA (отдельный небольшой разъём).

Подобно PCI и MCA, шина EISA разработана для подключения безджамперного устройства и она имеет те же возможности, как MCA: 32-х разрядная адресная и линия передачи данных, многоабонентский DMA и разделяемые линии прерываний. Устройства EISA настраиваются программным обеспечением, но им не требуется какая-либо особенная поддержка от операционной системы. Драйверы EISA уже существуют в ядре Linux для сетевых устройств и контроллеров SCSI.

Драйвер EISA проверяет значение EISA_bus, чтобы определить, поддерживает ли данный компьютер шину EISA. Как и MCA_bus, EISA_bus является либо макросом, либо переменной, в зависимости, определён ли EISA_bus__is_a_macro. Оба символа определены в .

Ядро имеет полную поддержку EISA для устройств с sysfs и функциональностью управления ресурсами. Это находится в каталоге drivers/eisa.
VLB
Ещё одним расширением для ISA является интерфейсная шина VESA Local Bus (VLB), которая расширяет разъёмы ISA, добавляя третий продольный слот. Устройство можно подключить только в этот дополнительный разъём (без подключения двух связанных с ним разъёмов ISA), потому что слот VLB дублирует все важные сигналы от разъёмов ISA. Такие "автономные" периферийные устройства VLB не использующие слот ISA редки, так как большинству устройств необходимо достичь задней панели, чтобы их внешние разъёмы стали доступны.

Шина VESA является гораздо более ограниченной по своим возможностям, чем шины EISA, MCA и PCI и исчезает с рынка. Не существует специальной поддержки в ядре для VLB. Однако, оба драйвера, Lance Ethernet и IDE дисков, в Linux 2.0 могут иметь дело с VLB версиями своих устройств.

12.4. Other PC Buses

12.4. Other PC Buses

PCI and ISA are the most commonly used peripheral interfaces in the PC world, but they aren't the only ones. Here's a summary of the features of other buses found in the PC market.

12.4.1. MCA

Micro Channel Architecture (MCA) is an IBM standard used in PS/2 computers and some laptops. At the hardware level, Micro Channel has more features than ISA. It supports multimaster DMA, 32-bit address and data lines, shared interrupt lines, and geographical addressing to access per-board configuration registers. Such registers are called Programmable Option Select (POS), but they don't have all the features of the PCI registers. Linux support for Micro Channel includes functions that are exported to modules.

A device driver can read the integer value MCA_bus to see if it is running on a Micro Channel computer. If the symbol is a preprocessor macro, the macro MCA_bus_ _is_a_macro is defined as well. If MCA_bus_ _is_a_macro is undefined, then MCA_bus is an integer variable exported to modularized code. Both MCA_BUS and MCA_bus_ _is_a_macro are defined in .

12.4.2. EISA

The Extended ISA (EISA) bus is a 32-bit extension to ISA, with a compatible interface connector; ISA device boards can be plugged into an EISA connector. The additional wires are routed under the ISA contacts.

Like PCI and MCA, the EISA bus is designed to host jumperless devices, and it has the same features as MCA: 32-bit address and data lines, multimaster DMA, and shared interrupt lines. EISA devices are configured by software, but they don't need any particular operating system support. EISA drivers already exist in the Linux kernel for Ethernet devices and SCSI controllers.

An EISA driver checks the value EISA_bus to determine if the host computer carries an EISA bus. Like MCA_bus, EISA_bus is either a macro or a variable, depending on whether EISA_bus_ _is_a_macro is defined. Both symbols are defined in .

The kernel has full EISA support for devices with sysfs and resource management functionality. This is located in the drivers/eisa directory.

12.4.3. VLB

Another extension to ISA is the VESA Local Bus (VLB) interface bus, which extends the ISA connectors by adding a third lengthwise slot. A device can just plug into this extra connector (without plugging in the two associated ISA connectors), because the VLB slot duplicates all important signals from the ISA connectors. Such "standalone" VLB peripherals not using the ISA slot are rare, because most devices need to reach the back panel so that their external connectors are available.

The VESA bus is much more limited in its capabilities than the EISA, MCA, and PCI buses and is disappearing from the market. No special kernel support exists for VLB. However, both the Lance Ethernet driver and the IDE disk driver in Linux 2.0 can deal with VLB versions of their devices.

12.3. PC/104 и PC/104+


PC/104 и PC/104+
В настоящее время в промышленных странах достаточно модны две шинных архитектуры: PC/104 и PC/104 +. Оба являются стандартом в одноплатных компьютерах класса ПК.

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

Электрическая и логическая схема этих двух шин идентична ISA (PC/104) и PCI (PC/104 +), так что программное обеспечение не заметит никакой разницы между обычными шинами настольного компьютера и этими двумя.

12.3. PC/104 and PC/104+

12.3. PC/104 and PC/104+

Currently in the industrial world, two bus architectures are quite fashionable: PC/104 and PC/104+. Both are standard in PC-class single-board computers.
Both standards refer to specific form factors for printed circuit boards, as well as electrical/mechanical specifications for board interconnections. The practical advantage of these buses is that they allow circuit boards to be stacked vertically using a plug-and-socket kind of connector on one side of the device.

The electrical and logical layout of the two buses is identical to ISA (PC/104) and PCI (PC/104+), so software won't notice any difference between the usual desktop buses and these two.

11.2. Взгляд назад: ISA


Взгляд назад: ISA

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

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

С другой стороны, большим недостатком ISA является то, что она тесно связана с архитектурой ПК; интерфейсная шина имеет все ограничения процессора 80286 и причина бесконечной боли системных программистов. Другой большой проблемой с дизайном ISA (унаследованной от оригинального IBM PC) является отсутствие географической адресации, которая привела ко многим проблемам и длительным циклам отключите-переставьте перемычки-подключите-проверьте при добавлении новых устройств. Интересно отметить, что даже на самых старых компьютерах Apple II уже использовалась географическая адресация, и они уже поддерживали безджамперные платы расширения.

Несмотря на свои большие недостатки, ISA всё ещё используется в нескольких неожиданных местах. Например, серия VR41xx процессоров MIPS, используемая в нескольких карманных компьютерах, включает ISA-совместимую шину расширения, как это ни странно. Причиной этих неожиданных использований ISA является крайне низкая стоимость некоторого устаревшего оборудования, такого как сетевые карты на базе 8390, так что процессор с электрическими сигналами ISA может легко использовать ужасные, но дешёвые устройства ПК.
Аппаратные ресурсы
ISA устройство может быть оснащено портами ввода/вывода, областями памяти и линиями прерывания. Даже несмотря на то, что x86 процессоры поддерживают 64 Кб пространство портов ввода/вывода (то есть процессор имеет 16 адресных линий), некоторое старое оборудование ПК декодирует только младшие 10 адресных линий. Это ограничивает используемое адресное пространство до 1024 портов, потому что любой адрес в диапазоне от 1 Кб до 64 Кб ошибочно принимается за более низкий адрес любым устройством, которое декодирует только младшие адресные линии. Некоторая периферия обходит это ограничение отображая только один порт в младший килобайт и используя старшие адресные линии для выбора между разными регистрами устройства. Например, устройство, отображаемое на 0x340, может безопасно использовать порт 0x740, 0xB40 и так далее.

Если количество портов ввода/вывода ограничено, с доступом к памяти всё ещё хуже. Устройство ISA можно использовать только память в диапазоне от 640 Кб до 1 Мб, а также между 15 Мб и 16 Мб для регистра ввода/вывода и управления устройством. Диапазон от 640 Кб до 1 Мб используется BIOS ПК, в VGA-совместимых видеокартах и различными другими устройствами, оставляя мало пространства для новых устройств. Память на 15 Мб, с другой стороны, непосредственно не поддерживает Linux и требуется изменение ядра для такой поддержки, в настоящее время это бесполезный расход времени программирования.

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

Хотя оригинальная спецификация ISA не разрешает разделение прерывания между устройствами, большинство плат устройств позволяют это. (* Проблема с разделением прерывания идёт из электротехники: если устройство, управляющее сигнальной линией неактивно, применяя уровень напряжения с малым сопротивлением, прерывание не может быть общим. Если, с другой стороны, устройство использует нагрузочный резистор для неактивного логического уровня, возможно совместное использование. В настоящее время это норма. Однако, всё ещё есть потенциальный риск потери событий прерывания, так как прерывания ISA запускаются перепадом, вместо срабатывания по уровню. Запускаемые по перепаду прерывания проще для реализации на оборудовании, но не поддаются безопасному совместному использованию.) Совместное использование прерывания на программном уровне описано в разделе "Разделяемые прерывания" в Главе 10.
Программирование ISA
Что же касается программирования, для облегчения доступа к ISA устройствам нет никакой особой помощи в ядре или в BIOS (как есть, например, для PCI). Единственными средствами, которые вы можете использовать, являются регистрации портов ввода/вывода и линий прерывания, описанные в разделе "Установка обработчика прерываний" в Главе 10.

Приёмы программирования, показанные в первой части этой книги, распространяются на ISA устройства; драйвер может проверять порты ввода/вывода, а линия прерывания должна определяться автоматически одним из методов, показанных в разделе "Автоопределение номера  прерывания" в Главе 10.

Вспомогательные функции isa_readb и друзья были кратко представлены в разделе "Использование памяти ввода/вывода" в Главе 9 и сказать о них больше нечего.

Спецификация Plug-and-Play
Некоторые новые платы устройств ISA следуют своеобразным правилам проектирования и требуют специальной последовательности инициализации для упрощения установки и настройки дополнительный интерфейсных плат. Эта спецификация называется plug and play (PnP, включай и работай) и состоит из набора громоздких правил для создания и конфигурирования безджамперных ISA устройств. Устройства PnP реализуют перемещаемые регионы ввода/вывода; BIOS в ПК, отвечающая за перемещение, напоминает PCI.

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

Географическая адресация работает назначая малое целое число, называемое card select number (CSN, номер выбора карты), для каждой периферии PnP в компьютере. Каждое устройство PnP имеет уникальный серийный идентификатор размером 64 бита, который установлен аппаратно в периферийной плате. CSN определение использует уникальный серийный номер, чтобы определить PnP устройства. Но CSN-ы могут быть назначены безопасно только во время загрузки системы, что требует BIOS, осведомлённый о PnP. По этой причине старые компьютеры требуют от пользователя получить и вставить специальную конфигурационную дискету, даже если устройство совместимо с PnP.

Интерфейсные платы, использующие спецификации PnP, сложны на аппаратном уровне. Они гораздо более сложные, чем платы PCI и требуют сложного программного обеспечения. Столкновение с трудностями при установке этих устройств не необычно и даже если установка идёт хорошо, вы-прежнему сталкиваетесь с ограничениями производительности и ограниченным пространством ввода/вывода шины ISA. Гораздо лучше вместо этого установить PCI устройства, когда возможно, и наслаждаться новыми технология.

Если вы заинтересованы в программной конфигурации PnP, вы можете просмотреть drivers/net/3c509.c, чьи зондирующие функции имеют дело с устройствами PnP. Ядро версии 2.6 вобрало много работы в области поддержки устройств PnP, так что по сравнению с предыдущими версиями ядра было убрано много не гибких интерфейсов.

12.2. A Look Back: ISA

12.2. A Look Back: ISA

The ISA bus is quite old in design and is a notoriously poor performer, but it still holds a good part of the market for extension devices. If speed is not important and you want to support old motherboards, an ISA implementation is preferable to PCI. An additional advantage of this old standard is that if you are an electronic hobbyist, you can easily build your own ISA devices, something definitely not possible with PCI.

On the other hand, a great disadvantage of ISA is that it's tightly bound to the PC architecture; the interface bus has all the limitations of the 80286 processor and causes endless pain to system programmers. The other great problem with the ISA design (inherited from the original IBM PC) is the lack of geographical addressing, which has led to many problems and lengthy unplug-rejumper-plug-test cycles to add new devices. It's interesting to note that even the oldest Apple II computers were already exploiting geographical addressing, and they featured jumperless expansion boards.

Despite its great disadvantages, ISA is still used in several unexpected places. For example, the VR41xx series of MIPS processors used in several palmtops features an ISA-compatible expansion bus, strange as it seems. The reason behind these unexpected uses of ISA is the extreme low cost of some legacy hardware, such as 8390-based Ethernet cards, so a CPU with ISA electrical signaling can easily exploit the awful, but cheap, PC devices.

12.2.1. Hardware Resources

An ISA device can be equipped with I/O ports, memory areas, and interrupt lines.
Even though the x86 processors support 64 KB of I/O port memory (i.e., the processor asserts 16 address lines), some old PC hardware decodes only the lowest 10 address lines. This limits the usable address space to 1024 ports, because any address in the range 1 KB to 64 KB is mistaken for a low address by any device that decodes only the low address lines. Some peripherals circumvent this limitation by mapping only one port into the low kilobyte and using the high address lines to select between different device registers. For example, a device mapped at 0x340 can safely use port 0x740, 0xB40, and so on.

If the availability of I/O ports is limited, memory access is still worse. An ISA device can use only the memory range between 640 KB and 1 MB and between 15 MB and 16 MB for I/O register and device control. The 640-KB to 1-MB range is used by the PC BIOS, by VGA-compatible video boards, and by various other devices, leaving little space available for new devices. Memory at 15 MB, on the other hand, is not directly supported by Linux, and hacking the kernel to support it is a waste of programming time nowadays.

The third resource available to ISA device boards is interrupt lines. A limited number of interrupt lines is routed to the ISA bus, and they are shared by all the interface boards. As a result, if devices aren't properly configured, they can find themselves using the same interrupt lines.

Although the original ISA specification doesn't allow interrupt sharing across devices, most device boards allow it.[5] Interrupt sharing at the software level is described in Chapter 10.

[5] The problem with interrupt sharing is a matter of electrical engineering: if a device drives the signal line inactive—by applying a low-impedance voltage level—the interrupt can't be shared. If, on the other hand, the device uses a pull-up resistor to the inactive logic level, sharing is possible. This is the norm nowadays. However, there's still a potential risk of losing interrupt events since ISA interrupts are edge triggered instead of level triggered. Edge-triggered interrupts are easier to implement in hardware but don't lend themselves to safe sharing.

12.2.2. ISA Programming

As far as programming is concerned, there's no specific aid in the kernel or the BIOS to ease access to ISA devices (like there is, for example, for PCI). The only facilities you can use are the registries of I/O ports and IRQ lines, described in Section 10.2.

The programming techniques shown throughout the first part of this book apply to ISA devices; the driver can probe for I/O ports, and the interrupt line must be autodetected with one of the techniques shown in Section 10.2.2.
The helper functions isa_readb and friends have been briefly introduced in Chapter 9, and there's nothing more to say about them.

12.2.3. The Plug-and-Play Specification

Some new ISA device boards follow peculiar design rules and require a special initialization sequence intended to simplify installation and configuration of add-on interface boards. The specification for the design of these boards is called plug and play (PnP) and consists of a cumbersome rule set for building and configuring jumperless ISA devices. PnP devices implement relocatable I/O regions; the PC's BIOS is responsible for the relocation—reminiscent of PCI.
In short, the goal of PnP is to obtain the same flexibility found in PCI devices without changing the underlying electrical interface (the ISA bus). To this end, the specs define a set of device-independent configuration registers and a way to geographically address the interface boards, even though the physical bus doesn't carry per-board (geographical) wiring—every ISA signal line connects to every available slot.

Geographical addressing works by assigning a small integer, called the card select number (CSN), to each PnP peripheral in the computer. Each PnP device features a unique serial identifier, 64 bits wide, that is hardwired into the peripheral board. CSN assignment uses the unique serial number to identify the PnP devices. But the CSNs can be assigned safely only at boot time, which requires the BIOS to be PnP aware. For this reason, old computers require the user to obtain and insert a specific configuration diskette, even if the device is PnP capable.

Interface boards following the PnP specs are complicated at the hardware level. They are much more elaborate than PCI boards and require complex software. It's not unusual to have difficulty installing these devices, and even if the installation goes well, you still face the performance constraints and the limited I/O space of the ISA bus. It's much better to install PCI devices whenever possible and enjoy the new technology instead.

If you are interested in the PnP configuration software, you can browse drivers/net/3c509.c, whose probing function deals with PnP devices. The 2.6 kernel saw a lot of work in the PnP device support area, so a lot of the inflexible interfaces have been cleaned up compared to previous kernel releases.

12.1. Интерфейс PCI


Интерфейс PCI

Хотя многие пользователи компьютеров думают о 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

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

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

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

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-х разрядные передачи данных. Прототипы соответствующих функций в :

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_ является использование символических имён, определённых в . Например, следующая небольшая функция получает идентификатор ревизии устройства, передавая его символическое имя в 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. Эти биты определены в .) Если область память обозначена как "с упреждением", процессор может кэшировать её содержимое и выполнять с ним все виды оптимизации; доступ к памяти без упреждающей выборки, с другой стороны, не может быть оптимизирован, так как каждый доступ может иметь побочные эффекты, так же как и с портами ввода/вывода. Периферия, которая связывает свои регистры управления с диапазоном адресов памяти, декларирует этот диапазон как "без упреждения", тогда как что-то вроде видео-памяти на плате 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 устройствами.

Все ресурсные флаги определены в ; наиболее важными являются:

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);
};

Структура определена в и используется в 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. Она определена в и заботится обо всём, что может изменяться между разными компьютерами на основе Alpha.