Search code examples
macossynchronizationiokitkernel-extensionxnu

Difference between GetWorkLoop()->runAction and GetCommandGate()->runAction?


This has been confusing me for a long time. In my IOkit driver,I registered interrupt event source, timer event source to workloop. and I use GetWorkLoop()->runAction(pAction) for hardware access. so, all hardware access from interrupt handler and timer handler and my pAction are serialized. But, I found another runAction from IOCommandGate. I wonder the difference between the two runAction. I looked into some iokit kernel docs.didn't get a clear answer.

In xnu source:

IOReturn IOWorkLoop::runAction(Action inAction, OSObject *target,
                                  void *arg0, void *arg1,
                                  void *arg2, void *arg3)
{
    IOReturn res;

    // closeGate is recursive so don't worry if we already hold the lock.
    closeGate();
    res = (*inAction)(target, arg0, arg1, arg2, arg3);
    openGate();
    return res;
}

I means when I call GetWorkLoop()->runAction(inAction). inAction is run in my thread context, not in workloop thread context. is this correct?

IOReturn IOCommandGate::runAction(Action inAction,
                                  void *arg0, void *arg1,
                                  void *arg2, void *arg3)
{
    if (!inAction)
        return kIOReturnBadArgument;

    // closeGate is recursive needn't worry if we already hold the lock.
    closeGate();

    // If the command gate is disabled and we aren't on the workloop thread
    // itself then sleep until we get enabled.
    IOReturn res;
    if (!workLoop->onThread()) {
    while (!enabled) {
        uintptr_t *sleepersP = (uintptr_t *) &reserved;

        *sleepersP += 2;
        IOReturn res = sleepGate(&enabled, THREAD_ABORTSAFE);
        *sleepersP -= 2;

        bool wakeupTearDown = (*sleepersP & 1);
        if (res || wakeupTearDown) {
        openGate();

         if (wakeupTearDown)
             commandWakeup(sleepersP);  // No further resources used

        return kIOReturnAborted;
        }
    }
    }

    bool trace = ( gIOKitTrace & kIOTraceCommandGates ) ? true : false;

    if (trace)
        IOTimeStampStartConstant(IODBG_CMDQ(IOCMDQ_ACTION),
                     VM_KERNEL_UNSLIDE(inAction), (uintptr_t) owner);

    IOStatisticsActionCall();

    // Must be gated and on the work loop or enabled
    res = (*inAction)(owner, arg0, arg1, arg2, arg3);

    if (trace)
        IOTimeStampEndConstant(IODBG_CMDQ(IOCMDQ_ACTION),
                       VM_KERNEL_UNSLIDE(inAction), (uintptr_t) owner);

    openGate();
    return res;
}

the code seems GetCommandGate()->runAction also run in my thread context. not workloop thread?


Solution

  • You are correct, in both cases your action will be run in the context of the current thread, not the IOWorkLoop thread. It is guaranteed that the IOWorkLoop will not be running any actions (secondary interrupt handlers, etc.) on its thread while your action is running however.

    The difference between the two, as you can see, is that the IOCommandGate can be disabled and re-enabled to pause running of actions. I have not needed this in practice, but maybe it comes in useful sometimes.

    To run actions on the IOWorkLoop thread itself, an IOEventSource subclass must override the checkForWork() virtual method, and notify the IOWorkLoop of new work via the IOEventSource's signalWorkAvailable() method.

    I'm not aware of a general-purpose event source you can use which allows you to queue arbitrary jobs to run on the IOWorkLoop thread, other than IOCommandQueue which has been deprecated for many years. (so you should not use that)