c++ IOUserClientMethodArguments完成值始终为NULL

rnmwe5a2  于 2022-11-19  发布在  其他
关注(0)|答案(1)|浏览(106)

我正在尝试使用IOConnectCallAsyncStructMethod来设置客户端和驱动程序之间的回调。
这就是我所说的IOConnectCallAsyncStructMethod

ret = IOConnectCallAsyncStructMethod(connection, MessageType_RegisterAsyncCallback, masterPort, asyncRef, kIOAsyncCalloutCount, nullptr, 0, &outputAssignCallback, &outputSize);

其中asyncRef为:

asyncRef[kIOAsyncCalloutFuncIndex] = (io_user_reference_t)AsyncCallback;
    asyncRef[kIOAsyncCalloutRefconIndex] = (io_user_reference_t)nullptr;

AsyncCallback是:

static void AsyncCallback(void* refcon, IOReturn result, void** args, uint32_t numArgs)
{
    const char* funcName = nullptr;
    uint64_t* arrArgs = (uint64_t*)args;
    ReadDataStruct* output = (ReadDataStruct*)(arrArgs + 1);

    switch (arrArgs[0])
    {
        case 1:
        {
            funcName = "'Register Async Callback'";
        } break;

        case 2:
        {
            funcName = "'Async Request'";
        } break;

        default:
        {
            funcName = "UNKNOWN";
        } break;
    }

    printf("Got callback of %s from dext with returned data ", funcName);
    
    printf("with return code: 0x%08x.\n", result);

    // Stop the run loop so our program can return to normal processing.
    CFRunLoopStop(globalRunLoop);
}

但是IOConnectCallAsyncStructMethod总是返回kIOReturnBadArgument,我可以看到当方法:

kern_return_t MyDriverClient::ExternalMethod(uint64_t selector, IOUserClientMethodArguments* arguments, const IOUserClientMethodDispatch* dispatch, OSObject* target, void* reference) {
    
    kern_return_t ret = kIOReturnSuccess;

    if (selector < NumberOfExternalMethods)
    {
        dispatch = &externalMethodChecks[selector];
        if (!target)
        {
            target = this;
        }
    }

    return super::ExternalMethod(selector, arguments, dispatch, target, reference);

IOUserClientMethodArguments* arguments中,完成是completion =(OSAction •) NULL
这是我用来检查值的IOUserClientMethodDispatch

[ExternalMethodType_RegisterAsyncCallback] =
    {
        .function = (IOUserClientMethodFunction) &Mk1dDriverClient::StaticRegisterAsyncCallback,
        .checkCompletionExists = true,
        .checkScalarInputCount = 0,
        .checkStructureInputSize = 0,
        .checkScalarOutputCount = 0,
        .checkStructureOutputSize = sizeof(ReadDataStruct),
    },

你知道我做错了什么吗?或者其他什么想法?

rryofs0p

rryofs0p1#

kIOReturnBadArgument的可能原因:

方法调用中的端口参数看起来很可疑:

IOConnectCallAsyncStructMethod(connection, MessageType_RegisterAsyncCallback, masterPort, …
------------------------------------------------------------------------------^^^^^^^^^^

如果你在这里传递IOKit main/master端口(kIOMasterPortDefault),那是错误的。这个参数的目的是提供一个通知Mach端口,它将接收异步完成消息。你将需要创建一个端口,并在适当的调度队列或runloop上调度它。我通常使用如下的方式:

// Save this somewhere for the entire time you might receive notification callbacks:
    IONotificationPortRef notify_port = IONotificationPortCreate(kIOMasterPortDefault);
    // Set the GCD dispatch queue on which we want callbacks called (can be main queue):
    IONotificationPortSetDispatchQueue(notify_port, callback_dispatch_queue);
    // This is what you pass to each async method call:
    mach_port_t callback_port = IONotificationPortGetMachPort(notify_port);

完成通知端口后,确保使用IONotificationPortDestroy()销毁它。
看起来你可能正在使用runloops,在这种情况下,你可以使用IONotificationPortGetRunLoopSource函数来获取通知端口的runloops源,而不是调用IONotificationPortSetDispatchQueue,然后你可以在正在使用的CFRunloop对象上调度它。

关于异步完成参数的一些注意事项:

您还没有发布您的DriverKit端AsyncCompletion()调用,无论如何,这不会直接导致您的问题,但一旦您修复异步调用本身,可能会爆炸:
如果你的异步补全只传递了2个用户参数,你在应用端使用了错误的回调函数签名。你必须使用IOAsyncCallback2而不是IOAsyncCallback形式。
此外,即使您传递了3个或更多的参数,其中IOAsyncCallback形式是正确的,我相信由于别名规则,此代码在技术上会触发未定义的行为:

uint64_t* arrArgs = (uint64_t*)args;
    ReadDataStruct* output = (ReadDataStruct*)(arrArgs + 1);

    switch (arrArgs[0])

我认为以下是正确的:

ReadDataStruct* output = (ReadDataStruct*)(args + 1);

    switch ((uintptr_t)args[0])

(Don(不能转换数组指针本身,请转换每个void*元素。)

有关异步输出结构参数的说明

我注意到您在异步方法调用中有一个struct输出参数,它的缓冲区看起来相当小。如果您打算在初始ExternalMethod返回 * 之后 * 用DriverKit端的数据更新它,您可能会感到惊讶:未作为IOMemoryDescriptor传递的输出结构参数将在方法返回时立即复制到应用程序端,而不是在触发异步完成时。
那么,如何解决这个问题呢?对于非常小的数据,在异步完成参数本身中传递它。对于任意大小的字节缓冲区,我所知道的唯一方法是确保输出结构参数通过IOMemoryDescriptor传递,它可以在驱动程序和应用程序进程之间的共享Map中持久地进行内存Map。好吧,如何将它作为内存描述符传递呢?基本上,输出结构必须大于4096字节。2是的,这实际上意味着如果你不得不让你的缓冲区变得异常的大。

相关问题