OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Fri, 20 Oct 2017     115100 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
Downloads
ListServer / Forum
Driver Jobs
  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 Soft Life -- Implementing Software-Only Drivers Using KMDF

 Click Here to Download: Code Associated With This Article   Zip Archive, 21KB

I have a confession to make, right here, right now, before whatever deity you believe in and all fifteen-or-so thousand readers of The NT Insider.  Ready?  Here's my confession: I have never fully understood WDM power management.

Oh, sure, I've written WDM drivers that have power management code in them. And that code seems like it probably works, at least most times.  Heck, I even teach a three and a half hour lecture on power management in our Advanced WDM Driver Lab seminar.   I "get" the whole S-IRP and D-IRP and power policy owner thing.  I know the rules for dealing with IRP_MN_QUERY and IRP_MN_SET. But I don't really understand any of it, at least not the way I understand, say, how the Memory Manager maps a frame buffer into kernel virtual address space, or the way the kernel interrupt dispatcher deals with interrupt service routines.

Each time I look to the DDK documentation to help lift me out of my ignorance, I am haunted by the opening lines of Lao Tzu's Tao Te Ching, the ancient Chinese book of wisdom. "The way that can be written," the Tao says, "is not the true way."  Who would have imagined that they would have known about WDM as far back as 600 BCE?

I am comforted that some of the smartest people I know seem to know less and less about WDM power management the more time they spend working with it.  I am starting to believe that I have come face-to-face with the universal unknowable.  "Auuummm.... Exactly what level of synchronization is required between power management and PnP.... Aummmm...."

If you haven't stopped reading by now, you're probably wondering what all this has to do with writing WDF software-only drivers.  Well, actually, it has a lot to do with it, and it leads me to the second part of my confession:  The fact that I'm supposed to implement a full-blown power policy owner whenever I write a root-enumerated software-only WDM driver has always pissed me off.  Get the S-IRP, send the D-IRP.  And, with no hardware to control, why is it that I'm supposed to do all this?  Yes, I know the standard answers, thanks very much: To keep the bus driver happy and blah blah.

And that leads me to the third and final part of my confession:  Frankly, in this particular case (a root-enumerated software-only WDM driver), I've never really believed that implementing all that power policy owner code was actually necessary.  Oh, sure, I tell my students they have to do it.  And I ardently lecture the poor souls on NTDEV who have the audacity to ask if such a thing is required. But, when it comes down to me, in the privacy of my own office, implementing one of these drivers?  Well, let's just say that I take quite a few shortcuts--one or two significant liberties, as it were.  And no, I'm not going to tell you what they are, because if I do, people like Jake Oshins will come into my room in the dark of night, cut off my nether parts, and feed them to the beast that inhabits building 7.

And now, having confessed all my manifest sins and wickedness before all and sundry, do I feel clean?  Redeemed?  Nah, not really.  Because I know that the weird little game I secretly play with power management IRPs in this special sub-class of drivers is just one hotfix or service pack away from failing in the field.  And I won't feel clever then.

Fortunately, redemption is at hand.  I can now legitimately write software-only drivers and not have to write a lick of power management code.  And, to top if all off, I'll be able to sleep nights knowing that I haven't played fast and loose with the rules.

Go, and Sin No More
And that brings me to writing software-only drivers using the WDF Kernel Mode Driver Framework.   In the KMDF, software-only drivers are fully PnP and power management capable by default.  A software-only KMDF driver doesn't have to do anything at all to get a complete and correct power management implementation. Huzzah!

Imagine: You can whip out a quick software-based driver that's fully PnP and power management compliant, and not have to copy/modify/fix that 1200+ lines of WDM power code that you've never really liked the looks of.  It's like going back to the days before Windows 2000, only more object-oriented.

If KMDF drivers don't have to implement any of their own power management code, how is the power policy owner thing handled, you ask?  When a root-enumerated software-only driver creates an FDO, the KMDF defaults to assuming the driver will be the power policy owner in the stack.  As a result, the KMDF automagically creates and manages the necessary PnP, power management, and power policy state machines on the driver's behalf.  The framework receives any arriving PnP and power management IRPs, and just...  deals with them appropriately.

What about filter drivers, you ask?  Filter drivers have never been quite as much of a pain in terms of power management as software-only drivers that are root-enumerated.  But even in a WDM filter driver, there's plenty of chance to screw up.  When you write a KMDF filter driver, the Framework defaults to assuming that your driver will not be the power policy owner for its stack. And, once again, the Framework creates and manages  the appropriate PnP and power management state machines for the driver.  Before you can ask: Of course, all this is configurable.  You want a filter driver to be the power policy owner in the stack?  No probs, just call WdfDeviceInitSetPowerPolicyOwnership. 

Pass the IRPs, Please
If your visit to the WC is not yet finished, and hence you are still reading this article, you're probably thinking that there's more to a driver than just handling PnP and power  management IRPs.  After all, there are all those IRPs like read, write, and device control. How are those handled in a KMDF software-only driver?

You may recall from other articles that you've read that the KMDF delivers I/O requests to a driver's event processing functions in the form of WDFREQUESTs.  The Framework sorts these requests using WDFQUEUEs that are established by the driver.

By default, any WDFQUEUEs created by a driver are power managed.  This means that the Framework automatically stops delivering requests to a driver's event processing functions for a device whenever that device enters a powered-down state.  This is almost always a good thing--it's not helpful to receive I/O requests that require access to your device hardware when that hardware is powered down.

One-time automatic power management for WDFQUEUEs is not helpful in a filter driver.  Filter drivers don't really have any hardware that's directly associated with their WDFDEVICEs, and thus typically shouldn't hold requests in their queues while the device they are filtering is powered down.  Fortunately, it's pretty easy for a driver to tell KMDF not to perform power management on its queues: The driver simply sets the PowerManaged field of its WDF_IO_ QUEUE_CONFIG structure to FALSE.

Prove It!
Do you doubt it's as easy as am I'm saying it is?  Do you need me to prove to you that writing a software-only KMDF driver entails zero power management headaches?   Well, if I must....

In the next few sections of this article, I will walk you through every line of a KMDF root-enumerated software-only driver called the NOTHING driver.  Not to worry, however, because it's not even 200 lines of code; 300 lines if you insist on counting the comments.  Not only that, but because I'm such a nice guy (and because I quite literally wrote this article and the entire driver on the plane while flying from Boston to Seattle) I'm going to let you download the complete driver from The NT Insider section of OSR Online.  So, let's get to it.

DriverEntry
Let's start where every Windows driver starts: DriverEntry.  Figure 1 contains the code for this first entry point in our KMDF-based NOTHING driver.

NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegistryPath)
{
   
NTSTATUS code;
   
WDF_DRIVER_CONFIG driverConfig;

#if DBG
   
DbgPrint("\nWDFNOTHING Driver V3.0 -- Compiled %s %s\n",
                                           
__DATE__, __TIME__);
#endif

    //
   
// Initialize the Driver Config structure:
   
//      Specify our Add Device event callback.
   
//
   
WDF_DRIVER_CONFIG_INIT(&driverConfig, NothingEvtDeviceAdd);

   
//
   
// Create a WDFDRIVER object
   
//
   
// We specify no object attributes, because we do not need a
   
// cleanup or destroy event callback, or any per-driver context.
   
//
   
code = WdfDriverCreate(DriverObj,
                       RegistryPath, 
                       WDF_NO_OBJECT_ATTRIBUTES,
                       &driverConfig, // Ptr to our config
                                     
// structure
                      
NULL);         // Ptr to return drv obj
                                     
// handle

#if DBG
   
if (!NT_SUCCESS(code)) {
       
DbgPrint("WdfDriverCreate failed with status 0x%0x\n",
       
code);
   
}

   
DbgPrint("DriverEntry: Leaving\n");
#endif

   
return(code);
}

 Figure 1 -- DriverEntry

The NOTHING driver's DriverEntry routine starts with a DbgPrint statement (herein after, I'm going to leave out references to these statements, because they have no bearing on the driver itself or the flow of control).  The first KMDF-related thing the driver does is initialize its WDF_ DRIVER_CONFIG structure, using the WDF_DRIVER_ CONFIG_INIT macro, providing a pointer to its device add event processing routine, NothingEvtDeviceAdd.  This is the function that the Framework will call whenever the PnP Manager detects a new device that we control.  In WDF, a driver's device add event processing routine is similar to a WDM driver's AddDevice function.

The next thing that the NOTHING driver does is initialize the Framework by creating a WDFDRIVER object.  It does this by calling WdfDriverCreate, passing in the driver's registry path (which was provided as a parameter to DriverEntry, as it always is in Windows) and a pointer to the WDF_ DRIVER_CONFIG structure that the driver previously initialized.

Assuming that the WdfDriverCreate call returns success, the driver returns from DriverEntry.  The next entry point called will be the driver's device add event processing routine.

Device Add
WDF/KMDF and WDM have one thing in common: Most of the code for a simple driver can be found in their Device Add or AddDevice routines, respectively.  Figure 2 shows the NOTHING driver's device add event processing routine.

NTSTATUS
NothingEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
{
    NTSTATUS code;
    WDF_OBJECT_ATTRIBUTES objAttributes;
    WDFDEVICE device;
    WDF_IO_QUEUE_CONFIG ioCallbacks;
    

    DECLARE_CONST_UNICODE_STRING(ntDeviceName,           
         
L
\\device\\NOTHING
);
    DECLARE_CONST_UNICODE_STRING(dosDeviceName,           
         
L
\\DosDevices\\NOTHING
);

   
UNREFERENCED_PARAMETER(Driver);
 

    //
    // Create our Device Object and its associated context
    //
   WDF_OBJECT_ATTRIBUTES_INIT(&objAttributes);
 

    //
    // Associate our device context structure type with our 
    // WDFDevice
    //
    WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&objAttributes,
                                          NOTHING_DEVICE_CONTEXT);
 
    //
    // We want our device object NAMED, thank you very much
    //
    code = WdfDeviceInitAssignName(DeviceInit, &ntDeviceName);

    if (!NT_SUCCESS(code)) {
#if DBG
        DbgPrint("WdfDeviceInitAssignName failed 0x%0x\n", code);
#endif
        return(code);
    }

    //
  
// Create the device now
    //
    code = WdfDeviceCreate(&DeviceInit,   // Device Init structure
               &objAttributes, // Attributes for WDF Device
               &device);      // returns pointer to new WDF Device
 
    if ( !NT_SUCCESS(code)) {
#if DBG
        DbgPrint("WdfDeviceInitialize failed 0x%0x\n", code);
#endif
        return(code);
    }
 
    //
    // Device creation is complete
    //
 
    //
    // Create a symbolic link for the control object so that
    // usermode can open the device.
    //
    code = WdfDeviceCreateSymbolicLink(device, &dosDeviceName);
 
    if (!NT_SUCCESS(code)) {
#if DBG
        DbgPrint("WdfDeviceCreateSymbolicLink failed 0x%0x\n", 
        code);
#endif
        return(code);
    }
 
    //
    // Configure our queue of incoming requests
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioCallbacks,
                             WdfIoQueueDispatchSequential);
 
    ioCallbacks.EvtIoRead = NothingEvtIoRead;
    ioCallbacks.EvtIoWrite = NothingEvtIoWrite;
    ioCallbacks.EvtIoDeviceControl = NothingEvtIoDeviceControl;
 
    code = WdfIoQueueCreate(device,
                            &ioCallbacks,
                            WDF_NO_OBJECT_ATTRIBUTES,
                            NULL);
 
    if (!NT_SUCCESS(code)) {
#if DBG
     DbgPrint("WdfIoQueueCreate for default queue failed 0x%0x\n", 
     code);
#endif
        return(code);
    }
 
    //
    // That's all there is
    //
    return(code);
}

Figure 2 -- Device Add

The driver's device add event processing routine is where we define our driver's queue structure for sorting arriving I/O requests, and declare the event processing routines that will handle arriving WDFREQESTs.  Like a WDM driver's AddDevice routine, this is also where we create our device object.

The NOTHING driver starts by initializing a WDF_OBJECT_ATTRIBUTES structure using the macro WDF_OBJECT_ATTRIBUTES_INIT.  This object attributes structure will be used when the driver's WDFDEVICE is created.  The NOTHING driver then sets into the object attributes structure the name of the data structure that the driver uses for storing per-device context information.  It does this using the WDF_OBJECT_ ATTRIBUTES_SET_CONTEXT_TYPE macro.

Because I've never fully bought into the whole idea of device interfaces (should this be an additional part of my confession?), I choose to name the device object created by the NOTHING driver.  We specify the name to be used when the device object is created by calling WdfDeviceInit AssignName.

The next thing the NOTHING driver does is create its WDFDEVICE by calling WdfDeviceCreate.  The WDFDEVICE is the Framework object that represents a single instance of the driver's device. Calling WdfDeviceCreate also eventually results in the creation of a Windows DEVICE_OBJECT that is named as specified.

Note that the input parameters to WdfDeviceCreate are (1) the object attributes structure that was previously initialized (with the context type information), and (2) a pointer to the WDFDEVICE_INIT structure that was passed into the driver's device add routine.  The WDFDEVICE_INIT structure is used by the driver and Framework to collect attributes for the WDFDEVICE before it has been created.  For example, if we had been writing a driver for a hardware device, pointers to the various PnP and power management event processing routines would have been associated with this structure.

By the way, you should notice (and be duly impressed) at this point that there's no reference so far in this driver to any power management functions or events.  Nor will you see any, anywhere, in this driver.  That's because--as I said earlier--the Framework is going to handle all that nonsense automatically on the driver's behalf.

Because we want our device to be accessible from Win32, the next thing the NOTHING driver does is create a symbolic link between the Win32 name space and the native Windows name space.  OK, I guess there are a few things that work almost exactly the same in WDM and KMDF.  The NOTHING driver creates the necessary link using the function WdfDeviceCreateSymbolicLink.

The NOTHING driver's device add event processing callback is almost complete.  The only thing left to do is to define the driver's queue structure and indicate the I/O event processing callbacks that the driver supports.  The NOTHING driver utilizes a single, default, WDFQUEUE to manage the arrival of I/O requests. Additionally, the driver (just for fun, really) indicates that it wants the Framework to pass any arriving I/O requests to it one at a time.  That is, if multiple requests arrive in parallel to the NOTHING device, the Framework will queue the requests and pass them to the driver serially.  As soon as the driver completes one request, the Framework will call the driver with the next request on its queue (if there is one pending).  The driver indicates this serialization selection by specifying WdfIoQueueDispatchSerial in its call to WDF_IO_QUEUE_CONFIG_INIT.

While the selection of serial dispatching isn't of much consequence in the NOTHING driver, you shouldn't overlook its importance and usefulness in real, hardware-oriented, drivers.  For devices that can only handle one I/O operation at a time, telling the Framework to serialize requests for you can be an amazing convenience.  This avoids you having to maintain queues of requests, and thus saves you from having to deal with cancellation of pending requests.  It's sort of like WDM drivers that use system queuing, but much, much, more flexible, more modern, and more useful. I see no spin locks here--and that's a good thing.

Next, the NOTHING driver specifies its I/O event processing routines by filling in the appropriate fields of the WDF_IO_QUEUE_CONFIG structure.  The NOTHING driver handles read, write, and device control requests, so it supplies an event processing routine for each. By the way, this is just one of the ways that a driver can handle arriving I/O requests in KMDF.  (You'll have to read our book on WDF if you want more details.)  Finally, the driver calls WdfIoQueueCreate, specifying the handle to the WDFDEVICE (that it created earlier in this routine) associated with the queue, and a pointer to the WDF_IO_ QUEUE_CONFIG structure it initialized.  The driver is now "open for business" and ready to accept requests.

What About Create?  Close?
If you're not bored to tears, and you're still paying attention, you've probably noticed that we haven't declared routines to handle incoming create or close requests.  Interesting, huh?

Think about it for a minute:  Most device drivers don't care about either create or close operations.  I mean, how many times have you written the same stupid create and close routines for a WDM driver? If you're like me, you point the dispatch entry points for both create and close to the same routine that does:

Irp->IoStatus.Status= STATUS_SUCCESS;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp, IRP_NO_INCREMENT);
return(STATUS_SUCCESS);

Heck, you probably can (and do) code that routine in your sleep.

During the initial design of WDF, the team wondered why a driver writer should be required to code an almost totally useless routine in every driver they write. So, they eliminated it.  If you don't provide an event processing routine for create or close, the Framework handles these requests for you, and completes them automatically with success.  Of course, if you do want to handle create and close requests in your driver (fussy person that you are), that's no problem.  The event processing routines for these requests are easy to specify.

Processing I/O Requests
That leaves us with very little left to discuss in our NOTHING driver.  Figure 3 contains the code for the driver's read I/O event processing routine.

VOID
NothingEvtIoRead(WDFQUEUE Queue,
    WDFREQUEST Request,
    size_t Length)
  
{
    UNREFERENCED_PARAMETER(Queue);
 
#if DBG
    DbgPrint("NothingEvtIoRead: Completing and leaving\n");
#endif
 
    WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, 
                                         Length);
    return;
} 

Figure 3 -- Read I/O Event Processing

Zzzzzzzzzzzz.  As the point of the NOTHING driver is to do, ah, "nothing," the I/O event processing routines are pretty simple.  The read, write and device control routines each complete all the requests that they receive with success.  For reads and writes, the information field in the I/O status block will be set to the size of the data buffer passed into the driver.

In Figure 3, you can see the driver's read event processing routine code is called with three parameters:

  • A handle to the WDFQUEUE on which the I/O request is arriving
  • A handle to a WDFREQUEST
  • The length, in bytes, of the I/O operation specified in the WDFREQUEST

All the NOTHING driver does with this information is call WdfRequestCompleteWithInformation, specifying the completion status of the request and the contents that should be placed into the Information field. 

In a driver that's controlling hardware, you'd probably want to know with which device the request was associated.  You'd do this by calling WdfIoQueueGetDevice, which would return a handle to the WDFDEVICE that is the target of the I/O request.

I know I promised to walk you through every single line of code in this driver, but I just can't bear to walk through the write event processing routine (which looks suspiciously like a cut and pasted version of the read routine) or the device control event processing routine (which looks a lot like the read and write routines but with slightly different arguments and an empty case statement).  If you're really that interested, download the driver and take a look at it. Anyhow, speaking of power management, it's time to shut off all electronic devices, raise my seatback to its most upright position, and stow away my tray table.  Oh look, it's raining in Seattle.  How unusual.

No Power Required
And so you see, not only is it possible to create a root-enumerated software-only KMDF driver without any power management code, it's easy to the point of being trivial.  So now you can avoid the hassle of writing that power management code, content in the notion that somewhere in the bowels of the KMDF, somebody else has written all that "Get an S-IRP send a D-IRP" crap, and they've tested it, and they've gotten it right. They hope. I say: Better them than me.

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

"A really good Intro for KMDF"
I already ramdomly read some article on how to write a device driver in Windows. Hey, this article worth 10 times of them, it provide a clearer overview on how to write device with KMDF with good explanation. Really really appreciate for it.

Rating:
25-Oct-09, Shin Guey Wong


"Fantastic job"
you surmised all i read about the KMDF software only driver in 10 mins. Wonderfully written.

Rating:
07-Dec-07, Muer Thor


"Nice Job"

26-Feb-07, brian boothman


"Very nicely written"

Rating:
23-Mar-06, Du Bhagat


"good enough to read it"
I think that this article enspired me to work longer and to learn more about WDF.

Rating:
18-Aug-05, Dmitry Shmykov


Post Your Comments.
Print this article.
Email this article.

Writing WDF Drivers I: Core Concepts
LAB

Nashua (Amherst), NH
15-19 May 2017

Writing WDF Drivers II: Advanced Implementation Techniques
LAB

Nashua (Amherst), NH
23-26 May 2017

Kernel Debugging and Crash Analysis
LAB

Dulles (Sterling), VA
26-30 Jun 2017

Windows Internals and Software Driver Development
LAB

Nashua (Amherst), NH
24-28 Jul 2017

 
 
 
 
x
LetUsHelp
 

Need to develop a Windows file system solution?

We've got a kit for that.

Need Windows internals or kernel driver expertise?

Bring us your most challenging project - we can help!

System hangs/crashes?

We've got a special diagnostic team that's standing by.

Visit the OSR Corporate Web site for more information about how OSR can help!

 
bottom nav links