3. I
Table of Contents
Acknowledgments IX
Introduction X
1 Beginning a Driver Project - 1 -
1.1 A Brief History of Device Drivers - 1 -
1.2 An Overview of the Operating Systems - 3 -
1.2.1 Windows XP Overview - 3 -
1.2.2 Windows 98/Windows Me Overview - 4 -
1.3 What Kind of Driver Do I Need? - 6 -
1.3.1 WDM Drivers - 7 -
1.3.2 Other Types of Drivers - 8 -
1.3.3 Management Overview and Checklist - 9 -
2 Basic Structure of a WDM Driver - 11 -
2.1 How Drivers Work - 11 -
2.1.1 How Applications Work - 11 -
2.1.2 Device Drivers - 12 -
2.2 How the System Finds and Loads Drivers - 14 -
2.2.1 Device and Driver Layering - 14 -
2.2.2 Plug and Play Devices - 15 -
2.2.3 Legacy Devices - 17 -
2.2.4 Recursive Enumeration - 18 -
2.2.5 Order of Driver Loading - 19 -
2.2.6 IRP Routing - 20 -
2.3 The Two Basic Data Structures - 23 -
2.3.1 Driver Objects - 23 -
2.3.2 Device Objects - 25 -
2.4 The DriverEntry Routine - 27 -
2.4.1 Overview of DriverEntry - 28 -
2.4.2 DriverUnload - 29 -
2.5 The AddDevice Routine - 29 -
2.5.1 Creating a Device Object - 30 -
2.5.2 Naming Devices - 31 -
2.5.3 Other Global Device Initialization - 39 -
2.5.4 Putting the Pieces Together - 42 -
2.6 Windows 98/Me Compatibility Notes - 43 -
2.6.1 Differences in DriverEntry Call - 43 -
2.6.2 DriverUnload - 43 -
2.6.3 The GLOBAL?? Directory - 43 -
2.6.4 Unimplemented Device Types - 43 -
3 Basic Programming Techniques - 45 -
3.1 The Kernel-Mode Programming Environment - 45 -
3.1.1 Using Standard Run-Time Library Functions - 46 -
3.1.2 A Caution About Side Effects - 46 -
4. II
3.2 Error Handling - 46 -
3.2.1 Status Codes - 47 -
3.2.2 Structured Exception Handling - 48 -
3.2.3 Bug Checks - 54 -
3.3 Memory Management - 55 -
3.3.1 User-Mode and Kernel-Mode Address Spaces - 55 -
3.3.2 Heap Allocator - 60 -
3.3.3 Linked Lists - 64 -
3.3.4 Lookaside Lists - 67 -
3.4 String Handling - 69 -
3.5 Miscellaneous Programming Techniques - 71 -
3.5.1 Accessing the Registry - 71 -
3.5.2 Accessing Files - 76 -
3.5.3 Floating-Point Calculations - 78 -
3.5.4 Making Debugging Easier - 79 -
3.6 Windows 98/Me Compatibility Notes - 82 -
3.6.1 File I/O - 82 -
3.6.2 Floating Point - 82 -
4 Synchronization - 83 -
4.1 An Archetypal Synchronization Problem - 83 -
4.2 Interrupt Request Level - 85 -
4.2.1 IRQL in Operation - 86 -
4.2.2 IRQL Compared with Thread Priorities - 86 -
4.2.3 IRQL and Paging - 87 -
4.2.4 Implicitly Controlling IRQL - 87 -
4.2.5 Explicitly Controlling IRQL - 88 -
4.3 Spin Locks - 88 -
4.3.1 Some Facts About Spin Locks - 89 -
4.3.2 Working with Spin Locks - 89 -
4.3.3 Queued Spin Locks - 90 -
4.4 Kernel Dispatcher Objects - 91 -
4.4.1 How and When You Can Block - 91 -
4.4.2 Waiting on a Single Dispatcher Object - 92 -
4.4.3 Waiting on Multiple Dispatcher Objects - 93 -
4.4.4 Kernel Events - 94 -
4.4.5 Kernel Semaphores - 96 -
4.4.6 Kernel Mutexes - 97 -
4.4.7 Kernel Timers - 98 -
4.4.8 Using Threads for Synchronization - 101 -
4.4.9 Thread Alerts and APCs - 102 -
4.5 Other Kernel-Mode Synchronization Primitives - 104 -
4.5.1 Fast Mutex Objects - 104 -
4.5.2 Interlocked Arithmetic - 106 -
4.5.3 Interlocked List Access - 108 -
4.5.4 Windows 98/Me Compatibility Notes - 110 -
5 The I/O Request Packet - 111 -
5.1 Data Structures - 111 -
5.1.1 Structure of an IRP - 111 -
5. III
5.1.2 The I/O Stack - 113 -
5.2 The “Standard Model” for IRP Processing - 114 -
5.2.1 Creating an IRP - 114 -
5.2.2 Forwarding to a Dispatch Routine - 116 -
5.2.3 Duties of a Dispatch Routine - 119 -
5.2.4 The StartIo Routine - 123 -
5.2.5 The Interrupt Service Routine - 124 -
5.2.6 Deferred Procedure Call Routine - 124 -
5.3 Completion Routines - 125 -
5.4 Queuing I/O Requests - 132 -
5.4.1 Using the DEVQUEUE Object - 134 -
5.4.2 Using Cancel-Safe Queues - 136 -
5.5 Cancelling I/O Requests - 140 -
5.5.1 If It Weren’t for Multitasking… - 140 -
5.5.2 Synchronizing Cancellation - 140 -
5.5.3 Some Details of IRP Cancellation - 141 -
5.5.4 How the DEVQUEUE Handles Cancellation - 142 -
5.5.5 Cancelling IRPs You Create or Handle - 146 -
5.5.6 Handling IRP_MJ_CLEANUP - 151 -
5.5.7 Cleanup with a DEVQUEUE - 152 -
5.5.8 Cleanup with a Cancel-Safe Queue - 153 -
5.6 Summary—Eight IRP-Handling Scenarios - 153 -
5.6.1 Scenario 1—Pass Down with Completion Routine - 154 -
5.6.2 Scenario 2—Pass Down Without Completion Routine - 154 -
5.6.3 Scenario 3—Complete in the Dispatch Routine - 155 -
5.6.4 Scenario 4—Queue for Later Processing - 156 -
5.6.5 Scenario 5—Your Own Asynchronous IRP - 157 -
5.6.6 Scenario 6—Your Own Synchronous IRP - 158 -
5.6.7 Scenario 7—Synchronous Pass Down - 159 -
5.6.8 Scenario 8—Asynchronous IRP Handled Synchronously - 160 -
6 Plug and Play for Function Drivers - 163 -
6.1 IRP_MJ_PNP Dispatch Function - 164 -
6.2 Starting and Stopping Your Device - 165 -
6.2.1 IRP_MN_START_DEVICE - 166 -
6.2.2 IRP_MN_STOP_DEVICE - 167 -
6.2.3 IRP_MN_REMOVE_DEVICE - 168 -
6.2.4 IRP_MN_SURPRISE_REMOVAL - 169 -
6.3 Managing PnP State Transitions - 169 -
6.3.1 Starting the Device - 171 -
6.3.2 Is It OK to Stop the Device? - 171 -
6.3.3 While the Device Is Stopped - 172 -
6.3.4 Is It OK to Remove the Device? - 173 -
6.3.5 Synchronizing Removal - 174 -
6.3.6 Why Do I Need This @#$! Remove Lock, Anyway? - 176 -
6.3.7 How the DEVQUEUE Works with PnP - 178 -
6.4 Other Configuration Functionality - 181 -
6.4.1 Filtering Resource Requirements - 181 -
6.4.2 Device Usage Notifications - 182 -
6. IV
6.4.3 PnP Notifications - 184 -
6.5 Windows 98/Me Compatibility Notes - 191 -
6.5.1 Surprise Removal - 191 -
6.5.2 PnP Notifications - 191 -
6.5.3 The Remove Lock - 191 -
7 Reading and Writing Data - 193 -
7.1 Configuring Your Device - 193 -
7.2 Addressing a Data Buffer - 195 -
7.2.1 Specifying a Buffering Method - 195 -
7.3 Ports and Registers - 198 -
7.3.1 Port Resources - 200 -
7.3.2 Memory Resources - 201 -
7.4 Servicing an Interrupt - 201 -
7.4.1 Configuring an Interrupt - 202 -
7.4.2 Handling Interrupts - 203 -
7.4.3 Deferred Procedure Calls - 204 -
7.4.4 A Simple Interrupt-Driven Device - 207 -
7.5 Direct Memory Access - 211 -
7.5.1 Transfer Strategies - 212 -
7.5.2 Performing DMA Transfers - 213 -
7.5.3 Using a Common Buffer - 222 -
7.5.4 A Simple Bus-Master Device - 224 -
7.6 Windows 98/Me Compatibility Notes - 225 -
8 Power Management - 227 -
8.1 The WDM Power Model - 227 -
8.1.1 The Roles of WDM Drivers - 227 -
8.1.2 Device Power and System Power States - 227 -
8.1.3 Power State Transitions - 228 -
8.1.4 Handling IRP_MJ_POWER Requests - 229 -
8.2 Managing Power Transitions - 231 -
8.2.1 Required Infrastructure - 232 -
8.2.2 Initial Triage - 233 -
8.2.3 System Power IRPs That Increase Power - 233 -
8.2.4 System Power IRPs That Decrease Power - 238 -
8.2.5 Device Power IRPs - 238 -
8.2.6 Flags to Set in AddDevice - 244 -
8.3 Additional Power-Management Details - 245 -
8.3.1 Device Wake-Up Features - 245 -
8.3.2 Powering Off When Idle - 250 -
8.3.3 Using Sequence Numbers to Optimize State Changes - 252 -
8.4 Windows 98/Me Compatibility Notes - 253 -
8.4.1 The Importance of DO_POWER_PAGABLE - 253 -
8.4.2 Completing Power IRPs - 253 -
8.4.3 Requesting Device Power IRPs - 253 -
8.4.4 PoCallDriver - 253 -
8.4.5 Other Differences - 253 -
9 I/O Control Operations - 255 -
7. V
9.1 The DeviceIoControl API - 255 -
9.1.1 Synchronous and Asynchronous Calls to DeviceIoControl - 256 -
9.1.2 Defining I/O Control Codes - 257 -
9.2 Handling IRP_MJ_DEVICE_CONTROL - 258 -
9.3 METHOD_BUFFERED - 259 -
9.3.1 The DIRECT Buffering Methods - 260 -
9.3.2 METHOD_NEITHER - 261 -
9.3.3 Designing a Safe and Secure IOCTL Interface - 262 -
9.4 Internal I/O Control Operations - 263 -
9.5 Notifying Applications of Interesting Events - 264 -
9.5.1 Using a Shared Event for Notification - 265 -
9.5.2 Using a Pending IOCTL for Notification - 266 -
9.6 Windows 98/Me Compatibility Notes - 269 -
10 Windows Management Instrumentation - 271 -
10.1 WMI Concepts - 271 -
10.1.1 A Sample Schema - 272 -
10.1.2 Mapping WMI Classes to C Structures - 273 -
10.2 WDM Drivers and WMI - 274 -
10.2.1 Delegating IRPs to WMILIB - 275 -
10.2.2 Advanced Features - 280 -
10.3 Windows 98/Me Compatibility Notes - 285 -
11 Controller and Multifunction Devices - 287 -
11.1 Overall Architecture - 287 -
11.1.1 Child Device Objects - 287 -
11.2 Handling PnP Requests - 289 -
11.2.1 Telling the PnP Manager About Our Children - 290 -
11.2.2 PDO Handling of PnP Requests - 290 -
11.2.3 Handling Device Removal - 293 -
11.2.4 Handling IRP_MN_QUERY_ID - 293 -
11.2.5 Handling IRP_MN_QUERY_DEVICE_RELATIONS - 294 -
11.2.6 Handling IRP_MN_QUERY_INTERFACE - 294 -
11.2.7 Handling IRP_MN_QUERY_PNP_DEVICE_STATE - 297 -
11.3 Handling Power Requests - 297 -
11.4 Handling Child Device Resources - 299 -
12 The Universal Serial Bus - 301 -
12.1 Programming Architecture - 301 -
12.1.1 Device Hierarchy - 302 -
12.1.2 What’s in a Device? - 303 -
12.1.3 Information Flow - 304 -
12.1.4 Descriptors - 310 -
12.2 Working with the Bus Driver - 315 -
12.2.1 Initiating Requests - 315 -
12.2.2 Configuration - 317 -
12.2.3 Managing Bulk Transfer Pipes - 323 -
12.2.4 Managing Interrupt Pipes - 329 -
12.2.5 Control Requests - 329 -
12.2.6 Managing Isochronous Pipes - 331 -
8. VI
12.2.7 Idle Power Management for USB Devices - 340 -
13 Human Interface Devices - 343 -
13.1 Drivers for HID Devices - 343 -
13.2 Reports and Report Descriptors - 343 -
13.2.1 Sample Keyboard Descriptor - 343 -
13.2.2 HIDFAKE Descriptor - 345 -
13.3 HIDCLASS Minidrivers - 346 -
13.3.1 DriverEntry - 346 -
13.3.2 Driver Callback Routines - 347 -
13.3.3 Internal IOCTL Interface - 351 -
13.3.4 IOCTL_HID_GET_DEVICE_DESCRIPTOR - 353 -
13.4 Windows 98/Me Compatibility Notes - 360 -
13.4.1 Handling IRP_MN_QUERY_ID - 360 -
13.4.2 Joysticks - 361 -
14 Specialized Topics - 363 -
14.1 Logging Errors - 363 -
14.1.1 Creating an Error Log Packet - 364 -
14.1.2 Creating a Message File - 365 -
14.2 System Threads - 367 -
14.2.1 Creating and Terminating System Threads - 368 -
14.2.2 Using a System Thread for Device Polling - 369 -
14.3 Work Items - 371 -
14.3.1 Watchdog Timers - 372 -
14.4 Windows 98/Me Compatibility Notes - 375 -
14.4.1 Error Logging - 375 -
14.4.2 Waiting for System Threads to Finish - 375 -
14.4.3 Work Items - 375 -
15 Distributing Device Drivers - 377 -
15.1 The Role of the Registry - 377 -
15.1.1 The Hardware (Instance) Key - 378 -
15.1.2 The Class Key - 379 -
15.1.3 The Driver Key - 380 -
15.1.4 The Service (Software) Key - 380 -
15.1.5 Accessing the Registry from a Program - 381 -
15.1.6 Device Object Properties - 382 -
15.2 The INF File - 383 -
15.2.1 Install Sections - 386 -
15.2.2 Populating the Registry - 388 -
15.2.3 Security Settings - 391 -
15.2.4 Strings and Localization - 392 -
15.2.5 Device Identifiers - 392 -
15.2.6 Driver Ranking - 396 -
15.2.7 Tools for INF Files - 397 -
15.3 Defining a Device Class - 399 -
15.3.1 A Property Page Provider - 400 -
15.4 Customizing Setup - 402 -
15.4.1 Installers and Co-installers - 403 -
9. VII
15.4.2 Preinstalling Driver Files - 407 -
15.4.3 Value-Added Software - 407 -
15.4.4 Installing a Driver Programmatically - 408 -
15.4.5 The RunOnce Key - 408 -
15.4.6 Launching an Application - 409 -
15.5 The Windows Hardware Quality Lab - 409 -
15.5.1 Running the Hardware Compatibility Tests - 409 -
15.5.2 Submitting a Driver Package - 413 -
15.6 Windows 98/Me Compatibility Notes - 417 -
15.6.1 Property Page Providers - 417 -
15.6.2 Installers and Co-installers - 417 -
15.6.3 Preinstalled Driver Packages - 417 -
15.6.4 Digital Signatures - 418 -
15.6.5 Installing Drivers Programmatically - 418 -
15.6.6 CONFIGMG API - 418 -
15.6.7 INF Incompatibilities - 418 -
15.6.8 Registry Usage - 418 -
15.7 Getting Device Properties - 419 -
16 Filter Drivers - 421 -
16.1 The Role of a Filter Driver - 421 -
16.1.1 Upper Filter Drivers - 421 -
16.1.2 Lower Filter Drivers - 423 -
16.2 Mechanics of a Filter Driver - 424 -
16.2.1 The DriverEntry Routine - 424 -
16.2.2 The AddDevice Routine - 424 -
16.2.3 The DispatchAny Function - 426 -
16.2.4 The DispatchPower Routine - 426 -
16.2.5 The DispatchPnp Routine - 427 -
16.3 Installing a Filter Driver - 428 -
16.3.1 Installing a Class Filter - 428 -
16.3.2 Installing a Device Filter with the Function Driver - 430 -
16.3.3 Installing a Device Filter for an Existing Device - 430 -
16.4 Case Studies - 430 -
16.4.1 Lower Filter for Traffic Monitoring - 430 -
16.4.2 Named Filters - 431 -
16.4.3 Bus Filters - 433 -
16.4.4 Keyboard and Mouse Filters - 433 -
16.4.5 Filtering Other HID Devices - 435 -
16.5 Windows 98/Me Compatibility Notes - 435 -
16.5.1 WDM Filters for VxD Drivers - 435 -
16.5.2 INF Shortcut - 436 -
16.5.3 Class Filter Drivers - 436 -
A Coping with Cross-Platform Incompatibilities - 437 -
Determining the Operating System Version - 437 -
Run-Time Dynamic Linking - 437 -
Checking Platform Compatibility - 438 -
Defining Win98/Me Stubs for Kernel-Mode Routines - 439 -
Version Compatibility - 440 -
10. VIII
Stub Functions - 440 -
Using WDMSTUB - 442 -
Interaction Between WDMSTUB and WDMCHECK - 442 -
Special Licensing Note - 442 -
B Using WDMWIZ.AWX - 443 -
Basic Driver Information - 443 -
DeviceIoControl Codes - 444 -
I/O Resources - 445 -
USB Endpoints - 445 -
WMI Support - 446 -
Parameters for the INF File - 447 -
Now What? - 448 -
11. IX
Acknowledgments
Many people helped me write this book. At the beginning of the project, Anne Hamilton, Senior Acquisitions Editor at
Microsoft Press, had the vision to realize that a revision of this book was needed. Juliana Aldous, the Acquisitions Editor,
shepherded the project through to the complete product you're holding in your hands. Her team included Dick Brown, Jim
Fuchs, Shawn Peck, Rob Nance, Sally Stickney, Paula Gorelick, Elizabeth Hansford, and Julie Kawabata. That the grammar
and diction in the book are correct, that the figures are correctly referenced and intelligible, and that the index accurately
correlates with the text are due to all of them.
Marc Reinig and Dr. Lawrence M. Schoen provided valuable assistance with a linguistic and typographical issue.
Mike Tricker of Microsoft deserves special thanks for championing my request for a source code license, as does Brad
Carpenter for his overall support of the revision project.
Eliyas Yakub acted as the point man to obtain technical reviews of the content of the book and to facilitate access to all sorts of
resources within Microsoft. Among the developers and managers who took time from busy schedules to make sure that this
book would be as accurate as possible are—in no particular order—Adrian Oney (no relation, but I'm fond of pointing out his
vested interest in a book that has his name on the spine), Allen Marshall, Scott Johnson, Martin Borve, Jean Valentine, Doron
Holan, Randy Aull, Jake Oshins, Neill Clift, Narayanan Ganapathy, Fred Bhesania, Gordan Lacey, Alan Warwick, Bob Fruth,
and Scott Herrboldt.
Lastly, my wife, Marty, provided encouragement and support throughout the project.
12. X
Introduction
This book explains how to write device drivers for the newest members of the Microsoft Windows family of operating systems
using the Windows Driver Model (WDM). In this Introduction, I'll explain who should be reading this book, the organization
of the book, and how to use the book most effectively. You'll also find a note on errors and a section on other resources you can
use to learn about driver programming. Looking ahead, Chapter 1 explains how the two main branches of the Windows family
operate internally, what a WDM device driver is, and how it relates to the rest of Windows.
Who Should Read This Book
I've aimed this book at experienced programmers who don't necessarily know anything about writing device drivers for
Windows operating systems. This book is for you if you want to learn how to do that. To succeed at driver writing, you will
need to understand the C programming language very well because WDM drivers are written in C. You'll also need to be
exceptionally able to tolerate ambiguity and to reverse-engineer portions of the operating system because a good deal of trial
and error in the face of incomplete or inaccurate information is required.
Writing a WDM driver is much like writing a kernel-mode driver for Windows NT4.0. It's a bit easier because you don't have
to detect and configure your own hardware. Ironically, it's simultaneously harder because correctly handling Plug and Play and
power management is fiendishly difficult. If you've written kernel-mode drivers for Windows NT, you'll have no trouble at all
reading this book. You'll also be glad to have some code samples that you can cut and paste to deal with the aforementioned
fiendishly difficult areas.
Writing a WDM driver is completely unlike writing a virtual device driver (VxD) for Windows 3.0 and its successors, a UNIX
driver, or a real-mode driver for MS-DOS. If your experience lies in those areas, expect to work hard learning this new
technology. Nonetheless, I think programming WDM drivers is easier than programming those other drivers because you have
more rules to follow, leading to fewer choices between confusing alternatives. Of course, you have to learn the rules before you
can benefit from that fact.
If you already own a copy of the first edition of this book and are wondering whether you should buy this revised edition,
here's a bit of information to help you decide. Windows XP and Windows Me made few changes in the way you develop
drivers for Windows 2000 and Windows 98, respectively. The main reason we decided to revise this book is that so many
changes had accumulated on my update/errata Web page. This edition does, of course, explain some of the new bells and
whistles that Windows XP brings with it. It contains more explicit advice about writing robust, secure drivers. It also, frankly,
explains some things much better than the first edition does.
Chapter 1 has some information that will be useful to development managers and others who need to plan hardware projects.
It's very embarrassing to be brought up short near the end of a hardware development project by the realization that you need a
driver. Sometimes you'll be able to find a generic driver that will handle your hardware. Often, however, such a driver won't
exist and you'll need to write one yourself. I hope to convince you managers in the first chapter that writing drivers is pretty
hard and deserves your attention earlier rather than later. When you're done reading that chapter, by the way, give the book to
the person who's going to carry the oar. And buy lots more copies. (As I told one of my college friends, you can always use the
extra copies as dining room chair extenders for a young family.)
Organization of This Book
After teaching driver programming seminars for many years, I've come to understand that people learn things in fundamentally
different ways. Some people like to learn a great deal of theory about something and then learn how to apply that theory to
practical problems. Other people like to learn practical things first and then learn the general theory. I call the former approach
deductive and the latter approach inductive. I personally prefer an inductive approach, and I've organized this book to suit that
style of learning.
My aim is to explain how to write device drivers. Broadly speaking, I want to provide the minimum background you'll need to
write an actual driver and then move on to more specialized topics. That "minimum background" is pretty extensive, however;
it consumes seven chapters. Once past Chapter 7, you'll be reading about topics that are important but not necessarily on the
fall line that leads straight downhill to a working driver.
Chapter 1, "Beginning a Driver Project," as I've mentioned, describes WDM device drivers and how they relate to Windows
itself. Along the way, I'll relate the story of how we got to where we are today in operating system and driver technology. The
chapter also explains how to choose the kind of driver you need, provides an overview and checklist specifically for
development managers, and addresses the issue of binary compatibility.
Chapter 2, "Basic Structure of a WDM Driver," explains the basic data structures that Windows 2000 uses to manage I/O
devices and the basic way your driver relates to those data structures. I'll discuss the driver object and the device object. I'll
also discuss how you write two of the subroutines—the DriverEntry and AddDevice routines—that every WDM driver
package contains.
19. - 1 -
Chapter 1
1 Beginning a Driver Project
In this chapter, I’ll present an overview of the driver writing process. My own personal involvement with personal computing
dates from the mid-1980s, when IBM introduced its personal computer (PC) with MS-DOS as the operating system. Decisions
made by IBM and Microsoft that long ago are still being felt today. Consequently, a bit of historical perspective will help you
understand how to program device drivers.
Windows Driver Model (WDM) drivers run in two radically different operating system environments, and I’ll provide an
overview of the architecture of these environments in this chapter. Windows XP, like Windows 2000 and earlier versions of
Windows NT, provides a formal framework in which drivers play well-defined roles in carrying out I/O operations on behalf of
applications and other drivers. Windows Me, like Windows 9x and Windows 3.x before it, is a more freewheeling sort of
system in which drivers play many roles.
The first step in any driver project is to decide what kind of driver you need to write—if indeed you need to write one at all.
I’ll describe many different classes of device in this chapter with a view toward helping you make this decision.
Finally I’ll round out the chapter with a management checklist to help you understand the scope of the project.
1.1 A Brief History of Device Drivers
The earliest PCs ran on an Intel processor chip that provided addressability for 640 KB of “real” memory—so called because
the memory was really there in the form of memory chips that the processor addressed directly by means of a 20-bit physical
address. The processor itself offered just one mode of operation, the so-called real mode, wherein the processor combined
information from two 16-bit registers to form a 20-bit memory address for every instruction that referenced memory. The
computer architecture included the concept of expansion slots that brave users could populate with cards purchased separately
from the computer itself. The cards themselves usually came with instructions about how to set DIP switches (later, jumpers
between pins) in order to make slight changes in I/O configuration. You had to keep a map of all the I/O and interrupt
assignments for your PC in order to do this correctly. MS-DOS incorporated a scheme based on the CONFIG.SYS file whereby
the operating system could load real-mode device drivers for original equipment and for add-on cards. Inevitably, these drivers
were programmed in assembly language and relied to a greater or lesser extent on the INT instruction to talk to the BIOS and
to system services within MS-DOS itself. End users perforce learned how to invoke applications via commands. Application
programmers perforce learned how to program the video display, keyboard, and mouse directly because neither MS-DOS nor
the system BIOS did so adequately.
Later on, IBM introduced the AT class of personal computers based on the Intel 80286 processor. The 286 processor added a
protected mode of operation wherein programs could address up to 16 MB of main and extended memory using a 24-bit
segment address (specified indirectly via a segment selector in a 16-bit segment register) and a 16-bit offset. MS-DOS itself
remained a real-mode operating system, so several software vendors built DOS extender products to allow programmers to
migrate their real-mode applications to protected mode and gain access to all the memory that was becoming available on the
market. Since MS-DOS was still in charge of the computer, driver technology didn’t advance at this point.
The watershed change in PC technology occurred—in my view, anyway—when Intel released the 80386 processor chip. The
386 allowed programs to access up to 4 GB of virtual memory addressed indirectly via page tables, and it allowed programs to
easily use 32-bit quantities for arithmetic and addressing. There was a flurry of activity in the software tools market as
compiler vendors and DOS extender companies raced to capture the ever-growing volume of large applications hungry for
memory and processor speed. Device drivers were still 16-bit real-mode programs written in assembly language and installed
via CONFIG.SYS, and end users still needed to manually configure cards.
Subsequent advances in processor chips have been mainly in the area of performance and capacity. As I write this chapter,
computers operating faster than 1 GHz with 50-GB hard drives and 512 MB (or more) of memory are commonplace and easily
affordable by large segments of the population.
In parallel with the evolution of the platform, another evolution was occurring with operating system technology. Most people,
even including programmers of system software, prefer graphics-based ways of interacting with computers to character-based
ways. Microsoft was late to the graphical operating system party—Apple beat them with the first Macintosh—but has come to
dominate it with the Windows family of operating systems. In the beginning, Windows was just a graphical shell for real-mode
MS-DOS. Over time, a collection of Windows drivers for common hardware, including the display, keyboard, and mouse,
came into existence. These drivers were executable files with a .DRV extension, and they were written primarily in assembly
language.
With the advent of the AT class of computer, Microsoft added a protected-mode version of Windows. Microsoft ported the
real-mode .DRV drivers to protected mode as well. Hardware other than the standard Windows devices (the display, keyboard,
and mouse) continued to be handled by real-mode MS-DOS drivers.
29. - 11 -
Chapter 2
2 Basic Structure of a WDM Driver
In the first chapter, I described the basic architecture of the Microsoft Windows XP and Microsoft Windows 98/Me operating
systems. I explained that the purpose of a device driver is to manage a piece of hardware on behalf of the system, and I
discussed how to decide what kind of driver your hardware will need. In this chapter, I’ll describe more specifically what
program code goes into a WDM driver and how different kinds of drivers work together to manage hardware. I’ll also present
an overview of how the system finds and loads drivers.
2.1 How Drivers Work
A useful way to think of a complete driver is as a container for a collection of subroutines that the operating system calls to
perform various operations that relate to your hardware. Figure 2-1 illustrates this concept. Some routines, such as the
DriverEntry and AddDevice routines, as well as dispatch functions for a few types of I/O Request Packet (IRP), will be present
in every such container. Drivers that need to queue requests might have a StartIo routine. Drivers that perform direct memory
access (DMA) transfers will have an AdapterControl routine. Drivers for devices that generate hardware interrupts will have an
interrupt service routine (ISR) and a deferred procedure call (DPC) routine. Most drivers will have dispatch functions for
several types of IRP besides the three that are shown in Figure 2-1. One of your jobs as the author of a WDM driver, therefore,
is to select the functions that need to be included in your particular container.
Figure 2-1. A driver considered as a package of subroutines.
I’ll show you in this chapter how to write the DriverEntry and AddDevice routines for a monolithic function driver, one of the
types of WDM driver this book discusses. As you’ll learn in later chapters, filter drivers also have DriverEntry and AddDevice
routines that are similar to what you’ll see here. As you’ll also learn, minidrivers have very different DriverEntry routines and
may or may not have AddDevice routines, all depending on how the author of the associated class driver designed the class
driver interface.
2.1.1 How Applications Work
It’s worth a moment to reflect on the implications of the “package of subroutines” model for a driver by contrasting it with the
“main program and helpers” model that applies to an application. Consider this program, which is among the first that many of
us learn to write: