The Windows NT Device Driver Book Ó 1997 OSR Open Systems Resources, Inc. Rating: Not Recommended I’m disappointed. I’ve been eagerly looking forward to the publication of the various NT device driver books that have been promised for so long. It’s fun to write a review of a good book. And the ever-expanding community of NT driver writers can really use a few quality texts. The reason I’m so disappointed is that the first of the long-awaited books to market, Art Baker’s The Windows NT Device Driver Book (Prentice Hall, 1997), turns out to be at best a mediocre offering for newbie driver writers. As anything more than an introductory text, the book is dangerously inaccurate and incomplete. The book starts out with a decent overview of the Windows NT operating system, where we are introduced to each of the executive subsystems, the microkernel, and the HAL. It does a particularly good job of describing, in reasonable detail, the different types of NT drivers. This conceptual technical material is precisely the sort of material with which Mr. Baker’s book is most successful. The book next moves on to a review of The Hardware Environment. Here the book reviews device registers, interrupts, and the data transfer methods that drivers use to move data between user buffers and the devices. Baker explains port I/O space and memory mapped registers. He describes the HAL functions to access I/O space registers as being provided "to promote platform independence". This is all pretty good. What keeps this section from being excellent is that Baker fails to tell his readers that where a device’s registers are located (i.e. in port space or in memory space, see In My Space, this issue) is a function of the device, and not the processor on which the device is installed. The text thus misses the point that to access registers that are wired to I/O space on the device, driver writers use the READ_PORT_xxxx function calls irrespective of the hardware platform on which the device is installed. This in fact, is the point of having these HAL macros in the first place. Perhaps Mr. Baker himself misunderstands this point (as do a good number of experienced NT driver writers), given a cryptic comment in the Preface that says his driver samples are "targeted to run on Intel 80x86 platforms," and hence "assume that device registers live in I/O space rather than being memory-mapped". Huh? Of course, the above would be nit picking if the rest of the book was scrupulously technically accurate. Sadly, this is not the case. The errors range from the trivial (calls to IoAllocateIrp(…) in the book have only one parameter – unfortunately, the function takes two parameters) to the enormously significant (Baker specifically states that DPCs cannot execute in parallel on SMP machines, more about which later). Mr. Baker’s technical reviewers should, at the very least, be shot. In the next few chapters, the book provides overviews of the I/O Subsystem, microkernel objects, and general issues in the implementation of NT drivers (incremental development, paging, header files, naming, and the like). When these chapters discuss high-level conceptual material they are solid and well written. It is in this last chapter (Chapter 5, entitled "General Development Issues"), when discussing specific technical issues that the book begins to falter seriously in its accuracy. The first major technical error of note appears when Baker describes using DPCs for the purpose of synchronization (an unusual idea in itself). In this section Baker writes, "Another advantage of DPC routines is that the Kernel’s DPC Dispatcher automatically handles synchronization even in a multiprocessor environment." Not true! The NT Kernel Mode Driver Design Guide, section 9.2.3, in the NT V4.0 DDK is correct on this point. It reads in part as follows: "On the other hand, if the ISR calls IoRequestDpc or KeInsertQueueDpc while the corresponding DpcForIsr or CustomDpc is running on another processor, two instantiations of that DPC routine can run concurrently." Another problem crops up in Chapter 7, which describes how to find and initialize device hardware. Baker spends a lot of time (17 pages!) explaining how to find devices that have been auto-detected and how to use IoQueryDeviceDescription(…) to get their configuration information. He tells us that, "NT goes to a lot of trouble to figure out what kinds of peripherals are attached to the system". Is he using the same version of NT that I am? On my NT, the system just believes whatever the BIOS or the ARC console firmware tells it, and goes to no trouble at all to find any of the unique devices that people typically need to write NT drivers for. "In the absence of auto-detection capabilities", readers are told, storing configuration information in the Registry, "may be your only option." It may be, but it’s not. PCI driver writers, for example, typically enumerate the PCI bus slots to find their device’s Vendor ID and Device ID in the PCI bus configuration data. What does the book tell us about how to do this? It includes one paragraph under the heading "Other Sources of Information" where HalGetBusData(…) is described in passing. What makes this particularly sad is that the DDK also lacks significant information on how driver writers should locate such devices. It is precisely to learn these techniques that people need a book on NT device drivers. In the next chapter, Baker describes how to define custom device IOCTLs. In so doing, he describes the various buffering strategies drivers can use. There is a serious problem with this section, in that Baker’s description of METHOD_IN_DIRECT is totally incorrect. Here’s what he says: "The I/O Manager checks the accessibility of the caller’s input buffer and locks it into physical memory. It then builds an MDL for the input buffer and stores a pointer to the MDL in the MdlAddress field of the IRP. It also allocates an output buffer from nonpaged-pool and stores the address of this buffer in the IRP’s AssociatedIrp.SystemBuffer field. The IRP’s UserBuffer field is set to the original caller’s output buffer address. When the IOCTL IRP is completed, the contents of the system buffer will be copied back into the caller’s original output buffer." Wrong! It’s enough to make one wonder if Mr. Baker has written a driver that users IOCTLs. A casual search of the Microsoft DDK yields the following (from the knowledge base): "For IOCTL requests, in both METHOD_IN_DIRECT and METHOD_OUT_DIRECT, if there is an input buffer, a system buffer is allocated (again, SystemBuffer has the address) and the input data is copied into it. If there is an output buffer, it is locked down, an MDL is built, and MdlAddress is set. The UserBuffer field has no meaning." This is correct. What makes this error particularly pernicious is that the process of defining custom IOCTLs is often very confusing to people learning how to write NT drivers. The DDK documentation, aside from the above quoted KB article, does not do a good job of describing how the various buffering schemes work. [Ed.: See also "Fun With IOCTLs: Defnining Custom Device I/O Control Codes" from The NT Insider, Volume 3 Number 2.) Another problem with the book is that it focuses on system queuing of IRPs, to the exclusion of any other possibilities. While the DDK emphasizes system queuing, at least it acknowledges that there are other methods that some drivers might use. Using system queuing for managing the incoming stream of IRPs is only reasonable when the device can support a single I/O operation at a time. That’s a single read, OR a single write, OR a single device control operation – Not one of each. While using system queuing of IRPs might make sense for such devices, the modern design of intelligent peripherals – which allow lots of requests to be in progress simultaneously – make devices that can use system queuing more and more rare. As an alternative to just simple system queuing, Baker suggests a strange admixture of system queuing and using a KDEVICE_QUEUE object in the device extension to support "full duplex drivers". While this architecture works, it is unusual. Why not just have the driver manage it’s own IRP queue? Baker does not even suggest that NT allows such an option. In the same chapter we are further told, "Any-driver defined data structures that are shared among the Cleanup Dispatch routine, a Cancel routine, and some other driver routine should also be guarded by this [the system-wide Cancel] spin lock." While this will certainly work using the system-wide Cancel spin lock to serialize access to your device’s I/O request queue will also result in a horribly performing driver, and most likely a badly performing system. Yikes! Using a system-wide resource to guard a driver-specific data structure does not sound like a good idea to me. The book continues with a discussion of DMA, providing a good description of what the infamous Mapping Registers are. However, it never does tell us that these Mapping Registers are implemented as intermediate buffers by the standard x86 HAL. Neither does it specifically mention the effect of using Mapping Registers on devices that do not support scatter/gather (it causes the data to be DMA’ed into intermediate buffers), though this is certainly hinted at. The later portions of the book (Chapters 13 through 18) contain descriptions of error logging, the use of system threads in drivers, intermediate drivers, driver building and installation, testing, and performance measurement. Here, again, when dealing with general issues of design the book is pretty good. Twenty pages on error logging is about 18 pages too much for me personally, but it all seemed correct. The description of using system threads is decent, if a bit light on the details. I would have preferred a bit of discussion regarding the different queue types (DelayedWorkQueue, CriticalWorkQueue, etc). The discussion of testing contains a general harangue on driver testing methodology that I could have lived without. But for the most part, this information seems to be clear and well written. The section on Writing Layered Drivers has a serious flaw. It describes the function IoCallDriver(…) as "an asynchronous call that returns immediately regardless of whether the lower-level driver completed the IRP." If Mr. Baker means that the lower-level driver whose dispatch routine is directly called by the code in IoCallDriver(…) can return STATUS_PENDING or STATUS_SUCCESS or some error then he should say so. Phrased the way it is in the book, I’m afraid the new NT driver writer will be left with the impression that some sort of queuing process is at work between levels of drivers on NT. And this, of course, is not the case. In summary, The Windows NT Device Driver Book suffers from what looks to me to be a lack of pragmatic experience on the part of the author, as well as many of the frailties of the Windows NT DDK. What is most disappointing is that Baker gets the hardest part of writing a book – cogent descriptions of technical concepts – right. He gets what should be the easy part – technical accuracy – seriously wrong. Technical accuracy is, of course, a prerequisite for a good technical book – a necessary, if not sufficient ingredient. And, lest you think otherwise, please rest assured that I have not listed every technical error, or problem, or personal style disagreement that I found in the book. Not by a long shot. What could have been an important and useful work has resulted in yet more FUD and obfuscation in the field of NT kernel mode drivers. As a result, The NT Insider rates this book Not Recommended. Guess we’ll all have to wait for the book by Fraser, Goldenberg and Hanrahan. Return to Previously Published Articles Return to OSR's Home Page [Image]consulting& developing/ kits / seminars / NT insider/ resources / client area / about OSR