Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to memory-map a PCI BAR using PCIDriverKit?

How to memory-map a PCI Base Address Register (BAR) from a PCIDriverKit driver (DEXT) to a userspace application?

Memory-mapping from a driver extension to an application can be accomplished by implementing the IOUserClient::CopyClientMemoryForType in the user client subclass (on the driver side) and then calling IOConnectMapMemory64 (from the user-space application side). This has been very nicely and thoroughly explained in this related answer.

The only missing bit is getting an IOMemoryDescriptor corresponding to the desired PCI BAR in order to return it from the CopyClientMemoryForType implementation.

Sample code

Asked another way, given the following simplified code, what would be the implementation of imaginaryFunctionWhichReturnsTheBARBuffer?

kern_return_t IMPL(MyUserClient, CopyClientMemoryForType) //(uint64_t type, uint64_t *options, IOMemoryDescriptor **memory)
{
    IOMemoryDescriptor* buffer = nullptr;
    
    imaginaryFunctionWhichReturnsTheBARBuffer(ivars->pciDevice /* IOPCIDevice */, kPCIMemoryRangeBAR0, &buffer);

    *memory = buffer;

    return kIOReturnSuccess;
}

In the previous code ivars->pciDevice refers to a ready-to-use IOPCIDevice (e.g.: it has been succesfully matched, opened and configured according to latest best practices).

This means it's already possible to use the various configuration and memory read/write methods to access explicit offsets from the desired PCI BAR memory. What's missing (or unclear) is how to use those APIs (or equivalent ones) to map the entire buffer corresponding to a PCI BAR to a user-space application.

Random notes that might or might not be relevant

  • This answer from the related question: How to allocate memory in a DriverKit system extension and map it to another process? contains the following quote:

    [...] The returned memory descriptor need not be an IOBufferMemoryDescriptor as I've used in the example, it can also be a PCI BAR or whatever.

    This is exactly what I want to do, so at least it sounds like it should be possible. The only remaining question is how to implement it.

  • A similar question has been posted on the Apple forums and even though it hasn't received any answers yet (as of the time of this writing), it does contain some useful pointers.

    It looks like there is a private function in PCIDriverKit with the following signature:

    virtual kern_return_t IOPCIDevice::_CopyDeviceMemoryWithIndex(uint64_t memoryIndex, IOMemoryDescriptor** memory, IOService* forClient)
    

    It's hard to tell what it's supposed to do exactly (since it is undocumented), but the signature does seem to match with the function I'm looking for. However, trying to use it always results in an error code (similar to the one reported in the original forum post). The error code seems to be different depending on which argument is passed as IOService *forClient).

    Another good point from that post is that there is a getDeviceMemoryWithIndex available to kernel extensions (KEXT) which could be used to accomplish what we need (if the driver was implemented as a PCI kernel extension which seems to be deprecated now).

    However, this function doesn't seem to be available to driver extensions (DEXT). So another question of framing this question could be: What's the equivalent getDeviceMemoryWithIndex for PCIDriverKit driver extensions?

like image 341
vially Avatar asked Nov 12 '21 17:11

vially


People also ask

How does the PCI bus protocol map memory to the system address?

Now, we have arrived in the core of our discussion: how does the PCI bus protocol map PCI devices memory to the system address map? The mapping is accomplished by using a set of PCI device registers called BAR (base address register).

How is PCI card memory allocated to the operating system?

The PCI card manufacturer will write in each BAR field how much memory it wants the Operating System to allocate, and each BAR field will also specify if it wants this allocated memory to use Memory-mapped IO or Port-mapped IO.

What is relocatable PCI device memory?

PCI device memory is said to be relocatable in the system address map, because you can change the “base” address (start address) of the PCI device memory in the system address map by changing the contents of the BARs/XROMBAR. It works like this: Different systems can have different main memory (RAM) size.

What are the hardcoded memory ranges used by PCI devices?

Among these “hardcoded” memory ranges are the memory ranges used by advanced programmable interrupt controller (APIC) and the flash memory which stores the BIOS/UEFI code. These hardcoded memory ranges cannot be used by PCI devices at all.


Video Answer


1 Answers

Turns out IOPCIDevice::_CopyDeviceMemoryWithIndex was indeed the function needed to implement this (but the fact that it's private is still an inconvenient).

Sample code

Bellow is some sample code showing how this could be implemented (the code uses MyDriver for the driver class name and MyDriverUserClient for the user client).

Relevant sections from MyDriver.cpp implementation:

struct MyDriver_IVars {
    IOPCIDevice* pciDevice = nullptr;
};

// MyDriver::init/free/Start/Stop/NewUserClient implementation ommited for brevity

IOMemoryDescriptor* MyDriver::copyBarMemory(uint8_t barIndex)
{
    IOMemoryDescriptor* memory;
    uint8_t barMemoryIndex, barMemoryType;
    uint64_t barMemorySize;

    // Warning: error handling is omitted for brevity
    ivars->pciDevice->GetBARInfo(barIndex, &barMemoryIndex, &barMemorySize, &barMemoryType);
    ivars->pciDevice->_CopyDeviceMemoryWithIndex(barMemoryIndex, &memory, this);

    return memory;
}

Relevant sections from MyDriverUserClient.cpp implementation:

struct MyDriverUserClient_IVars {
    MyDriver* myDriver = nullptr;
};

// MyDriverUserClient::init/free/Start/Stop implementation ommited for brevity

kern_return_t
IMPL(MyDriverUserClient, CopyClientMemoryForType) //(uint64_t type, uint64_t *options, IOMemoryDescriptor **memory)
{
    *memory = ivars->myDriver->copyBARMemory(kPCIMemoryRangeBAR0);

    return kIOReturnSuccess;
}

Additional Resources

A complete implementation that uses this pattern can be found in the ivshmem.dext project (which implements a macOS driver for IVSHMEM).

like image 97
vially Avatar answered Oct 24 '22 17:10

vially