KMDF delivers I/O requests from a parallel
queue by calling the appropriate callback function that the driver
registered for the queue. For example, the PCIDRV sample configures its parallel device I/O control queue, which accepts only WdfRequestTypeDeviceControl requests, with an EvtIoDeviceControl callback.
The driver’s actions in the callback depend on the
type of I/O that is required to satisfy the request. If the request
required DMA, the driver should retrieve a pointer to the device
context area and then set up the DMA transaction.
For I/O requests that do not involve DMA, a driver takes the following steps:
1. | Gets the parameters for the I/O request.
|
2. | Parses the parameters.
|
3. | Performs the requested I/O or manually requeues the request for later processing.
|
1. Code to Handle I/O Requests
The following code shows how the PCIDRV sample handles certain device I/O control requests. The PciDrvEvtIoControl function is the driver’s EvtIoDeviceControl callback for one of its parallel queues and appears in the pcidrv\sys\Pcidrv.c source file.
VOID
PciDrvEvtIoDeviceControl (
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t OutputBufferLength,
IN size_t InputBufferLength,
IN ULONG IoControlCode
)
{
NTSTATUS status = STATUS_SUCCESS;
PFDO_DATA fdoData = NULL;
WDFDEVICE hDevice;
WDF_REQUEST_PARAMETERS params;
UNREFERENCED_PARAMETER (OutputBufferLength);
UNREFERENCED_PARAMETER (InputBufferLength);
hDevice = WdfIoQueueGetDevice (Queue);
fdoData = FdoGetData (hDevice);
WDF_REQUEST_PARAMETERS_INIT (¶ms);
WdfRequestGetParameters (
Request,
¶ms
);
switch (IoControlCode)
{
case IOCTL_NDISPROT_QUERY_OID_VALUE:
ASSERT ((IoControlCode * 0x3) ==
METHOD_BUFFERED);
NICHandleQueryOldRequest (
Queue,
Request,
¶ms
);
break;
// code omitted
// ... . .
//
case IOCTL_NDISPROT_INDICATE_STAUS:
status = WdfRequestForwardToIoQueue (Request,
fdoData->PendingIoctlQueue);
if (!NT_SUCCESS (status))
{
WdfRequestcomplete (Request, status);
break;
}
break;
default:
ASSERTMSG (FALSE, "Invalid IOCTL request\n");
WdfRequestComplete (Request,
STATUS_INVALID_DEVICE_REQUEST);
break;
}
return;
}
The driver requires access to its device object context area, so it starts by calling WdfIoQueueGetDevice, which returns a handle to the device object that is associated with the I/O queue. It then passes the returned handle to FdoGetData, the accessor function for its device context area, which returns a pointer to the context area.
Next, the driver retrieves the parameters that were passed with the I/O request. KMDF defines the WDF_REQUEST_PARAMETERS structure for this purpose. The driver initializes the structure by calling WDF_REQUEST_PARAMETERS_INIT and then passes it to the WdfRequestParameters method. This method fills in the requested parameters. The driver must retrieve the parameters from the request structure before it performs the I/O.
The driver’s action depends on the I/O control code
that was specified in the request. For most control codes, the driver
calls a device-specific function to handle the request. If the control
code is valid, the driver performs the requested action. If the control
code is not valid, the driver completes the request by calling WdfRequestComplete with the handle to the request and the status STATUS_INVALID_DEVICE_REQUEST.
In response to the two control codes shown in the code, the driver takes two different actions. If the control code is IOCTL_NDISPROT_QUERY_OID_VALUE, the driver performs buffered I/O. If the control code is IOCTL_NDISPROT_INDICATE_STATUS, the driver forwards the request to a different I/O queue. The next two sections describe these actions.
2. Performing Buffered I/O
The WDFREQUEST object contains pointers to the input and output buffers for the I/O request. The driver can obtain these pointers by calling WdfRequestRetrieveOutputBuffer and WdfRequestRetrieveInputBuffer.
For METHOD_BUFFERED
requests, however, the input and output buffers are the same, so both
of these methods return the same pointer. Therefore, when performing
buffered I/O, a driver must read all input data from the buffer before
writing any output data to the buffer.
The NICHandleQueryOidRequest function, defined in the pcidrv\sys\hw\Nic_req.c file, gets the input and output buffers from the WDFREQUEST
object and retrieves the requested information. Most of this function
performs device-specific tasks, so this section describes only the
KMDF-specific actions.
The sample retrieves the buffer by calling WdfRequestRetrieveOutputBuffer, as follows:
status = WdfRequestRetrieveOutputBuffer (Request,
sizeof (NDISPROT_QUERY_OID),
&DataBuffer,
&BufferLength
);
if (!NT_SUCCESS (status))
{
WdfRequestcomplete (Request, status);
return;
}
The input parameters to WdfRequestRetrieveOutputBuffer
are the handle to the request object and the minimum required size for
the buffer. The method returns a pointer to the buffer and a pointer to
its length. If the length is smaller than the minimum required or if
some other error occurs, WdfRequestRetrieveOutputBuffer returns a failure status and the driver immediately completes the request with that status.
The driver then performs device-specific queries for
the requested information and writes the returned data into the output
buffer. If the buffer is too small to hold the data, the driver sets
status to STATUS_BUFFER_TOO_SMALL.
Next, the driver updates the buffer length to
include the number of bytes of data, the size of the structure that
holds it, and any padding bytes that are required to align the
structure correctly. The driver performs this calculation even if the
buffer is too small, so that it can return the buffer length that would
be required to successfully complete the request. Finally, the driver
completes the request by calling WdfRequestCompleteWithInformation with the handle to the request, the previously set status value, and the calculated buffer length, as follows:
//
// Adjust the size to include the structure.
//
ulInfoLen += FIELD_OFFSET (NDISPROT_QUERY_OID, Data);
WdfRequestCompleteWithInformation (Request,
Status, ulInfoLen);