Search code examples
macosgrand-central-dispatchiokitdriverkitlibdispatch

How to use `IOConnectCallAsyncScalarMethod` with a GCD dispatch queue


I am working on some code that uses IOConnectCallAsyncScalarMethod() to get callbacks from a DriverKit extension. The setup is quite heavy, involving spawning a thread, manually creating a CFMachPortRef, adding its CFRunLoopSourceRef to a CFRunLoop and then pumping that run loop.

In order to simplify this code and reduce the risk of race conditions, I would like to get the IOKit callback on a dispatch queue instead. Is there any way to achieve this?


Solution

  • After going back and forth on this unsuccessfully, I finally found the answer in the OS X and iOS Kernel Programming book (page 95, listing 5-15).

    The trick is to use a IONotificationPortRef along with IONotificationPortSetDispatchQueue to set the target dispatch queue. Then to actually have the callback dispatched to that queue, set up an io_async_ref64_t and use it. Here's an outline of what the code would look like:

    // Create a notification port for IOKit service callbacks
    IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
            
    // Run notification callbacks on the desired dispatch queue
    IONotificationPortSetDispatchQueue(self.notificationPort, dispatch_get_main_queue()); 
    
    io_async_ref64_t asyncRef;
       
    asyncRef[kIOAsyncCalloutFuncIndex] = (uint64_t)callback;
    asyncRef[kIOAsyncCalloutRefconIndex] = (uint64_t)(__bridge void *)self;
        
    uint32_t cmd = 0xCAFE; // Method as defined by the service
    kern_return_t error = IOConnectCallAsyncScalarMethod(connection, cmd, IONotificationPortGetMachPort(notificationPort), asyncRef, kIOAsyncCalloutCount, NULL, 0, NULL, NULL);
    

    callback should have this signature: void commandReadyCallback(void *context, IOReturn result). (AKA. IOAsyncCallback0)

    I hope this helps some poor soul in the future.