I am writing a SystemExtension to communicate with a usb-device. My initial plan is to create a class Transfer that allocates the necessary IOMemoryDescriptor, and then pass the interface that I want the Transfer class to communicate with. I would like to have the callback resulting from AsyncIO completing to be made to the Transfer class. If I need to queue up multiple reads I could then create more instances of this class. In the callback completeCallback I would then unpack the data and then submit another read.
I create the Transfer class with OSTypeAlloc(Transfer).
The problem I am facing is that creating the OSAction fails with this stack trace:
0 libsystem_kernel.dylib 0x000000010df950ce __pthread_kill + 10
1 libsystem_pthread.dylib 0x000000010e07cf6a pthread_kill + 152
2 libsystem_c.dylib 0x000000010df2c3a0 abort + 120
3 com.apple.DriverKit 0x000000010dc9124b __assert_rtn + 102
4 com.apple.DriverKit 0x000000010dc91a34 OSCopyOutObjects(IOUserServer_IVars*, IORPCMessageMach*, IORPCMessage*, bool) (.cold.4) + 35
5 com.apple.DriverKit 0x000000010dc7a2ff OSCopyOutObjects(IOUserServer_IVars*, IORPCMessageMach*, IORPCMessage*, bool) + 328
6 com.apple.DriverKit 0x000000010dc79126 InvokeRemote(IOUserServer_IVars*, unsigned long long, IORPC) + 159
7 com.apple.DriverKit 0x000000010dc796b6 OSMetaClassBase::Invoke(IORPC) + 754
8 com.apple.DriverKit 0x000000010dc90048 OSAction::Create_Call(OSObject*, unsigned long long, unsigned long long, unsigned long, OSAction**) + 212
9 com.apple.DriverKit 0x000000010dc7ca15 OSAction::Create(OSObject*, unsigned long long, unsigned long long, unsigned long, OSAction**) + 37
10 sc.example.MyUserUSBInterfaceDriver 0x000000010dc3bffc Transfer::CreateActioncompleteCallback(unsigned long, OSAction**) + 60 (Transfer.iig.cpp:175)
11 sc.example.MyUserUSBInterfaceDriver 0x000000010dc34e6e Transfer::setup(IOUSBHostInterface*, int, int) + 638 (Transfer.cpp:53)
If I instead move the callback to be defined, implemented and created in the class that is instantiated by the system when the usb-device is attached (this class is specified in the plist with key IOUserClass), then creating the OSAction object works fine.
The call to IOUSBHostInterface::Open is made from the IOUserClass passing a pointer to the IOUserClass as the first argument to Open. Should it be ok to do this? Or is it required that the IOService object is also the same object that receives the callbacks from AsyncIO.
class Transfer : public OSObject {
public:
bool init() override;
void free() override;
bool setup(IOUSBHostInterface* interface, int endpointAddress, int maxPacketSize) LOCALONLY;
bool submit() LOCALONLY;
protected:
virtual void completeCallback(OSAction* action,
IOReturn status,
uint32_t actualByteCount,
uint64_t completionTimestamp) TYPE(IOUSBHostPipe::CompleteAsyncIO);
};
struct Transfer_IVars {
IOUSBHostInterface* interface;
int maxPacketSize;
IOUSBHostPipe* pipe;
IOBufferMemoryDescriptor* buffer;
OSAction* ioCompleteCallback;
};
bool Transfer::init() {
LOG_DEBUG();
if (!super::init()) {
LOG_ERROR("super::init failed");
}
if (ivars = IONewZero(Transfer_IVars, 1); ivars == nullptr) {
LOG_ERROR("Allocating ivars failed");
return false;
}
return true;
}
void Transfer::free() {
LOG_DEBUG();
IOSafeDeleteNULL(ivars, Transfer_IVars, 1);
super::free();
}
bool Transfer::setup(IOUSBHostInterface* interface, int endpointAddress,
int maxPacketSize) {
ivars->interface = interface;
ivars->maxPacketSize = maxPacketSize;
if (auto ret = interface->CopyPipe(endpointAddress, &ivars->pipe);
ret != kIOReturnSuccess) {
LOG_ERROR("Could not copy pipe: %{public}s, endpointAddress: %{public}d",
kern_return_t_toCStr(ret), endpointAddress);
}
if (auto ret =
interface->CreateIOBuffer(kIOMemoryDirectionIn, maxPacketSize, &ivars->buffer);
ret != kIOReturnSuccess) {
LOG_ERROR("CreateIOBuffer failed, ret: %{public}d", ret);
return false;
}
if (auto ret = CreateActioncompleteCallback(0, &ivars->ioCompleteCallback);
ret != kIOReturnSuccess) {
LOG_ERROR("Failed to set iocomplete callback, ret: %{public}d", ret);
return false;
}
return true;
}
bool Transfer::submit() {
if (auto ret = ivars->pipe->AsyncIO(ivars->buffer, ivars->maxPacketSize,
ivars->ioCompleteCallback, 0);
ret != kIOReturnSuccess) {
LOG_ERROR("Failed to call AsyncIO, ret: %{public}d", ret);
return false;
}
return true;
}
void IMPL(Transfer, completeCallback) {
LOG_DEBUG(
"Complete callback bytecount %{public}d, timestamp: %{public}llu, status %{public}s",
actualByteCount, completionTimestamp, kern_return_t_toCStr(status));
// TODO Unpack and then schedule another read.
}