4.5. Default I/O Queue
The SimpleToaster
driver uses a default I/O queue, for which KMDF handles power
management. The default queue receives all I/O requests for which the
driver does not specifically configure another queue. In this case, the
default queue receives all read, write, and device I/O control requests
that are targeted at the Simple Toaster driver. Default queues do not receive create requests.
The driver configures the queue by using the WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE function, which sets initial values in a configuration structure for the queue:
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE (&ioEvents,
WdfIoQueueDispatchParallel);
The function takes two parameters: a pointer to a WDF_IO_QUEUE_CONFIG structure (ioEvents) and an enumerator of the WDF_IO_QUEUE_DISPATCH_TYPE, which indicates how to dispatch requests from the queue. By specifying WdfIoQueueDispatchParallel,
the driver indicates that KMDF should dispatch I/O requests from the
queue as soon as they arrive and that the driver can handle multiple
requests in parallel.
After configuring the queue, the driver registers its callbacks for I/O events. The Simple Toaster supports only read, write, and device I/O control requests, so it sets callbacks for only these three events in the ioEvents configuration structure. It then calls WdfIoQueueCreate to create the WDFQUEUE object, as the following code shows:
ioEvents.EvtIoRead = ToasterEvtIoRead;
ioEvents.EvtIoWrite = ToasterEvtIoWrite;
ioEvents.EvtIoDeviceControl = ToasterEvtIoDeviceControl;
status = WdfIoQueueCreate (
hDevice,
&ioEvents,
WDF_NO_OBJECT_ATTRIBUTES,
&queue // pointer to default queue
);
When the driver calls WdfIoQueueCreate, it passes a handle to the WDFDEVICE object (hDevice) and a pointer to the ioEvents configuration structure. The driver accepts the default attributes for the queue, so it passes WDF_NO_OBJECT_ATTRIBUTES. The method returns a handle to the queue at &queue. By default, KMDF places all read, write, and device I/O requests targeted at the device into this queue.
4.6. Handling I/O Request: EvtIoRead, EvtIoWrite, EvtIoDeviceControl
A driver can include one or more of the following
I/O callback functions to handle the I/O requests that are dispatched
from its queues:
For each queue, the driver registers one or more
such callbacks. When an I/O request arrives, KMDF invokes the callback
that is registered for that type of request, if one exists. For
example, when a read request arrives, KMDF dispatches it to the EvtIoRead callback. Write requests are dispatched to the EvtIoWrite callback, and so forth. KMDF calls the EvtIoDefault callback when a request arrives for which the driver has not registered another callback. (In some cases, EvtIoDefault is also called to handle create request.)
KMDF queues and dispatches only the request for
which the driver configures a queue. If KMDF receives any other I/O
requests targeted at the Simple Toaster driver, it fails the requests with STATUS_INVALID_DEVICE_REQUEST.
As the preceding section mentions, the Simple Toaster driver handles only read, write, and device I/O control requests, so it includes only the EvtIoRead, EvtIoWrite, and EvtIoDeviceControl callback functions.
When KMDF invokes these callbacks, it passes a handle to the WDFQUEUE object, a handle to the WDFREQUEST
object that represents the I/O request, and one or more additional
parameters that provide details about the request. The additional
parameters vary depending on the specific callback.
If the driver supports buffered or direct I/O, it can use either the buffer passed by the originator of the request or a WDFMEMORY object that represents that buffer. Using a WDFMEMORY
object is simpler and requires less code because the framework handles
all validation and addressing issues. For example, the handle to the WDFMEMORY object contains the size of the buffer, thus ensuring that buffer overruns does not occur. The Simple Toaster driver uses this technique.
To get a handle to the WDFMEMORY object, the driver calls WdfRequestRetrieveOutputMemory for a read request and WdfRequestRetrieveInputMemory for a write request. Each of these methods creates a WDFMEMORY object that represents the corresponding buffer and is associated with the WDFREQUEST object.
To handle a read request, the driver then gets the data from its device and uses WdfMemoryCopyFromBuffer to copy the data from its internal buffer into the WDFMEMORY object that is associated with the request.
To handle a write request, the driver has three options:
Calling WdfMemoryCopyToBuffer to copy data from the WDFREQUEST object to the driver’s internal buffer, from which the driver can write to the device.
Getting the buffer pointer from the request by calling WdfRequestRetrieveInputBuffer, which also returns the number of bytes to write.
Getting the buffer pointer and the number of bytes to write by calling WdfMemoryGetBuffer.
For most write requests, drivers should use WdfMemoryCopyToBuffer to copy data supplied in the I/O request (and now stored in the associated WDFMEMORY object) to the driver’s output buffer. This function copies the data and returns an error if a buffer overflow occurs. Use the WdfRequestRetrieveInputBuffer and WdfMemoryGetBuffer
methods only if you require the buffer pointer so that you can cast it
to a structure, which might be necessary when handling a device I/O
control request.
Initially, the names WdfMemoryCopyFromBuffer and WdfMemoryCopyToBuffer might appear somewhat confusing. Remember that the word “Memory” in the name means that the function acts on a WDFMEMORY object and copies data to or from a program buffer into that object. Thus, WdfMemoryCopyFromBuffer copies data from the driver’s internal buffer into a WDFMEMORY object, so it is used for read requests. WdfMemoryCopyToBuffer copies data to the driver’s internal buffer, so it is used for write requests.
When the driver has satisfied the I/O request, it calls WdfRequestCompleteWithInformation, which completes the underlying I/O request with the specified status and passes the number of bytes read or written.
The Simple Toaster sample’s EvtIoRead, EvtIoWrite, and EvtIoDeviceControl callbacks are essentially stubs. Although the ToasterEvtIoRead function calls WdfRequestRetrieveOutputMemory to get the output buffer and the ToasterEvtIoWrite function calls WdfRequestRetrieveInputMemory
to get the input buffer, neither function reads, writes, or returns any
data. Therefore, none of these I/O event callbacks is reproduced here.