📜 ⬆️ ⬇️

One step closer. Working with the device's PCI registers in IOKit

It is time to continue our journey into the wilds of system programming. Today we will go even deeper and talk about the implementation of work with device registers and other basic IO operations in IOKit.



Meet - IOPCIDevice


As you probably remember, in my first article! - (link) I told you that IOKit is probably one of the few object-oriented frameworks for creating drivers. According to the OOP paradigm, any entity in IOKit, as well as the functions that belong to it, turn into a separate class. And so, IOPCIDevice is a class that encapsulates methods for working with a PCI device.
')

This class allows you to:


As I wrote earlier, the pointer to the IOPCIDevice instance is passed to the start method (IOService * provider) of our driver, and the desired PCI device is set in the Info.plist driver file using the corresponding Vendor / Device ID.


So, let's look at an example:
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 );
// ...
}


This code-snippet is a piece of code from my old RealtekR1000 driver (like the other examples in this article), which, incidentally, is fairly stable and clear.


A couple of explanations of what happens in this example:

  1. DbgPrint is a macro wrapper around the IOLog function that allows you to write text messages from our driver to the system log. This is the oldest and most reliable way to debug drivers, unfortunately. You will probably not be surprised, but you can view the system log messages in real-time mode by typing the following lines in the console: tail –f /var/log/system.log
  2. pciDev is a member of the class, a pointer to an IOPCIDevice. Using the OSDynamicCast macro, we perform a downward conversion (from IOService down the inheritance hierarchy) to a pointer to an IOPCIDevice (yes, you correctly noted, IOPCIDevice is like any other nab in the system inherited from IOService).
  3. Then we try to start our base class - IOService.
  4. The following lines increase the reference count of the instance class and open it up for use. Remember to call IOPCIDevice :: close () and IOPCIDevice :: release () to prevent resource leaks.


As you can see, everything is simple. The following lines include the bus master mode for the device (the same Bus Master that allows DMA transactions to the device controller), and also activates the Memory Mapped and IO Mapped device registers:
pciDev-> setBusMasterEnable ( true );
pciDev-> setIOEnable ( true );
pciDev-> setMemoryEnable ( true );


It’s pretty trivial :) But there’s more, you can read the Vendor and Device ID of the device from the PCI configuration address space:
vendorId = pciDev-> configRead16 (0);
deviceId = pciDev-> configRead16 (2);



Recording happens in a similar way, but you will hardly need to resort to it, since IOPCIDevice has everything you need :)


IO ports, how to tame?


If your device uses IO Mapped registers, then you can resort to one of two existing port access methods.

#include <architecture / i386 / pio.h>


Work with ports is as follows:
outb (0x295, 0x20);
UInt8 portV = inb (0x296);


The second method is based on calling the ioRead8 / ioRead16 / ioRead32 and ioWrite8 / ioWrite16 / ioWrite32 methods of the IOPCIDevice instance, i.e. in our case we will have the equivalent code:
pciDev-> ioWrite8 (0x295, 0x20);
UInt8 portV = pciDev-> ioRead8 (0x296);


Memory Mapped Registers


In order to access the MMR device you need to do a little more work. Before I explain the short algorithm for how the MMR access method works, I want to introduce you to another new class, namely IOMemoryMap. In my previous article on DMA! - (link), I mentioned that the physical memory address, where the device registers are mapped, is stored in one of the BAR registers of the PCI configuration space. It would seem that the scheme should be as follows: read this address and use a special API to map physical memory to virtual memory in order to gain access to device registers from the driver (in Mac OS X, you cannot directly access physical memory, even in the kernel). Yes, in general, this is true for other operating systems, but not for Mac OS X. IOKit uses IOMemoryMap, a wrapper class over a section of physical memory that has been mapped into a virtual address space.


So. An approximate algorithm for accessing the MMR device is as follows:


Voila, everything is ready. It remains only to read / write memory with the desired offset.


Arrived


That's all, now you have a basic concept of how to work with device registers in IOKit. In the next article we will discuss how to work with DMA and interrupts, as this is a fairly extensive topic that requires a lot of explanation. Comments, corrections and additions are welcome;)

Source: https://habr.com/ru/post/38144/


All Articles