c++macosiokitdriverkitmacos-system-extension

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


Solution

  • 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).