[Previous] [Next]
Chapter 9
In the
[Previous] [
The Windows Driver Model assumes that a hardware device can have several drivers that each contribute in some way to the successful management of the device. The WDM accomplishes the layering of drivers by means of a stack of device objects. I discussed this concept in Chapter 2, "Basic Structure of a WDM Driver." Up until now, I've been talking exclusively about the
function driver
that
A filter driver that's above the function driver is called an
upper filter driver;
a filter driver that's below the function driver (but still above the bus driver) is called a
lower filter driver.
The mechanics of building either type of filter are exactly the same, even though the drivers
The intended purpose of an upper filter driver is to facilitate supporting a device that behaves in most respects like a generic device of its class but that has some additional functionality. You can rely, perhaps, on a generic function driver to support the generic behavior. To deal with the extra functionality, you write an upper filter driver to intervene in the flow of I/O
Figure 9-1. Role of an upper filter driver.
Another use for upper filter drivers is to compensate for
Lower filter drivers can't intervene in the normal operation of a device with which the function driver communicates directly. That's because the function driver will implement most substantive requests by making hardware abstraction layer (HAL) calls that directly access the hardware. The filter driver, of course, sees only those IRPs that something above chooses to pass down to it, and it never
A lower filter driver might find employment in the stack of drivers for a USB (universal serial bus) device, however. For such devices, the function driver uses internal control IRPs as containers for USB request blocks (URBs). A lower filter driver could monitor and modify these IRPs, perhaps. See Figure 9-2.
Figure 9-2. Role of a lower filter driver.
Another possible use for a lower filter driver, suggested by one of my seminar students, is to help you write a bus-independent driver. Imagine a device packaged as a PCI (Peripheral Component Interconnect) expansion card, a PCMCIA (Personal Computer Memory Card International Association) card, a USB device, and so on. You could write a function driver that is totally independent of the bus architecture, except that it wouldn't be able to talk to the device. You'd also write several lower filter drivers, one for each possible bus architecture, as
Figure 9-3. Using lower filter drivers to achieve bus independence.
The DriverEntry routine for a filter driver is very similar to that for a function driver. The major difference is that a filter driver must install dispatch routines for every type of IRP, not just for the types of IRP it expects to handle:
extern"C"NTSTATUSDriverEntry(PDRIVER_OBJECTDriverObject,
PUNICODE_STRINGRegistryPath)
{
DriverObject->DriverUnload=DriverUnload;
DriverObject->DriverExtension->AddDevice=AddDevice;
for(inti=0;i<arraysize(DriverObject->MajorFunction);++i)
DriverObject->MajorFunction[i]=DispatchAny;
DriverObject->MajorFunction[IRP_MJ_POWER]=DispatchPower;
DriverObject->MajorFunction[IRP_MJ_PNP]=DispatchPnp;
returnSTATUS_SUCCESS;
}
|
A filter driver has a DriverUnload and an AddDevice function just as any other driver does. I filled the major function table with the address of a routine named DispatchAny that would pass any random request down the stack. I specified specific dispatch routines for power and Plug and Play (PnP) requests.
The reason that a filter driver has to handle every conceivable type of IRP has to do with the order in which driver AddDevice functions get called vis--vis DriverEntry. In general, a filter driver has to support all the same IRP types that the driver immediately underneath it supports. If a filter were to leave a particular MajorFunction table entry in its default state, IRPs of that type would get failed with STATUS_INVALID_DEVICE_REQUEST. (The I/O Manager includes a default dispatch function that simply completes a request with this status. The driver object initially comes to you with all the MajorFunction table entries pointing to that default routine.) But you won't know until AddDevice time which device object(s) are underneath you. You could investigate the dispatch table for each lower device driver inside AddDevice and plug in the needed dispatch pointers in your own MajorFunction table, but remember that you might be in multiple device stacks, so you might get multiple AddDevice calls. It's easier to just declare support for all IRPs at DriverEntry time.
Filter drivers have AddDevice functions that get called for each appropriate piece of hardware. You'll be calling IoCreateDevice to create an unnamed device object and IoAttachDeviceToDeviceStack to plug in to the driver stack. In addition, you'll need to copy a few settings from the device object underneath you:
NTSTATUSAddDevice(PDRIVER_OBJECTDriverObject,PDEVICE_OBJECTpdo)
{
PDEVICE_OBJECTfido;
NTSTATUSstatus=IoCreateDevice(DriverObject,
sizeof(DEVICE_EXTENSION),NULL,FILE_DEVICE_UNKNOWN,
,FALSE,&fido);
if(!NT_SUCCESS(status))
returnstatus;
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fido->DeviceExtension;
_ _try
{
pdx->DeviceObject=fido;
pdx->Pdo=pdo;
PDEVICE_OBJECTfdo=IoAttachDeviceToDeviceStack(fido,pdo);
pdx->LowerDeviceObject=fdo;
fido->Flags=fdo->Flags&
(DO_DIRECT_IODO_BUFFERED_IODO_POWER_PAGABLE
DO_POWER_INRUSH);
fido->DeviceType=fdo->DeviceType;
fido->Characteristics=fdo->Characteristics;
fido->Flags&=~DO_DEVICE_INITIALIZING;
}
_ _finally
{
if(!NT_SUCCESS(status))
IoDeleteDevice(fido);
}
returnstatus;
}
|
The part that's different from a function driver is shown in boldface. Basically, we're propagating a few flag bits, the
DeviceType
value, and the
Characteristics
value from the device object next
NOTE
The reason I told you that you have to declare your choice of buffered versus direct I/O in AddDevice and that you can't change you mind afterward should now be clear: a filter driver might copy your settings at AddDevice time and won't have any way to know about a later change.
There's ordinarily no need for a filter device object (FiDO) to have its own
Do not use the FILE_DEVICE_SECURE_OPEN characteristics flag when you create a FiDO object. The PnP Manager propagates this flag, and a few others, up and down the device object stack. It's not your decision whether to enforce security checking on file opens—it's the function driver's and maybe the bus driver's.
You write a filter driver in the first place because you want to modify the behavior of a device in some way. Therefore, you'll have dispatch functions that do something with some of the IRPs that come your way. But you'll be passing most of the IRPs down the stack, and you pretty much know how to do this already:
NTSTATUSDispatchAny(PDEVICE_OBJECTfido,PIRPIrp)
{
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fido->DeviceExtension;
NTSTATUSstatus=IoAcquireRemoveLock(&pdx->RemoveLock,Irp);
if(!NT_SUCCESS(status))
returnCompleteRequest(Irp,status,0);
IoSkipCurrentIrpStackLocation(Irp);
status=IoCallDriver(pdx->LowerDeviceObject,Irp);
IoReleaseRemoveLock(&pdx->RemoveLock,Irp);
returnstatus;
}
NTSTATUSDispatchPnp(PDEVICE_OBJECTfido,PIRPIrp)
{
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fido->DeviceExtension;
NTSTATUSstatus=IoAcquireRemoveLock(&pdx->RemoveLock,Irp);
if(!NT_SUCCESS(status))
returnCompleteRequest(Irp,status,0);
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(Irp);
ULONGfcn=stack->MinorFunction;
IoSkipCurrentIrpStackLocation(Irp);
status=IoCallDriver(pdx->LowerDeviceObject,Irp);
if(fcn==IRP_MN_REMOVE_DEVICE)
{
IoReleaseRemoveLockAndWait(&pdx->RemoveLock,Irp);
IoDetachDevice(pdx->LowerDeviceObject);
IoDeleteDevice(fido);
}
else
IoReleaseRemoveLock(&pdx->RemoveLock,Irp);
returnstatus;
}
NTSTATUSDispatchPower(PDEVICE_OBJECTfido,PIRPIrp)
{
PoStartNextPowerIrp(Irp);
PDEVICE_EXTENSIONpdx=(PDEVICE_EXTENSION)fido->DeviceExtension;
NTSTATUSstatus=IoAcquireRemoveLock(&pdx->RemoveLock,Irp);
if(!NT_SUCCESS(status))
returnCompleteRequest(Irp,status,0);
IoSkipCurrentIrpStackLocation(Irp);
status=PoCallDriver(pdx->LowerDeviceObject,Irp);
IoReleaseRemoveLock(&pdx->RemoveLock,Irp);
returnstatus;
}
|
It's necessary, by the way, to acquire and release the remove lock for a filter driver's device object, as shown in these examples. The initial call to IoAcquireRemoveLock checks whether a device removal is currently pending for the FiDO. If so, the dispatch function fails the IRP immediately with STATUS_DELETE_PENDING, the only nonsuccess value that IoAcquireRemoveLock ever returns. While the filter owns its remove lock in one dispatch function, another thread that might be trying to process an IRP_MN_REMOVE_DEVICE inside DispatchPnp will block inside IoReleaseRemoveLockAndWait . What's thereby prevented is the call to IoDetachDevice , which might allow the lower device object to disappear. Our own device object is protected from deletion by a reference that was obtained by the caller before sending us this IRP—by using IoGetAttachedDeviceReference , for example.
Except for IRP_MJ_PNP, all dispatch functions in a filter driver need to be in nonpaged memory, and none should assume they're being called at PASSIVE_LEVEL. Here are two real-world examples of why this might matter. First, a lower filter for a USB device will be receiving and passing along IRP_MJ_INTERNAL_DEVICE_CONTROL requests that contain URBs. (See Chapter 11, "The Universal Serial Bus.") Some of these IRPs arrive at PASSIVE_LEVEL. Others might
NOTE
You should follow this guideline when you program a filter driver: First, do no harm. In other words, don't cause drivers above or below you to fail because you perturbed anything at all in their environment or in the flow of IRPs.

The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)

Windows System Programming (4th Edition) (Addison-Wesley Microsoft Technology Series)

Developing Drivers with the Windows Driver Foundation (Pro Developer)

Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)