If you haven't started playing with KMDF yet, you really should. Now that we've taught our first few WDF Seminars, and watched the students do their lab assignments, we can say for certain that KMDF makes driver writing easier and less prone to error. Because so much of the (annoying, complex, and difficult) driver infrastructure is handled for you, KMDF also allows you to concentrate on the features of your device and spend less time hassling with the details of PnP and power management. What illustrated this point most clearly for us was seeing students take the initiative to add novel features to the drivers they created as part of their seminar lab assignments. After just a few days in class, these students were comfortable enough using the Framework to think about how they could use it to expand the example drivers' feature sets. That's proof that KMDF "works" as advertised.
While KMDF is great, it's not entirely devoid of eccentricities. From writing KMDF drivers ourselves, and from watching our students in class, we've noticed a few "gotchas" that can trap those new to KMDF driver development. We present them here, in no particular order, so that you can (hopefully) avoid them.
Let's start with a group of common "gotchas" to do with debugging:
Strange NTSTATUS Values Returned From KMDF Functions
A very common question that we get in our KMDF seminar is "Help! The NTSTATUS value I got back from calling a KMDF function isn't listed in NTSTATUS.H!" Well, yeaaaahhhh. If you get a status value that starts with 0xC020xxxx, it's a custom WDF status value. Look in the WDK's \inc\wdf\kmdf\10\wdfstatus.h for the definitions of the WDF status values. Problem solved!
Loading the WDF Debugger Extension and Defining the TMF File Path
KMDF comes complete with its own custom debugger extension and activity trace log. To load the debugger extension into WinDbg and to enable decoding of trace messages, you use the commands (for KMDF V1.5 that is part of the Vista WDK) in Figure 1 below.
Figure 1 - Commands to Load/Enable WDF Debug Extensions
Note that you'll need to use the version of WDFKD that matches the version of KMDF that you're using.
As you initiate debugging sessions or start and stop WinDbg, you'll find yourself repeatedly typing these commands (unless the version of WinDbg that you've installed includes the WDFKD extension DLL that matches the version of KMDF that you're using). This will quickly get annoying. But don't despair! There are at least two potential solutions to this problem.
One solution is to use the OSR utility WinDbgStart. This utility, which is available (free) in the download section of OSR Online (http://www.osronline.com) allows you to specify and save different sets of WinDbg configurations. As part of each configuration, you can specify one or more initial commands to be executed each time WinDbg starts. Just insert the !load and !wdffmffile commands above as initial commands, and the problem is solved.
An alternative to using WinDbgStart is to either copy the desired version of WDFKD.DLL to the debugger's \winext\ directory or to set the _NT_DEBUGGER_EXTENSION _PATH environment variable to point to the directory in which WDFKD.DLL is located. To specify the TMF search path, you can set the TRACE_FORMAT_SEARCH_PATH environment variable.
"Build My Driver With Newer WDF Libraries"??
While we're on the topic of debugging KMDF drivers using WDFKD, we should probably mention a couple of common errors with the WDF Trace Log, which is often referred to as the In Flight Recorder (IFR). Onc problem we see frequently is the following response to the !WDFKD.WDFLOGDUMP xxxx command (See Figure 2).
error: Could not retrieve WDF IFR log header for driver xxxx.
hint: Build your driver with newer WDF libraries.
Figure 2 - Common Error Response Interpreted As (Typically) Improper Symbols
This error is especially common on systems that are not connected to the internet and hence are not using the on-line symbol server.
While the message is cryptic and scary, the underlying problem is almost always not having your symbols properly loaded. Note that you need symbols for both your driver and the KMDF library. Make sure your symbol search path is properly configured, do a !reload, and the problem will almost certainly be solved.
The IFR Messages Seem To Be Confused About Which Function I Called
Once you successfully generate and examine the KMDF trace log output, you might discover another sticking point. Let's say, for example, you call WdfWorkItemCreate and forget that specifying a WDF_OBJECT_ATTRIBUTES structure is required. You get back an error, but when you look in the IFR log for further information you see a message that says "imp_WdfDpcCreate - WDF_OBJECT_ATTRIBUTES required, status 0xc0200212" -- We previously explained how to make sense out of the status, but what's with the "WdfDpcCreate"?? Your driver might not even call WdfDpcCreate.
Well, yeah.... that's "just one of those things" that you'll learn to love in KMDF. Basically, the function name shown in the IFR isn't always correct - So, you should just ignore it. YES, we told the KMDF team about it. And, YES, they know why this problem occurs. They're just not sure if it's something that'll be fixed any time soon.
The Framework Hit a Breakpoint
This is one that makes me nuts. Let's say you're running your driver with KMDF Verifier enabled. Things look like they're going well, until you suddenly hit a breakpoint. No output is shown in WinDbg to indicate why the breakpoint occurred. You look at the stack trace, and discover that you're stopped in the middle of some function in the KMDF library. What's up, and what do you do next??
The first thing to do in this situation is check the KMDF trace log (using !WDFLOGDUMP). If there's nothing in the last few log entries that seem likely to account for your problem, continue in the debugger until the Framework returns to your driver. Once back in your driver, look at the return status (if there is one) from the KMDF function that you called and dump the KMDF trace log again. Chances are an error message describing the problem will now appear in the trace log.
Enabling KMDF Verifier
This isn't really a "gotcha" but rather a tip we learned from the KMDF samples in the WDK. Because enabling the KMDF Verifier requires making a few Registry entries (not exactly ultra-convenient), why not enable KMDF Verifier via the driver's INF file. Obviously, you'll only want to do this while testing your driver and not for the final released version (duh).
The Registry keys can most easily be set via an AddReg section, that's called from the Service Install section of the DDINSTALL.Services section of your INF. An example of is shown in Figure 3.
AddService = Nothing,%SPSVCINST_ASSOCSERVICE%,OsrNothingDriver_Service_Inst
ServiceType = %SERVICE_KERNEL_DRIVER%
StartType = %SERVICE_DEMAND_START%
ErrorControl = %SERVICE_ERROR_NORMAL%
LoadOrderGroup = "Base"
ServiceBinary = %12%\nothing.sys AddReg = KMDFVerifierAddReg
Figure 3 - Enabling KMDF Verifier via your INF file
Now let's move on to a discussion of common KMDF design and implementation "gotchas":
Parent/Child Isn't Universal
When we describe the relationship among KMDF objects here at OSR, we typically refer to it as a "limited parent/child relationship." As you'll quickly discover if you try to utilize this feature of KMDF to the max, in the current version of the Framework every object is not capable of being the parent of every other type of object.
We ran into this restriction ourselves a while back, as part of a KMDF development project. We used a WDFWORKITEM to "send-and-forget" a WDFREQUEST to an in-stack Target at IRQL PASSIVE_LEVEL. We thought we'd be clever by setting the Request to be the parent of the Work Item, reasoning that the happy KMDF reference counting and parent/child stuff would clean everything up appropriately when the WDFREQUEST was forwarded and we exited from the WDFWORKITEM. Instead, the result was a deadlock. Basically, we outsmarted ourselves.
To avoid these types of problems, at least until parent/child relationships in KMDF are more universal, we recommend sticking to the more conventional relationships described in the WDF documentation. At least you can be sure these have been tested and are supported. Don't expect every creative combination of parent and child objects that you can imagine to work.
Queue Dispatching Isn't The Same As Synchronization
When you create your driver's WDFQUEUEs, you must specify a dispatch type. Your choices are Manual, Sequential, and Parallel. If you're not familiar with WDFQUEUEs and dispatch types, you can learn more about them in "When is a Queue not just a Queue".
While Sequential Queues only allow one Request to be in progress at a time from that queue, it's important to understand that the dispatch type isn't a substitute for (or interchangeable with) KMDF synchronization (as specified by Synchronization Scope).
Keep the following in mind: Queue dispatching is about how many total Requests from a Queue can be outstanding at any given time. KMDF synchronization scope is about how many callback functions can be executing simultaneously at the given scope (irrespective of how many requests are outstanding). Sure, these two things are related, but they most certainly are not interchangeable. I know it's confusing. But, it's worth taking the time to learn how to use the unique benefits of both sync scope and dispatching in your driver.
More Synchronization Isn't Always Better
Speaking of Synchronization Scope, a common error we see starts when new driver devs first learn the level of reentrancy possible in Windows drivers. As soon as they truly "get" that their drivers can be re-entered multiple times, they often panic and want to eliminate it. When they discover KMDF's Synchronization Scope -- that automatically serializes a driver's execution at the Device or Queue level -- they often see this as a way to magically allow them to ignore multiprocessor locking and reentrancy issues. A common question we hear is "If I set my Sync Scope to Device, I don't have to worry about any of this (reentrancy) stuff, right?"
The answer we give is "Well, yes. At least, sort of. But..."
Naturally, the main concern with using excessive synchronization is performance. By specifying Device Sync Scope, you can be sure that only one I/O Event Processing Callback routine associated with a particular Device will be executing at a time. While that might make for convenient code, it sure isn't likely to make optimal use of you customer's multiprocessor system.
Another concern, beyond simple performance, relates to IRQL. Specifying a Synchronization Scope (in absence of an Execution Level constraint) causes your I/O Event Processing Callbacks to run at IRQL DISPATCH_LEVEL. Running at IRQL DISPATCH_LEVEL limits the functions you can call, can restrict the Targets to which you can send Requests, and (perhaps worst of all) can degrade overall system performance. Consider that while your Event Processing Callback is running, items in the processor's DPC Queue will have to wait. So, while interrupts will still be serviced, request processing and completion for other devices in the system can be significantly delayed.
Our advice is to avoid the Device and Queue Sync Scopes unless you've fully considered the alternatives. Sync Scope is a powerful tool; Use it wisely.
AutomaticSerialization and WDFINTERRUPTs
I was reminded of this last item by a recent discussion on the OSR NTDEV discussion list. However, it's a "gotcha" that puzzled me when I first started writing KMDF drivers. The "gotcha" is simple: Setting AutomaticSerialization=TRUE when configuring a WDFINTERRUPT Object does not serialize the ISR itself with anything. Rather, it causes the DpcForIsr to be serialized with the driver's Event Processing Callback functions, and other callbacks that have AutomaticSerialization set to TRUE.
We don't have the space in this article for a full discussion of AutomaticSerialization -- that'll have to wait for an article all its own. Suffice it to say that AutomaticSerialization is just one more of KMDF's powerful mechanisms for serializing the execution of certain callback routines, without having to resort to manual lock management. But, contrary to its name, specifying AutomaticSerialization = TRUE on a WDFINTERRUPT Object serializes the driver's DpcForIsr, not the ISR itself. This "gotcha" has bitten some pretty smart folks. Now, you've been warned.
Let me say it again: If you haven't taken the time to play with KMDF yet, you really should. If you're writing a new driver, or you find yourself hacking an old driver to bits, you'd be well advised to seriously consider implementing any new driver in KMDF.
Sure, there are some "gotchas" -- But those exist in any software package. And there's enough experience with KMDF at this point that we know what most of those "gotchas" are, so you don't have to discover them yourself.