OSRLogo
OSRLogoOSRLogoOSRLogo x Seminar Ad
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

A New Interface for Driver Writing -- The Windows Driver Framework


Most driver experts would agree: Writing drivers for Windows is, in general, much harder than it should be.

 

Some of the complexity inherent in writing Windows drivers today is due to the vast array of different driver models.  Some more is due to the ten-year long evolution of the Windows Driver Development Interfaces (DDI’s).  The driver model started out simply enough.  And then it grew.  And then Plug and Play was grafted on.  And then it grew some more.

 

The set of DDI’s we have today isn’t completely unmanageable if you grew up with it.  But have you ever had the responsibility of bringing a new Windows driver writer up to speed?  It’s a daunting task.

 

Well, you might be surprised to hear that the driver folks at Microsoft agree, and they’re planning to do something about it.  It looks like we’ll be getting a whole new way to write drivers.  Please keep in mind as you read this that everything in this article is very preliminary.  Things are likely to change substantially before release. Or, it’s even possible that some of the features described here might never be released.  Consider yourself warned.

 

Framework Concepts and Goals

The new driver model currently under development is called the “Windows Driver Framework.”  At its heart, it’s a library of C language functions that work at a higher level of abstraction than the existing WDM DDIs.  The Framework is object-based, in that operates on handles to objects, rather than pointers to those objects.  These objects have methods, properties, and events associated with them that a driver uses to operate on the objects (we’ll talk more about this object-based approach later in this article).

 

The goals of the framework are:

  • Simplicity
  • Conceptual Scalability
  • Flexibility
  • Diagnosability
  • Extensibility

 

Key to the Framework are the concepts of Simplicity and Conceptual Scalability.  One concept that was often discussed during the development of the Frameworks is the fact that in order to write a very simple Windows driver, you have to know a lot of “stuff.” Even worse, you have to know that “stuff” at a very detailed level.  A good example of this problem is the number of complex issues involved in forwarding an IRP and specifying a completion routine for that IRP.  Consider the implications of the lower driver returning STATUS_PENDING to the upper drivers call to IoCallDriver(…).  In the completion routine, do you include the following famous code sequence or not?

 

if  (Irp->PendingReturned)  {

IoMarkIrpPending(Irp)

       }

 

Do you always do this check in your completion routine (Answer: No)?  Are there cases where you should not do this check (Answer: Yes)?  But, I wonder, how many people really understand this issue?  In other words, there are few enough people who know the rules entailed in the above check.  There are even fewer who really understand the design and logic behind those rules.

 

However, instead of asking the questions above, a better question might be: Why should anybody need to understand complex concepts like this?

 

The Framework’s concepts of Simplicity and Conceptual Scalability imply that it should be easy for a driver writer to write a simple driver.

 

Once the driver’s written, suppose that driver writer wants to add a few more features or manage a few things manually that the Framework might otherwise handle automatically?  In this case, the concept of Conceptual Scalability dictates that the amount of “stuff” that driver writer needs to learn should be incremental.   In other words, the information that a driver writer needs to know to get his job done should be relative to the complexity of his implementation task.

 

This brings us to the Framework goals of Flexibility and Extensibility.  These goals imply that the Framework need to be able to grow to encompass all type of drivers, and that the Framework should be flexible enough to evolve as new types of devices evolve. 

 

Together, these four goals (Simplicity, Conceptual Scalability, Flexibility, and Extensibility) also imply another important feature: Drivers must be able to grow in complexity within the Framework.  That is, the Framework is not intended to be a “newbie interface.” The Framework needs to facilitate the development of all types of drivers, at all levels of complexity.  When building a Framework-based driver, a developer shouldn’t ever hit a “dead end”, and be required to start over from the beginning using the base WDM DDIs.  This goal isn’t as hard to achieve as it might at first sound, because the Framework has been developed to work within the overall WDM architecture, instead of as a total replacement for that architecture.

 

Finally, there’s the goal of Diagnosability.  The Framework must include built-in checking and tracing that helps identify and eliminate errors early.

 

But, that’s enough talk about goals and concepts.  Let’s take a look at some code!  Please keep in mind that our goal in this article is merely to introduce the Driver Framework, and give you a general idea of how it works.  We’re not even going to attempt a complete description, and it’s certainly far too early for us to do a comprehensive evaluation or critique.

 

Driver Initialization

To get a feel for the Driver Framework, let’s start by examining how you deal with driver initialization, and with adding and manipulating your driver’s functional Device Object.

 

As part of a Framework driver’s DriverEntry processing, you create an instance of your Driver’s Framework Driver Object.  You do this, not surprisingly, by calling the function DfwDriverCreate(…):

 

    status = DfwDriverCreate(DriverObject, // pointer to DRIVER_OBJECT

RegistryPath,                 // pointer to registry path

DriverAttribs,                // optional Dfw object attributes

DriverConfig,                 // pointer to driver config

DriverObjectHandle);    // where to store returned handle

 

The first two parameters, DriverObject and RegistryPath, are the standard parameters that are passed into any driver at its DriverEntry entry point.  The pointer to the driver object attributes structure is optional.  Framework Object Attributes allow for things such as generic per-object context and a callback on object destruction.

 

The most interesting parameter is the fourth: DriverConfig.  This is a pointer to a DFW_DRIVER_CONFIG structure that the driver has allocated and initialized appropriately.  This structure has the following format:

 

typedef struct _DFW_DRIVER_CONFIG {

 

    //

    // Size of this structure in bytes

    //

    ULONG Size;

    //

    // The size of the device extension for devices created automatically

    // by the frameworks.  (ie, during AddDevice)

    //

    USHORT DeviceExtensionSize;

    //

    // Size of the context associated with each DFWREQUST handle created

    // by the frameworks.

    //

    USHORT RequestContextSize;

    //

    // Event callbacks

    //

    DFW_DRIVER_OBJECT_CALLBACKS Events;

    ULONG DriverInitFlags;

    DFW_LOCKING_TYPE LockingConfig;

    DFW_THREADING_TYPE ThreadingConfig;

    DFW_SYNCHRONIZATION_TYPE SynchronizationConfig;

 

} DFW_DRIVER_CONFIG, *PDFW_DRIVER_CONFIG;

 

There are several things about this structure that are interesting.  Note that the size of the Device Extension is specified here.  The Framework will use this size for all Device Objects it creates.  Interestingly, you can also provide the size for a Request Context.  A Request is (basically) the Framework’s abstraction of an IRP.  The Request Context is a driver private per-Request (i.e. per IRP) storage location.

 

The next field in the DFW_DRIVER_CONFIG structure is the Events field.  You fill this in with pointers to you driver’s functions that you want the Framework to call under certain situations.  There are currently two such Events defined:

 

  • EvtDriverDeviceAdd – The function in your driver that is called back during AddDevice processing;

 

  • EvtDriverUnload – The function in your driver that is called back during driver unload processing.

 

The LockingConfig, ThreadingConfig, and SynchronizationConfig fields are used to describe the model that the Framework will use for the driver’s locking, threading, and serialization respectively. A complete description of these models would require an article for each.  However, let’s look at each briefly.

 

The LockingConfig parameter specifies a DFW_LOCKING_TYPE.  These types indicate the granularity of “automatic” locking that the Framework provides for the driver.  Options range from extremely simple and automatic driver-wide locking (that is, requests are serialized on a per-driver basis) to all locking being performed by the driver itself (that is, the Framework provides no automatic locking).  Between these two extremes reside the options that are most likely to be commonly chosen: device-level locking and queue-level locking.

 

The SynchronizationConfig parameter appears to describe how the external synchronization of the driver is achieved (we say “appears to describe” as the documentation released to date is incomplete on this topic).

 

The ThreadingConfig parameter indicates the threading model the Frameworks should implement.  Choices here are simple: Asynchronous or Synchronous.  The asynchronous model is similar to that used natively by WDM.  The synchronous model allows the driver to block during request processing, and guarantees that almost all callbacks to the driver will be made at IRQL PASSIVE_LEVEL, in the context of a system worker thread. 

 

Let’s review where we’ve gotten so far.  During DriverEntry, a Framework’s driver will create an instance of its Framework Driver Object by calling DfwDriverCreate.  This function returns a handle to the Framework Driver Object.  The major input parameter to DfwDriverCreate is a pointer to a DFW_DRIVER_CONFIG structure.  This structure contains a description of the driver’s attributes including its model for serialization and threading.  It also contains pointers to driver-supplied callbacks for driver-wide event processing, such a AddDevice and Unload.

 

Because the DFW_DRIVER_CONFIG structure has a significant number of fields, the Framework provides a macro to facilitate initialization of the structure.  That macro is DFW_DRIVER_CONFIG_INIT.  The use of this function is shown in the following code sample from DriverEntry:

 

 

NTSTATUS

DriverEntry(IN PDRIVER_OBJECT  DriverObject,

            IN PUNICODE_STRING RegistryPath)

{

    DFW_DRIVER_CONFIG driverConfig;

    DFWDRIVER hDriver;

    DFWSTATUS status;

       ...

 

    DFW_DRIVER_CONFIG_INIT(

        &driverConfig,                   // ptr to structure to init

        sizeof(MY_DEVICE_EXTENSION),

        0,

        AddDeviceCallback,               // Driver-supplied function

        UnloadCallback,                  // Driver-supplied function

        0,                               // flags - TBD

        DfwLockingDevice,                // Locking model: per device

        DfwThreadingAsynchronous,        // Threading model: asynch

        DfwSynchronizationNone );        // Synch model: none

 

    //

    // Create the instance of the Framework’s Driver Object

    //

    status = DfwDriverCreate(DriverObject,

RegistryPath,

NULL,

&driverConfig,

&hDriver);

 

    return(status);

}

 

The code sample above shows how a Framework’s Driver Object is initialized and instantiated.  Note that per-device locking has been chosen.  This will cause the Framework to automatically serialize requests on a per-device basis.  Also note that the threading model chosen is the WDM-standard asynchronous model.  Further, callbacks have been specified for both driver-wide AddDevice and Unload processing.

 

Device Initialization

Are you getting a feel for Frameworks yet?  Let’s look at one more example, the instantiation, initialization, and use of the Frameworks Device Object.

 

After DriverEntry executes, the next Framework event to occur is that the Framework Driver Object’s Add Device callback will be called.  A pointer to this function was supplied in the DFW_DRIVER_CONFIG structure that was passed into DfwDriverCreate during DriverEntry processing.  The prototype for the Framework’s Add Device event callback is:

 

DFWSTATUS

DrvDriverDeviceAdd(

IN DFWDRIVER hDriver,

IN DFWDEVICE hDevice)

 

Note that when a Framework driver’s Add Device callback is called, the Framework’s Device Object representing the driver’s WDM FDO has already been created.  But, fear not, there’s still opportunity to set the Device Object’s various properties (such as device name, type, characteristics, and I/O transfer type).  Each of these properties is settable using a Framework-supplied method.  Once the Framework Device Object’s properties have been appropriately set, the Framework Device Object is initialized, as shown in the following example:

 

DfwDeviceSetDeviceName(hDevice, L"\\Device\\OsrDevice");

DfwDeviceSetDeviceType(hDevice, FILE_DEVICE_OSR_TYPE);

 

status = DfwDeviceInitialize(hDevice);

 

if (!NT_SUCCESS(status) ) {

       return(status);

}

 

The example shows the device type and device name properties being set, and then the Framework Device Object being initialized.

 

Before the more security conscious among you flip-out and ask why we didn’t set FILE_DEVICE_SECURE_OPEN, note that the Framework presently appears to set FILE_DEVICE_SECURE_OPEN in the device characteristics by default.

 

Also, note that in addition to (or instead of) a device name, a device interface may also be registered (using DfwDeviceCreateDeviceInterface).

 

Processing in the Add Device callback continues in a pattern similar to that in DriverEntry.  Since we’re dealing with our WDM FDO here, the next thing that happens in Add Device is that device-specific callbacks into our driver are provided.  This is done by appropriately initializing a DFW_FDO_EVENT_CALLBACKS structure, as follows:

 

DFW_FDO_EVENT_CALLBACKS FdoCallbacks;

 

DFW_FDO_EVENT_CALLBACKS_INIT(&FdoCallbacks);

 

FdoCallbacks.EvtDeviceStart = StartDeviceCallback;

FdoCallbacks.EvtDeviceRemove = RemoveDeviceCallback;

FdoCallbacks.EvtDeviceCanSuspend = CanSuspendDeviceCallback;

FdoCallbacks.EvtDeviceSetPowerState = SetPowerDeviceCallback;

 

These callback are registered by calling the function DfwDeviceRegisterFdoCallbacks, as follows:

 

status = DfwDeviceRegisterFdoCallbacks(hDevice, &FdoCallbacks);

 

Then There Are Queues

Before leaving the Framework’s Add Device callback, a driver will also want to initialize its per-device request queues.  Once again, the Framework provides an abstraction for I/O request queues, including a selection of Queue Dispatching Models.  These Dispatching Models indicate how requests are handled on arrival by the Framework.  If the Serial Dispatching Model is specified, the Framework driver is only called with one I/O request at a time per device. If the Parallel Model is specified, the Framework driver will be called as each request arrives.  There’s also a choice of a Manual Dispatch Model, where the Framework driver can do just about whatever it wants to.

 

The driver registers I/O request processing callbacks (what we’d typically think of as dispatch entry points) at the same time it initializes and specifies attributes for its per-device I/O queues.  Drivers may register callbacks for Read, Write, Device Control, and Internal Device Control.  In addition to (or instead of) these type-specific callbacks, a Framework driver may register a generic IoStart callback.  IoStart is used whenever an I/O request type-specific callback hasn’t been specified by a driver.  This information is passed to the Framework using the DFW_IO_QUEUE_CONFIGURATION structure.

 

A really simple driver could request that for any I/O request type that’s received (read, write, device control, or anything else) it be called back at its IoStart entry point with no more than one I/O request at a time.  This could be done as follows:

 

DFW_IO_QUEUE_CONFIGURATION_INIT(

    &ioEvents,                    // ptr to structure to init

    DfwIoQueueDispatchSerial,     // Dispatch model

    IoStartCallback,       // driver-supplied callback

    NULL);                        //  no cancel callback provided

 

 

status = DfwDeviceRegisterIoCallbacks(

         hDevice,                 // handle to device

         &ioEvents,               // ptr to DFW_IO_QUEUE_CONFIGURATION

         NULL,                    // ptr to object attributes

         &hQueue);                // receives queue handle on output

 

The example above specifies a queue using the Serial Queue Dispatch model, and one I/O callback into the driver named IoStartCallback.  The I/O processing callbacks for this queue are registered by calling DfwDeviceRegisterIoCallbacks, specifying the handle to the Frameworks Device Object that owns the queue, and a pointer to a DFW_IO_QUEUE_CONFIGURATION structure that has been appropriately initialized.  The handle to the created I/O request queue is returned.

 

To allow the IoStartCallback to be called as requests are received (that is, potentially in parallel), that same driver need only change the specification of DfwIoQueueDispatchSerial to DfwIoQueueDispatchParallel.  This is an example of how “Conceptual Scalability” is achieved using the Framework.  Also, as mentioned previously, a Framework driver may choose to be called at different entry points for read, write, and device control.  This can be accomplished by filling in the appropriate fields of  the DFW_IO_QUEUE_CONFIGURATION structure prior to calling DfwDeviceRegisterIoCallbacks, as shown below:

 

IoEvents.EvtIoRead = IoReadCallback;

IoEvents.EvtIoWrite = IoWriteCallback;

 

If the two lines above were added just before the call to DfwDeviceRegisterIoCallbacks in the previous example, the Framework driver would be called at IoReadCallback when there was a read request to process; it would be called back at it’s IoWriteCallback function when there was a write request to process; and would be called at its IoStartCallback when any other type of I/O request was ready to be processed.  Assuming DfwIoQueueDispatchSerial was specified, the driver would only be called with one request at a time per device, regardless of which callback was called.  That is, if the Framework had called the driver’s IoWriteCallback function, and a device control request arrived, the device control request would be queued until all previous requests on the device (of any type) were complete.

 

And I/O Dispatch

The last Driver Framework function we’ll look at is the I/O Processing callback.  This callback, which is specified in the DFW_IO_QUEUE_CONFIGURATION has the following prototype:

 

VOID

IoReadCallback(IN DFWQUEUE Queue,

    IN DFWREQUEST Request,

    IN DFWFILEOBJECT FileObject,

    IN LARGE_INTEGER ByteOffset,

    IN ULONG Length,

    IN ULONG Key)

 

Note that the Framework passes the driver a queue handle and a request handle.  It also passes the major parameters likely to be of interest to the driver.  Other parameters may be retrieved from the Request Object as required.

 

A driver typically initiates I/O processing in its event callbacks.  All I/O processing event callbacks are “void” – That is, they return no status value.

 

A driver completes Framework requests by calling DfwRequestComplete or DfwRequestCompleteEx.  The only difference between these two functions is that DfwRequestCompleteEx allows the driver to specify the contents of the information field to be returned when the I/O request is completed.  Thus, the IoStart I/O processing event callback function for a simple driver might resemble the following:

 

VOID

IoStartCallback(IN DFWQUEUE Queue,

    IN DFWREQUEST Request,

    IN DFWFILEOBJECT FileObject,

    IN LARGE_INTEGER ByteOffset,

    IN ULONG Length,

    IN ULONG Key)

{

 

    if ( Length == 0 ) {

        DfwRequestCompleteEx(Request, STATUS_SUCCESS, (ULONG_PTR)0L);

        return;

    }

 

    MyDriverStartRequestOnDevice(Request, Length);

}

 

 

Finding Out More

You’ll undoubtedly be hearing lots more about the Windows Driver Framework.   A preview of the Framework is available as part of the WinHEC DDK.  We hope that this brief introduction has at least given you a bit of a feel for the Framework.  You can count on The NT Insider to have lots more information for you on this topic in the future.

 

 

Related Articles
You're Testing Me - Testing WDM/Win2K Drivers
Who Cares? You Do! - Implementing PnP for WDM/NT V5
Converting Windows NT V4 Drivers to WDM/Win2K
Simplifying Time Interval Specification
WDF PnP/Power Interview with Microsoft's Jake Oshins
I/O Manager & Vista
Starting Out: Should You Learn WDM or WDF?
KMDF Support for USB Devices
Ten Things You Need To Know About KMDF
When is a Queue not just a Queue? WDFQUEUEs

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

Post Your Comments.
Print this article.
Email this article.
bottom nav links