NOTE:It is assumed that the reader has some familiarity with Windows XP, PNP, the concept of Function Device Objects (FDOs) and Physical Device Objects (PDOs), and of “.INF” files.

This article is the second in a series on how to write a driver for Windows that implements the functionality of a bus driver.This article will deal with the following issues:

Distinguishing a FDO request from a PDO request

DriverEntry and AddDevice

IoRegisterDeviceInterface

Distinguishing a FDO request from a PDO request

One of the first big issues that the bus driver writer must address is the creation and management of two different types of device objects.On the one hand, we have the Functional Device Object (FDO), which may or may not be receiving Non-PNP IRPs, depending on the design and purpose of your driver.On the other hand, we have Physical Device Objects (PDOs), which have been created to represent devices that have been found on our “bus” (and could also be responsible for processing Non-PNP IRPs).So how do we distinguish between the two types of objects?The simplest solution is to have a common structure at the start of both the FDO and PDO device extensions that a dispatch routine could look at in order to determine which object the IRP was targeted to.That common structure may look something like that shown in Figure 1a.

Typedef struct _COMMON_DEV_EXT {

BOOLEAN IsFdo;

} COMMON_DEV_EXT, *PCOMMON_DEV_EXT;

Figure 1a – Common Structure for FDO and PDO Device Extensions

In which case our driver dispatch routines need only look like Figure 1b.

NTSTATUS FdoDispatchRoutine(PDEVICE_OBJECT PDevObject, PIRP PIrp) {

PDEV_EXT devExt = (PDEV_EXT) PDevObject->DeviceExtenstion;

If(devExt->CommonDevExt.IsFdo) {

return FdoDispatchRoutine(PDevObject,PIrp);

}

// Do Pdo functionality…….

return status;

}

Figure 1b – Dispatch Routine

While this is a workable solution, it is not the most flexible.It is conceivable that the bus driver writer may want to have different processing routines for the different types of PDOs that may be found on the bus.Consider our example in Part I of this article concerning the “FredBus” that could have PDOs presenting either the “Fred Toaster” or “FredOven” device.If I want different processing routines for the Toaster and the Oven devices, my PdoDispatch routine is going to have to have some additional code to determine which type of PDO is targeted so that it can handle it accordingly.

A better solution may be to use the following common device extension (Figure 2a) at the start of all PDO and FDO device extensions.

So for the bus driver that we will be writing in this series of articles, we will be using the following common device extension (Figure 3) that will precede the specific portions of the device extensions to be used for the FDO and PDOs that we will create.

Typedef struct _COMMON_DEV_EXT {

// Magic Number used to validate structure…

ULONGMagicNumber;

// A backpointer to the device object for which this is the extension

PDEVICE_OBJECTSelf;

// This flag helps distinguish between PDO and FDO

BOOLEANIsFDO;

// We track the state of the device with every PnP irp

// that affects the device through this variable.

DEVICE_PNP_STATEDevicePnPState;

// Stores the current system power state

SYSTEM_POWER_STATESystemPowerState;

// Stores current device power state

DEVICE_POWER_STATEDevicePowerState;

// Function Dispatch Table

PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1];

} COMMON_DEV_EXT, *PCOMMON_DEV_EXT;

Figure 3– Our Bus Driver Common Device Extension

Also notice that we have used the COMMON_DEV_EXT to contain some information that is necessary for all PnP devices, e.g., SYSTEM_POWER_STATE, DEVICE_POWER_STATE, and our DEVICE_PNP_STATE which is an enumeration that I have created to keep track of our devices PnP state.It is defined in Figure 4.

typedef enum _DEVICE_PNP_STATE {

NotStarted = 0,// Not started yet

Started,// Device has received the START_DEVICE IRP

StopPending,// Device has received the QUERY_STOP IRP

Stopped,// Device has received the STOP_DEVICE IRP

RemovePending,// Device has received the QUERY_REMOVE IRP

SurpriseRemovePending,// Device has received the SURPRISE_REMOVE IRP

Deleted// Device has received the REMOVE_DEVICE IRP

} DEVICE_PNP_STATE;

When we get around to talking about our PnP state machine, you will see that all the enums defined here will match the PnP states that our state machine will contain.

Driver Entry and AddDevice

Now that we have decided about how we’re going to distinguish between our FDO and PDOs, it is time to start laying the foundation for our driver.Naturally, the two functions that we’re going to have to add for our driver are DriverEntry and AddDevice. As all driver writers know, DriverEntry is the first routine called when the driver is loaded and its purpose is to export all the necessary entry points that the driver needs in order to accomplish its goals.It must also do any driver wide initialization that may be necessary, for example allocating memory to store the received RegistryPath for later use.For our driver, DriverEntry is shown in Figure 5.

Now that we have that done, we can move on to our AddDevice routine.AddDevice is called when the PnP Manager has determined that a PDO, that it has been notified about, has been determined to be owned by the driver being called.The PDO could be for an actual physical device, if we were servicing a physical adapter or it could be for a virtual physical device that the PnP Manager created for us if our “.INF” file said that we were “ROOT” enumerated.The purpose of AddDevice (shown in Figure 6) is to create a FDO, representing the functionality that our device, and attach it to the PDO which represents the physical side of our device.Together the FDO and PDO represent an “operational device” or a DEVNODE (Device Node).Remember, because we are attached to the PDO, we are actually a filter driver of the PDO, therefore we will see all the PnP and Power IRPs that were targeted at the PDO.Is this a good thing?Sure it is. The functionality of a device is affected by changes in its physical state.

When we call IoCreateDevice to create the FDO, you can see that for the second parameter we pass in sizeof(FDO_DEVEXT).This is the Device Extension for our FDO which contains the COMMON_DEV_EXT (located as the first element of the structure) that both our FDO and subsequent PDOs will contain.In our FDO device extension we have added an OutstandingIO count that we set to “1”.We use this count in conjunction with the StopEvent and RemoveEvent variables in order to allow us to be able to detect whether all our outstanding IO requests have completed.This, in turn, allows us to properly handle IRP_MJ_PNP, IRP_MN_QUERY_STOP and IRP_MN_QUERY_REMOVE requests.Whenever we have to pass a request to a driver below us, we increment the OutstandingIO by one and if the new count is 2 we reset the signal state of the Stop and RemoveEvents to non-signaled.Whenever an outstanding IO completes we’ll decrement the OutstandingIO count.Whenever the decremented count is 1, we set the StopEvent, and if the decremented count is 0 we set the RemoveEvent.

The last thing that I want to talk about in this article is IoRegisterDeviceInterface

IoRegisterDeviceInterface

Microsoft added IoRegisterDeviceInterface in Windows 2000 to correct a problem that the style of naming used in NTV4 could not address.As you probably remember in NTV4, device driver writers used a convention whereby their device objects where named DevX where X is a number starting at 0 and going to N.When a user wanted to find out how many devices there were, they had to start at Dev0 and continue enumerating devices until they tried to open up a device that did not exist.With PnP, this style of naming does not work because it could be possible that Dev0 and Dev2 exist but Dev1 doesn’t exist because it was removed.Also, with the old style of naming it was not easy for developers of devices that offered the same services to allow their users to easily switch their applications to use another vendor’s device without changing the names that the application used in order to open the device.

So Microsoft took an idea from the Component Object Model (COM) and added it to device driver writing.Essentially, with IoRegisterDeviceInterface, a device registers using a GUID (Globally Unique Identifier) of the type of device it represents.It could be a common GUID like GUID_DEVINTERFACE_COMPORT or GUID_DEVINTERFACE_DISK or it could be a custom one like GUID_DEVINTERFACE_OSRTOASTER.Devices that register using these interface GUIDS agree to support the functionality that they imply.For example registering with GUID_DEVINTERFACE_COMPORT implies that the registrant supports all the functionality that a COM port must provide in Windows while registrants of GUID_DEVINTERACE_OSRTOASTER agree to support all the functionality that being an OSRTOASTER implies (We don’t let the toast get burnt or stuck in the toaster!).Users that want to use a device that supports a specific type of interface can use the new SetUpDi interface in order to enumerate and open up all devices that register supporting a specific type of device interface.What’s also nice is that unlike NTV4 style device naming, PnP device interfaces have state either “enabled” or “disabled”.A PnP driver can enable the interface when it is ready to receive user requests and can disable it if goes into a start where it does not want to receive user requests.Is that nice or what?

Summary

In this article we talked about continuing the development of our virtual bus driver by discussing: Distinguishing a FDO request from a PDO request, DriverEntry and AddDevice, and IoRegisterDeviceInterface.All three of these topics are necessary to discuss in order to develop a proper and functional Bus Driver.

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

"Bus Driver FDO creation"
So my bus has created and enumerated PDO's for each child. Why is DriverEntry called for each PDO prior to calling AddDevice and is there a mechanism to simply call AddDevice for requesting each subsequent child PDO to create a FDO?