Code Example: A Filter Driver

< BACK  NEXT >
[oR]

This example demonstrates a basic filter driver, called the HIFILTER, that intercepts all requests intended for a lower-level driver (LODRIVER). The purpose of the filter is to hide the lower driver's limited output transfer size. To do this, it breaks large transfer requests into smaller pieces. It also overrides an IOCTL from the lower driver that returns the maximum size of an output buffer. All other major function codes supported by the lower driver are passed through from the filter.

The code for this example is included on the accompanying disk and at the book's Web site: http://www.W2KDriverBook.com.

The DEVICE_EXTENSION Structure

The declarations of the Device Extension for the filter driver are minimal; the filter driver has few direct responsibilities.

      // BUFFER_SIZE_INFO is a driver-defined structure      // that describes the buffers used by the filter typedef struct _BUFFER_SIZE_INFO {      ULONG MaxWriteLength;      ULONG MaxReadLength; } BUFFER_SIZE_INFO, *PBUFFER_SIZE_INFO; typedef struct _DEVICE_EXTENSION {    PDEVICE_OBJECT pDeviceObject;    // Back pointer        PDEVICE_OBJECT pTargetDevice;    // Lower device        BUFFER_SIZE_INFO bufferInfo; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; 

The DriverEntry Function

The DriverEntry function for a filter is unique in that its Dispatch routine announcement must closely follow the functionality exposed by the lower driver. If the lower driver supports a Dispatch entry, the filter must expose it as well. For those entries that the filter is modifying (overriding), the filter installs its own Dispatch function address. For entries that the filter does not override, a pass-through dispatch function is installed.

 NTSTATUS DriverEntry(                IN PDRIVER_OBJECT pDrvObj,                IN PUNICODE_STRING pRegPath ) {    // Export other driver entry points    pDrvObj->DriverUnload = DriverUnload;    pDrvObj->DriverExtension->AddDevice = AddDevice;        // Assume (initially) nothing is overridden    for (int i=0; i<=IRP MJ MAXIMUM_FUNCTION; i++)        if (i!=IRP MJ POWER)            pDriverObject->MajorFunction[i] = DispatchPassThru;                // Export the overridden MajorFunctions    pDrvObj->MajorFunction[ IRP_MJ_WRITE ] =            OverriddenDispatchWrite;    pDrvObj->MajorFunction[ IRP_MJ_DEVICE_CONTROL ] =            OverriddenDispatchIoControl;    :    return STATUS_SUCCESS; } 

The AddDevice Function

This portion of the example shows the code executed each time a filter device attaches to the target device.

 NTSTATUS AddDevice( IN PDRIVER_OBJECT pDrvObj,                     IN PDEVICE_OBJECT pdo ) {    NTSTATUS status;    PDEVICE_OBJECT pFilterDevObj;    PDEVICE_EXTENSION pDevExt;        // Create the un-named filter device    status =       IoCreateDevice( pDrvObj,                      sizeof(DEVICE_EXTENSION),                      NULL,      // no name                      FILE_DEVICE_UNKNOWN,                      0, TRUE,                      &pFilterDevObj );    if (!NT_SUCCESS(status))      return status;          // Initialize the Device Extension    pDevExt = (PDEVICE_EXTENSION)      pFilterDevObj->DeviceExtension;    pDevExt->pDevice = pFilterDevObj;    // back pointer        // Pile this new filter on top of the existing target    pDevExt->pTargetDevice =         // downward pointer       IoAttachDeviceToDeviceStack( pFilterDevObj, pdo);           // Copy the characteristics of the target into the    //        the new filter device object    pFilterDevObj->DeviceType =           pDevExt->pTargetDevice->DeviceType;    pFilterDevObj->Characteristics =           pDevExt->pTargetDevice->Characteristics;    pFilterDevObj->Flags |=          ( pDevExt->pTargetDevice &               ( DO_BUFFERED_IO | DO_DIRECT_IO |                 DO_POWER_INRUSH | DO_POWER_PAGABLE));                     // Explore the limitations of the target device's    // buffer. Save the results in the bufferInfo struct    GetBufferLimits( pDevExt->pTargetDevice,                      &pDevExt->bufferInfo );                          return STATUS_SUCCESS; } 
GetBufferLimits

This is a helper function that queries the lower-level driver for information about its buffer size limits. It shows how to make a synchronous IOCTL call from one driver to another.

 VOID GetBufferLimits(                 IN PDEVICE_OBJECT pTargetDevObj,                 OUT PBUFFER_SIZE_INFO pBufferInfo ) {    KEVENT keIoctlComplete;    IO_STATUS_BLOCK Iosb;    PIRP pIrp;        // Initialize the event that is signaled when the    // IOCTL IRP completes (by the target device)    KeInitializeEvent(           &keIoctlComplete,           NotificationEvent,           FALSE );               // Construct the IRP for the private IOCTL request    pIrp = IoBuildDeviceIoControlRequest(                IOCTL_GET_MAX_BUFFER_SIZE,                pTargetDevObj,                NULL,                0,                pBufferInfo,                sizeof(BUFFER_SIZE_INFO),                FALSE,                &keIoctlComplete,                &Iosb );                    // Send the new IRP down to the target    IoCallDriver( pTargetDevObj, pIrp );        // Wait for the target to complete the IRP    KeWaitForSingleObject(           &keIoctlComplete,           Executive,           KernelMode,           FALSE,           NULL ); } 

The OverriddenDispatchWrite Function

The DispatchWrite function of the target (lower) driver is overridden by the filter. Since the lower driver has a limit on the maximum size of a write request, the filter breaks client requests for larger transfers into smaller pieces. This OverriddenDispatchWrite routine, along with an I/O Completion routine, performs the work of the split transfer.

 NTSTATUS OverriddenDispatchWrite(           IN PDEVICE_OBJECT pDevObj,           IN PIRP pIrp ) {               PDEVICE_EXTENSION pFilterExt = (PDEVICE_EXTENSION)           pDevObj->DeviceExtension;               PIO_STACK_LOCATION pIrpStack =           IoGetCurrentIrpStackLocation( pIrp );               PIO_STACK_LOCATION pNextIrpStack =           IoGetNextIrpStackLocation( pIrp );               ULONG maxTransfer =           pFilterExt->bufferInfo.MaxWriteLength;               ULONG bytesRequested =           pIrpStack->Parameters.Write.Length;               // We can handle the request for 0 bytes ourselves    if (bytesRequested == 0) {       pIrp->IoStatus.Status = STATUS_SUCCESS;       pIrp->IoStatus.Information = 0;       IoCompleteRequest( pIrp, IO_NO_INCREMENT );       return STATUS_SUCCESS;    }        // If the request is small enough for the target    // device, just pass it thru...    if (bytesRequested < maxTransfer)    return DispatchPassThru( pDevObj, pIrp );        // Set up the next lower stack location to xfer as    // much data as the lower level allows.    pNextIrpStack->MajorFunction = IRP_MJ_WRITE;    pNextIrpStack->Parameters.Write.Length = maxTransfer;        // It turns out that the lower driver doesn't use the    // ByteOffset field of the IRP's Parameter.Write block    // so we use it for context storage.    // HighPart holds the remaining transfer count.    // LowPart holds the original buffer address.    pIrpStack->Parameters.Write.ByteOffset.HighPart =           bytesRequested;    pIrpStack->Parameters.Write.ByteOffset.LowPart =           (ULONG) pIrp->AssociatedIrp.SystemBuffer;               // Set up the I/O Completion routine. Since there is    // no external context (beyond the IRP's Parameters)    // no context is passed to the Completion routine.    IoSetCompletionRoutine(                pIrp,                WriteCompletion,                NULL,          // no context                TRUE, TRUE, TRUE );                    // Pass the IRP to the target    return IoCallDriver(                pFilterExt->pTargetDevice,                pIrp ); } 

The OverriddenDispatchDeviceIoControl Function

To further hide the limitations of the lower-level driver, the filter intercepts the IOCTL queries about the driver's maximum transfer size. Instead of returning the lower-level driver's limit values, it lies and says there are no limits. Any other kind of IOCTL function is passed through.

 NTSTATUS OverriddenDispatchDeviceIoControl(           IN PDEVICE_OBJECT pDevObj,           IN PIRP pIrp ) {               PIO_STACK_LOCATION pIrpStack =           IoGetCurrentIrpStackLocation( pIrp );               PBUFFER_SIZE_INFO pBufferInfo;        // Here is the interception    if (pIrpStack->Parameters.DeviceIoControl.IoControlCode                     == IOCTL_GET_MAX_BUFFER_SIZE ) {       // The buffer passed by the user (by mutual       // agreement) is treated as BUFFER_SIZE_INFO type.       pBufferInfo = (PBUFFER_SIZE_INFO)            pIrp->AssociatedIrp.SystemBuffer;       pBufferInfo->MaxWriteLength = NO_BUFFER_LIMIT;       pBufferInfo->MaxReadLength = NO_BUFFER_LIMIT;              // Complete the IRP by announcing the size of       // the returned BUFFER_SIZE_INFO information.       pIrp->IoStatus.Information =                 sizeof(BUFFER_SIZE_INFO);       pIrp->IoStatus.Status = STATUS_SUCCESS;       IoCompleteRequest( pIrp, IO_NO_INCREMENT );       return STATUS_SUCCESS;       } else       // not the IOCTL we're supposed to intercept,       // just pass it thru to the "real" device.       return DispatchPassThru( pDevObj, pIrp ); } 

The DispatchPassThru Function

If the IRP request (intercepted by the filter) is not a write or an IOCTL with IOCTL_GET_MAX_BUFFER_SIZE code, it is passed down to the target device without modification. The filter driver attaches a generic I/O Completion routine to the request to handle the case of marking the IRP pending when the target requires.

 NTSTATUS DispatchPassThru(                IN PDEVICE_OBJECT pDevObj,                IN PIRP pIrp ) {                    PDEVICE_EXTENSION pFilterExt = (PDEVICE_EXTENSION)            pDevObj->DeviceExtension;                PIO_STACK_LOCATION pIrpStack =            IoGetCurrentIrpStackLocation( pIrp );                PIO_STACK_LOCATION pNextIrpStack =            IoGetNextIrpStackLocation( pIrp );                // Copy args to the next level    *pNextIrpStack = *pIrpStack;        // Set up a completion routine to handle the bubbling    // of the "pending" mark of an IRP    IoSetCompletionRoutine(           pIrp,           GenericCompletion,           NULL,           TRUE, TRUE, TRUE );               // Pass the IRP to the target.    return IoCallDriver(                 pFilterExt->pTargetDevice,                 pIrp );    } 

The I/O Completion Routines

The filter driver example uses two I/O Completion routines. One handles the completion of write requests. The other handles the pass-through completion.

WriteCompletion

This somewhat involved function performs all the additional work required after a partial transfer has occurred. If any partial transfer results in an error, the entire transfer is aborted. Otherwise, it sets up the IRP for another small chunk and sends it to the target device. When the entire transfer finishes, the routine completes the original IRP.

 NTSTATUS WriteCompletion(                IN PDEVICE_OBJECT pDevObj,                IN PIRP pIrp,                IN PVOID pContext ) {                    PDEVICE_EXTENSION pFilterExt = (PDEVICE_EXTENSION)            pDevObj->DeviceExtension;                PIO_STACK_LOCATION pIrpStack =            IoGetCurrentIrpStackLocation( pIrp );                PIO_STACK_LOCATION pNextIrpStack =            IoGetNextIrpStackLocation( pIrp );                ULONG transferSize =                 pIrp->IoStatus.Information;                     ULONG bytesRequested =                 pIrpStack->Parameters.Write.Length;                     ULONG bytesRemaining = (ULONG)            pIrpStack->Parameters.Write.ByteOffset.HighPart;                ULONG maxTransfer =            pFilterExt->bufferInfo.MaxWriteLength;                NTSTATUS status = pIrp->IoStatus.Status;        // If the last transfer was successful, reduce the    // "bytesRemaining" context variable.    if (NT_SUCCESS( status ))       bytesRemaining -= transferSize;    pIrpStack->Parameters.Write.ByteOffset.HighPart =        bytesRemaining;                // If there is still more data to transfer, do it.    if ( NT_SUCCESS( status )   (bytesRemaining > 0) ) {            // Bump the buffer address to next chunk.        pIrp->AssociatedIrp.SystemBuffer =                (PUCHAR) pIrp->AssociatedIrp.SystemBuffer +                             transferSize;                                     // Update the new transferSize:        transferSize = bytesRemaining;        if ( transferSize > maxTransfer )               transferSize = maxTransfer;        // Build the IRP stack beneath us (again)           pNextIrpStack->MajorFunction = IRP_MJ_WRITE;                      pNextIrpStack->Parameters.Write.Length =               transferSize;                          // Set up so we get called again:           IoSetCompletionRoutine(                     pIrp,                     WriteCompletion,                     NULL,                     TRUE, TRUE, TRUE );                                // Now pass it down:           IoCallDriver(               pFilterExt->pTargetDevice,               pIrp );                   return STATUS_MORE_PROCESSING_REQUIRED;     } else {    // There was either an error on the last xfer, or    // we're done. Either way, complete the IRP.        // Restore the original system buffer address:    pIrp->AssociatedIrp.SystemBuffer = (PVOID)        pIrpStack->Parameters.Write.ByteOffset.LowPart;            // Show the total number of bytes xfered:    pIrp->IoStatus.Information =        bytesRequested - bytesRemaining;             // See if the pending mark should be bubbled:     if ( pIrp->PendingReturned )         IoMarkIrpPending( pIrp );              return STATUS_SUCCESS;   } } 
GenericCompletion

When a pass-through request completes, this completion routine checks the IRP returned by the lower level to see if it has been marked PendingReturned. If so, it marks the (now current) filter IRP stack location as pending.

 NTSTATUS GenericCompletion(                IN PDEVICE_OBJECT pDevObj,                IN PIRP pIrp,                IN PVOID pContext ) {                   if ( pIrp->PendingReturned )     IoMarkIrpPending( pIrp );        return STATUS_SUCCESS; } 
< BACK  NEXT >


The Windows 2000 Device Driver Book(c) A Guide for Programmers
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
ISBN: 0130204315
EAN: 2147483647
Year: 2000
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net