Search code examples
c++iokitipadosdriverkit

How to fill up an IOBufferMemoryDescriptor with data


I'm using DriverKit in my iPadOS.

I'm trying to create a IOBufferMemoryDescriptor and add the data I've got in an OSData object.

I'm creating the IOBufferMemoryDescriptor with:

    ret = ivars->interface->CreateIOBuffer(kIOMemoryDirectionOut,
                                           length,
                                           &myBuffer);

Although I think it can be created with this too:

    IOBufferMemoryDescriptor::Create(kIOMemoryDirectionOut, length, 0, &mybuffer);

How do I add the data from my OSData object. I've seen examples of:

IOMemoryDescriptor *buffer = IOMemoryDescriptor::withAddress((void*)firmwareData->getBytesNoCopy(), firmwareData->getLength(), kIODirectionOut);

(from How to use deviceRequest of IOUSBHostDevice / IOUSBHostInterface? )

But that method doesn't seem to work in DriverKit.

I've also tried Mapping the Buffer:

    uint64_t bufferAddress;
    uint64_t bufferLength;
    ret = myBuffer->Map(0, 0, 0, 0, &bufferAddress, &bufferLength);

and then copying the data with:

    memcpy(reinterpret_cast<void*>(bufferAddress), inputOsData->getBytesNoCopy(), length);

But this would always make the driver to crash when tries to run the memcpy

Thanks


Solution

  • Update: On first reading I missed that you used kIOMemoryDirectionOut. In my experience, this doesn't seem to work reliably for buffer memory descriptors, probably because write-only mapping isn't available on most CPU architectures. Use kIOMemoryDirectionInOut.

    Main answer:

    To obtain a pointer to the IOBufferMemoryDescriptor's internal buffer, you need to use the GetAddressRange method. The returned IOAddressSegment struct's address field can then be reinterpret_cast to void* and filled with memcpy or similar.

    The API is rather clumsy, so I recommend wrapping it into a helper function. I have something like this:

    djt_buffer djt_iobmd_get_byte_range(IOBufferMemoryDescriptor* buffer)
    {
        IOAddressSegment range = {};
        kern_return_t ret = buffer->GetAddressRange(&range);
        if (ret != kIOReturnSuccess)
            return djt_buffer{};
    
        return djt_buffer { reinterpret_cast<void*>(range.address), range.length };
    }
    

    where djt_buffer is defined as:

    struct djt_buffer
    {
        void* bytes;
        size_t size;
    };
    

    Note that the CreateIOBuffer method of creating the buffer is preferable when you're going to use the buffer for USB I/O, as it's supposed to prevent the use of bounce buffers. The generic IOBufferMemoryDescriptor::Create() variant is fine for other purposes.

    Note also that every memory descriptor creation, every call to GetAddressRange or GetLength, etc. requires a round trip to the kernel, so I recommend re-using/pooling descriptors instead of destroying/recreating them for every IO operation. You can also cache the address pointer and length of each descriptor in your driver.