Pull to refresh

На шаг ближе. Работа с регистрами PCI устройства в IOKit

Reading time4 min
Views5.5K
Настало время продолжить наше путешествие в дебри системного программирования. Сегодня мы опустимся еще глубже и поговорим о реализации работы с регистрами устройства и другими базовыми IO операциями в IOKit.



Встречайте – IOPCIDevice


Как вы наверное помните, в моей первой статье !-(линк) я рассказывал о том, что IOKit – пожалуй один из немногих объектно-ориентированных фреймворков для создания драйверов. Согласно парадигме ООП, любая сущность в IOKit, а также функции которые к ней относятся – оборачиваются в отдельный класс. И так, IOPCIDevice – это класс, который инкапсулирует методы по работе с PCI устройством.


Данный класс позволяет:
  • Получить доступ к чтению/записи данных в конфигурационном адресном пространстве PCI.
  • Получить доступ К IO портам и Memory Mapped Registers устройства.
  • Назначить обработчик прерываний, которые инициирует контроллер устройства.
  • Получить управление механизмами энергосбережения (Power Management), и многими другими полезными прибамбасами (Bus Mastering, etc.).


Как я писал раннее, указатель на инстанс IOPCIDevice передается в метод start(IOService *provider) нашего драйвера, а нужное PCI устройство – задается в Info.plist файле драйвера с помощью соответствующих Vendor/Device ID.


Итак, посмотрим на примере:
bool RealtekR1000::start(IOService *provider)
{
   DbgPrint("[RealtekR1000] RealtekR1000::start(IOService *provider)\n");
   pciDev = OSDynamicCast(IOPCIDevice, provider);
   if (!pciDev)
   {
      DbgPrint("[RealtekR1000] failed to cast provider\n");
      return false;
   }
   if (BaseClass::start(pciDev) == false) return false;
   pciDev->retain();   
   pciDev->open(this);
   // ...
}


Данный code-snippet – это кусочек кода моего старого драйвера RealtekR1000 (как и остальные примеры в этой статье), который впрочем достаточно стабилен и нагляден.


Пару пояснений о том, что происходит в этом примере:

  1. DbgPrint – это макрос-обертка вокруг функции IOLog, которая позволяет записывать в системный лог текстовые сообщения от нашего драйвера. Это самый старый и надежный способ отладки драйверов, к сожалению. Вы наверное не будете удивлены, однако можно просматривать сообщения системного лога в real-time режиме, набрав в консоли следующие строки: tail –f /var/log/system.log
  2. pciDev – это член класса, указатель на IOPCIDevice. С помощью макроса OSDynamicCast мы выполняем нисходящее преобразование (с IOService вниз по иерархии наследования) к указателю на IOPCIDevice (да, вы правильно заметили, IOPCIDevice как и любой другой наб в системе является наследником от IOService).
  3. Затем мы пытаемся стартовать наш базовый класс — IOService.
  4. Следующие строки увеличивают счетчик ссылок инстанса класса и открывают его для использования. Не забывайте вызывать IOPCIDevice::close() и IOPCIDevice::release(), чтобы не допустить утечки ресурсов.


Как видите, все просто. Следующие строки включают для устройства режим хозяина шины (тот самый Bus Master, который позволяет осуществлять DMA транзакции контроллеру устройства), а также активирует Memory Mapped и IO Mapped регистры устройства:
pciDev->setBusMasterEnable(true);
pciDev->setIOEnable(true);
pciDev->setMemoryEnable(true);


Достаточно тривиально :) Но это еще не все, вот так вы можете прочесть Vendor и Device ID устройства из конфигурационного адресного пространства PCI:
vendorId = pciDev->configRead16(0);
deviceId = pciDev->configRead16(2);



Запись происходит подобным образом, но прибегать к ней вам врядли понадобиться, так как IOPCIDevice имеет все необходимое :)


IO порты, как приручить?


Если ваше устройство, использует IO Mapped регистры, то вы можете прибегнуть к одному из двух существующих методов доступа к портам.

#include <architecture/i386/pio.h>


Работа с портами выполняется следующим образом:
outb(0x295, 0x20);
UInt8 portV = inb(0x296);


Второй способ основан на вызове методов ioRead8/ioRead16/ioRead32 и ioWrite8/ioWrite16/ioWrite32 инстанса класса IOPCIDevice, т.е. в нашем случае мы будем иметь эквивалентный код:
pciDev->ioWrite8(0x295, 0x20);
UInt8 portV = pciDev->ioRead8(0x296);


Memory Mapped Registers


Для того, чтобы получить доступ к MMR устройства нужно выполнить немного больше работы. Прежде чем я поясню краткий алгоритм работы метода доступа к MMR, я хочу представить вам еще один новый класс, а именно IOMemoryMap. В своей предыдущей статье о DMA !-(линк), я упомянул, что адрес физической памяти, куда отображены (mapped) регистры устройства, храниться в одном из BAR регистров конфигурационного пространства PCI. Казалось бы, схема должна быть следующая: прочесть данный адрес и с помощью специального API отобразить физическую память в виртуальную, чтобы получить доступ к регистрам устройства из драйвера (в Mac OS X нельзя обращаться напрямую к физической памяти, даже в ядре). Да, в общем случае это справедливо для других операционных систем, но не для Mac OS X. IOKit использует IOMemoryMap – класс-обертку над участком физической памяти, который был отображен в виртуальное адресное пространство.


Итак. Примерный алгоритм доступа к MMR устройства следующий:
  • — С помощью метода IOPCIDevice::mapDeviceMemoryWithRegister получить указатель на объект IOMemoryMap. Если адрес MMR находится в регистре BAR0, то метод mapDeviceMemoryWithRegister следует вызывать с параметром kIOPCIConfigBaseAddress0 и т.д.
  • — Вызвав метод IOMemoryMap::getVirtualAddress мы получим виртуальный адрес, по которому можно получить доступ к регистрам устройства.


Вуаля, все готово. Остается лишь читать/писать память с нужным смещением.


Приехали


Вот и все, теперь вы имеете базовые понятия о том, как работать с регистрами устройства в IOKit. В следующей статье мы с вами обсудим как работать с DMA и прерываниями, так как это достаточно обширная тема, которая требует достаточно много пояснений. Комментарии, исправления и дополнения приветствуются ;)
Tags:
Hubs:
0
Comments0

Articles

Change theme settings