I've revised my previous answer to try and collate all of the knowledge in the comments into a single post, for clarity.
Both the parent and the child dexts require special setup, so we'll cover the parent first.
First, you'll need to make sure that you have an IODispatchQueue in your dext. You'll be using this to get notifications about other dexts that are available. Then create a matching dictionary for your other dexts using something like CreateUserClassMatchingDictionary. Then create an IOServiceNotificationDispatchSource with your notification queue and matching dictionary. Then you can create a new OSAction with CreateActionServiceNotificationReady to get a notification when a new service is ready. You'll then need to use your IOServiceNotificationDispatchSource to handle that action. This process should look relatively similar to how you would do something similar in IOKit. Here's a highly simplified code example:
struct ParentDext_IVars
{
IOServiceNotificationDispatchSource* fServiceNotifier;
OSAction* fServiceNotificationAction;
IODispatchQueue* fServiceNotificationQueue;
// ...
};
// ...
ParentDext::Start_Impl(IOService* provider) // Can also use IMPL macro
{
kern_return_t ret = kIOReturnSuccess;
OSDictionaryPtr dict = NULL;
ret = IODispatchQueue::Create("Notify", 0, 0, &ivars->fServiceNotificationQueue);
// Error check
ret = SetDispatchQueue("Notify", ivars->fServiceNotificationQueue);
// Error check
dict = CreateUserClassMatchingDictionary("ChildDext", NULL);
// Check that dict is non-null
ret = IOServiceNotificationDispatchSource::Create(dict, 0, ivars->fServiceNotificationQueue, &ivars->fServiceNotifier);
// Error check
ret = CreateActionServiceNotificationReady(0, &ivars->fServiceNotificationAction);
// Error check
ret = ivars->fServiceNotifier->SetHandler(ivars->fServiceNotificationAction);
// Error check
// The rest of your Start method
// You *must* register this service and all child services, otherwise you will get strange behavior.
RegisterService();
}
From here, you'll need a function to handle your IOServiceNotificationDispatchSource callbacks. Define a function like so in your ParentDext.iig
file:
virtual void ServiceNotificationReady(OSAction* action) TYPE(IOServiceNotificationDispatchSource::ServiceNotificationReady) QUEUENAME(Notify);
Note the QUEUENAME
and how it matches to the queue name we made in the code. The TYPE
macro is also important for denoting that this function is a callback for a IOServiceNotificationDispatchSource::ServiceNotificationReady
action. The CreateActionServiceNotificationReady
function was created because of the name of this function and its macros. Note that if you change the name of the function, that call will also change to CreateActionMyFuncName
.
Implement this function, and you will have access to communicate with other dexts:
void ParentDext::ServiceNotificationReady_Impl(OSAction* action)
{
kern_return_t ret = ivars->fServiceNotifier->DeliverNotifications(^(uint64_t type, IOService* service, uint64_t options)
{
// ...
service->retain();
// ...
ChildDext* childDextInstance = OSDynamicCast(ChildDext, service);
// ...
});
}
There's a couple things you'll need to do in your child dext to make sure that everything will work appropriately, but thankfully it's relatively simple.
Make sure that both dexts have the same IOUserServerName
property so they can see each other.
Add the REMOTE
keyword to your definition of your class in the .iig
like so:
class REMOTE ChildDext: public IOService {
This allows a dext to be called remotely. Otherwise you will see issues with references not casting correctly.
Declare a function in your child dext to receive a call from the parent dext and set a reference, so the child dext can communicate to the parent dext. For this example, it will be called Connect
. The definition in the .iig
:
kern_return_t Connect(IOService* peerService) LOCALONLY;
The implementation is relatively straightforward:
kern_return_t PeerDextB::Connect(IOService* peerService)
{
Log("Connect()");
ivars->peerDext = OSDynamicCast(PeerDextA, peerService);
if (ivars->peerDext == nullptr)
{
Log("Connect() - Peer dext is null.");
return kIOReturnBadArgument;
}
// Retain an instance of the peer dext so it can be accessed later.
ivars->peerDext->retain();
return kIOReturnSuccess;
}
This should complete the requirements to have two dext communicate with each other.
Add this key to both IOKitPersonalities
entries:
<key>IOUserServerOneProcess</key>
<true/>
This is equivalent to IOUserServerOneProcess
- Boolean
- 1
, if you are looking at it in the visual editor.
Note that this key was added as part of macOS 12 Monterey, so you will need to use Monterey to combine dexts in this manner.
- Previously in comments I had mentioned that C-style casting was acceptable. You are likely to have issues if you choose to do this long term. Please stick to
OSDynamicCast
as much as possible. - Calls from parent to child and child to parent follow simple C-style function call parameters. No further special actions are required.
- Make sure that both dexts call
RegisterService()
in their Start
implementations, otherwise they will not be able to find each other.