Содержание


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

Comments

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

Подключение к линии прерывания

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

Установка (связывание) обработчиков прерываний выполняется в ходе инициализации PCI-устройства в модуле. Техника обработки прерываний и написание обработчиков для них будут рассматриваться в отдельной статье. Сейчас мы сконцентрируемся на важной особенности, так как при установке обработчика прерывания для PCI устройства необходимо определить используемую им линию IRQ, с помощью функции, объявленной в файле <linux/interrupt.h>.

int request_irq( unsigned int irq, irq_handler_t handler, unsigned long flags,
                 const char *name, void *dev );

Тип irq_handler_t— это фиксированный в ядре тип функции обработчика прерываний:

typedef irqreturn_t (*irq_handler_t)( int, void* );

Параметр handler— имя функции, вызываемой при возбуждении аппаратного прерывания. Другие параметры вызова request_irq() детализируют дальнейшую обработку прерывания, например, значение IRQF_SHARED в качестве flags указывает на то, что эта линия IRQ может разделяться между несколькими устройствами, что характерно для PCI. Детальный разбор параметров будет выполнен позже, так как в данный момент важно определить, откуда берётся параметр irq— номер линии IRQ.

В устройствах шины ISA в поле первого параметра указывалось фиксированное значение, соответствующее линии, установленной механическими переключателями на плате устройства или записанной конфигурационными программами в EPROM память устройства. В устройствах PnP ISA предпринимались попытки проверки различных линий IRQ на принадлежность данному устройству. В современных PCI-устройствах это значение извлекается из области конфигурационных параметров устройства (смещение 0x3C), но делается это не напрямую, а с помощью API ядра для структуры struct pci_dev. И тогда весь процесс регистрации (который очень часто происходит в теле функции probe(), о которой говорилось выше) записывается примерно так:

struct pci_dev *pdev = NULL;
pdev = pci_get_device( MY_PCI_VENDOR_ID, MY_PCI_DEVICE_ID, NULL );
char irq;
pci_read_config_byte( pdev, PCI_INTERRUPT_LINE, &irq );
request_irq( irq, ... );

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

Отображение памяти

Адресные регионы PCI-устройства (которых может быть до 6-ти: bar=0...5) извлекаются из конфигурационной области вызовами PCI API:

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

Это адреса шины, которые (в зависимости от архитектуры) необходимо преобразовать в виртуальные (логические) адреса, используемыми в коде адресных пространств и ядра и пользователя (<asm/io.h>):

unsigned long virt_to_bus( volatile void *address );
void *bus_to_virt( unsigned long address );
unsigned long virt_to_phys( volatile void *address );
void *phys_to_virt( unsigned long address );

Большинство PCI-устройств отображают свои управляющие регистры на адреса памяти, и высокопроизводительные приложения предпочитают иметь прямой доступ к таким регистрам, вместо вызовов ioctl() для выполнения этой работы. Отображение устройства означает связывание диапазона адресов пользовательского пространства с памятью устройства. Когда программа читает или записывает в заданном диапазоне адресов, она на самом деле обращается к устройству. Существенным ограничением отображения памяти является то, что ядро может управлять виртуальными адресами только на уровне таблиц страниц, поэтому отображаемая область должна быть кратной размеру страницы RAM (PAGE_SIZE) и находиться в физической памяти, начиная с границы кратной PAGE_SIZE. Если рассмотреть адрес памяти (виртуальный или физический), то он делится на номер страницы и смещение внутри этой страницы, например, если используются страницы по 4096 байт, 12 младших значащих бит являются смещением, а остальные, старшие биты, указывают номер страницы. Если отказаться от смещения и сдвинуть оставшуюся часть адреса вправо, результат называют номером страничного блока (page frame number— PFN). Сдвиг битов для конвертации адреса в номер страничного блока является довольно распространённой операцией, так существует макрос PAGE_SHIFT, сообщающий на сколько бит в текущей архитектуре должно быть выполнено смещение адреса для выполнения преобразования в PFN.

Техника обмена данными

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

Более эффективным будет способ отображения (операция mmap()) региона ввода-вывода устройства в страницы RAM, с которыми программный код модуля может работать напрямую (читать и писать данные). Так организована работа, например, видеоадаптеров.

Но и этот способ не подходит для высокопроизводительных устройств, так как процессор должен постоянно копировать большие объёмы данных между буферами в RAM. Обмен большинства PCI-устройств организован на использовании механизма DMA (Direct Memory Access). Передача данных по DMA организуется на аппаратном уровне (только для устройств PCI, умеющих самостоятельно вести такой обмен) и выполняется, например, когда программа запрашивает данные через функцию read() в следующем порядке:

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

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

Заключение

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


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


Похожие темы


Комментарии

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

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