Notifications from the Driver

The methods that we have implemented in the sample driver's user client are designed to be blocking functions; that is, the user process does not continue execution until the user client has completed the requested operation. For example, the method in our user client DelayForMs() would suspend the thread on which it was called until the specified delay has elapsed. While this may not be a bad thing for a function whose aim is to explicitly delay the calling thread, a user space application may not always wish to wait for a driver operation to complete, particularly if that operation may take an indeterminate amount of time or is dependent on an event over which the driver has no control, such as the arrival of data on a serial port.

Two approaches are used to overcome this problem. Unlike the Windows driver model, which, unless explicitly enabled, does not allow an application to send multiple control requests to a driver simultaneously, the I/O Kit allows as many threads to send requests to a user client as the application requires. This means that one solution for blocking operations is for the application to create a secondary thread on which to call blocking user client methods. This frees up the rest of the application to continue executing while the driver processes the request. The second approach is for the user client to implement asynchronous operations and notify the application when the operation completes through a callback function.

The I/O Kit framework makes it easy for a developer to turn an operation from a synchronous, blocking operation into an asynchronous operation. To see how it is done, let's extend the driver interface that we have developed in this chapter to include a function named InstallTimer(), which will delay for a specified period and then notify the application through a callback function. In this way, the InstallTimer() function can be thought of as an asynchronous implementation of our existing function DelayForTime().

The I/O Kit framework uses an implementation for asynchronous notifications that is very similar to the delivery of notifications for device arrival that was described at the start of this chapter (See Listing 5-2). In fact, the notifications received for device arrival can be thought of as a special asynchronous completion callback that is implemented by the I/O Kit framework itself. To initialize our driver library to support the delivery of completion callbacks, we need to create a notification port on which the kernel driver will signal the user space process when an operation has completed. This is done in the same way in which we created a notification port for the delivery of device arrival notifications, by calling the IONotificationPortCreate() function. Although this notification port will be allocated by the driver's user space library, it's good design practice to provide a function that allows the application to access the port so that the application can install the notification port on the run loop of its choice. A possible implementation of an accessor function for a driver's notification port is shown in Listing 5-14.

Listing 5-14. Allocating a Port on Which an Application Can Receive Notifications When an Asynchronous Operation Completes

IONotificationPortRef   gAsyncNotificationPort = NULL;

IONotificationPortRef   MyDriverGetAsyncCompletionPort ()
{
        // If the port has been allocated, return the existing instance.
        if (gAsyncNotificationPort != NULL)
                return gAsyncNotificationPort;
        
        gAsyncNotificationPort = IONotificationPortCreate(kIOMasterPortDefault);
        return gAsyncNotificationPort;
}

An application can then allocate and install the notification port in one of its run loops, as follows:

CFRunLoopSourceRef      runLoopSource;
notificationPort = MyDriverGetAsyncCompletionPort ();
runLoopSource = IONotificationPortGetRunLoopSource(notificationPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

Having allocated a notification port on which the user space application can receive messages from the kernel driver, we now need to provide the port to our kernel driver so that it has a port on which it can signal the completion of asynchronous operations. The notification port is provided to the driver's user client on each asynchronous control request. The I/O Kit framework provides asynchronous variations of each of the IOConnectCallXXX() functions named IOConnectCallAsyncXXX(). The asynchronous form of these functions take additional arguments, including a notification port, a callback function, and a context parameter that is passed to the callback function.

In all regards, the asynchronous variation of the IOConnect functions behave identically to their synchronous counterparts; for example, IOConnectCallAsyncScalarMethod() passes an array of integer values to the user client and receives an array of integer values from the user client. As with the synchronous form of these functions, any output parameters are written as soon as the function returns and not when the asynchronous operation completes (the driver may still be handling the operation when the function returns).

To provide an example of how the asynchronous functions are used, let's examine the implementation of our sample InstallTimer() function. The user space code is shown in Listing 5-15.

Listing 5-15. The User Space Implemenation of an Asynchronous Control Request for Our Driver's InstallTimer() Function

kern_return_t   InstallTimer (io_connect_t connection, uint32_t milliseconds,
                        IOAsyncCallback0 timerCallback, void* context)
{
        io_async_ref64_t        asyncRef;
        uint64_t                scalarIn[1];
        
        // Set up the callback function.
        asyncRef[kIOAsyncCalloutFuncIndex] = (uint64_t)timerCallback;
        asyncRef[kIOAsyncCalloutRefconIndex] = (uint64_t)context;
        
        // Set up the input parameter.
        scalarIn[0] = milliseconds;
        return  IOConnectCallAsyncScalarMethod(connection, kTestUserClientInstallTimer,
                IONotificationPortGetMachPort(gAsyncNotificationPort),
                asyncRef,  kIOAsyncCalloutCount,
                scalarIn, 1, NULL, NULL);
}

If you compare the preceding function to the implementation for DelayForTime(), you will notice that both functions have a lot in common and pass the same input and output parameters to the user client. The only difference between the two functions is the addition of the timerCallback and context arguments. The callback function and its context parameter are provided to the IOConnectCallAsync functions through the structure io_async_ref64_t, which is defined as an array of unsigned 64-bit integers. Because certain elements of the io_async_ref64_t array are used internally by the I/O Kit, an application should use the constants kIOAsyncCalloutFuncIndex and kIOAsyncCalloutRefconIndex to access the array, as shown in the example.

This is all that an application needs to do to perform an asynchronous operation. When the operation completes, the provided callback function will be notified and will execute on the run loop on which the application installed the run loop source. The callback function has the following signature:

typedef void (*IOAsyncCallback0)(void* context, IOReturn result);

Note that the “0” in IOAsyncCallback0 refers to the number of parameters from the driver that the function receives. These are separate from the output scalar count passed to the function IOConnectCallAsyncScalarMethod() and are specified by the kernel when the asynchronous operation is complete.

Of course, for an operation to be asynchronous, the kernel driver must ensure that it returns immediately from the user client but will continue to handle the requested operation in the background and signal the process when the operation completes. An asynchronous method call is received by the user client no differently than from any other method call and is dispatched by externalMethod() to a handler function that receives its arguments through an IOExternalMethodArguments structure. However, unlike a synchronous method, the IOExternalMethodArguments structure contains fields that are useful to an asynchronous operation, as shown in the following definition:

struct IOExternalMethodArguments
{
        …
        mach_port_t             asyncWakePort;
        io_user_reference_t*    asyncReference;
        uint32_t                asyncReferenceCount;
        …
};

By checking the value of asyncWakePort, the method that implements a control request can determine whether the user application invoked it through an asynchronous function call. If it is non-zero, an asynchronous operation was requested. Given that the handler function will perform the requested operation in the background (since an asynchronous control request should avoid blocking the calling application), it needs to save any values from the IOExternalMethodArguments structure that it will need to refer to while performing the operation. This includes copying any scalar and structure input parameter values that were provided by the caller (noting that the output values are returned to the calling application as soon as the user client returns and not when the asynchronous operation is completed). An important value from the IOExternalMethodArguments structure that needs to be saved is the asyncReference buffer, since this is used to signal the application when the operation has completed.

An example of how an asynchronous operation is performed is shown below in Listing 5-16.

Listing 5-16. The User Client Implementation of an Asynchronous Operation. This Implements the Kernel-side of the InstallTimer() Function.

// A structure to hold parameters required by the background operation.
struct TimerParams
{
        OSAsyncReference64              asyncRef;
        uint32_t                        milliseconds;
        OSObject*                       userClient;
};

IOReturn  com_osxkernel_driver_IOKitTestUserClient::
       sInstallTimer (OSObject* target, void* reference, IOExternalMethodArguments* arguments)
{
        TimerParams*    timerParams;
        thread_t        newThread;
        
        // Allocate a structure to store parameters required by the timer.
        timerParams = (TimerParams*)IOMalloc(sizeof(TimerParams));
        // Take a copy of the asyncReference buffer.
        bcopy(arguments->asyncReference, timerParams->asyncRef, sizeof(OSAsyncReference64));

        // Take a copy of the "milliseconds" value provided by the user application.
        timerParams->milliseconds = (uint32_t)arguments->scalarInput[0];
        // Take a reference to the userClient object.
        timerParams->userClient = target;
        // Retain the user client while an asynchronous operation is in progress.
        target->retain();
        
        // Start a background thread to continue the operation after returning to the caller.
        kernel_thread_start(DelayThreadFunc, timerParams, &newThread);
        thread_deallocate(newThread);
        
        // Return immediately to the calling application.
        return kIOReturnSuccess;
}

void    com_osxkernel_driver_IOKitTestUserClient::
                DelayThreadFunc (void *parameter, wait_result_t)
{
        TimerParams*    timerParams = (TimerParams*)parameter;
        
        // Sleep for the requested time.
        IOSleep(timerParams->milliseconds);
        // Send a notification to the user application that the operation has competed.
        sendAsyncResult64(timerParams->asyncRef, kIOReturnSuccess, NULL, 0);
        
        // The background operation has completed, release the extra reference to the
        // user client object.
        timerParams->userClient->release();
        
        IOFree(timerParams, sizeof(TimerParams));
}

Although there is a lot to take in from the preceding code, it is important to note the way that it allocates an object to hold the parameters that are required while the driver completes the requested operation on a background thread. To prevent the user client object from being released while the operation is in progress, the method increments its retain count when starting the operation and decrements its retain count when the operation completes. Finally, when the background operation has completed, the user client (or driver) signals the user application by calling sendAsyncResult64(). The final two parameters of sendAsyncResult64(), which are unused in this example, allow a driver to provide additional values to the application's callback function. For example, an asynchronous read operation could use this to return the number of bytes that it read.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.180.145