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?
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.