100% found this document useful (1 vote)
839 views1,279 pages

Kernel-Mode Driver Architecture Design Guide (Microsoft)

This document provides an overview of the key components and architecture for developing kernel-mode drivers in Windows, including driver objects, device objects, I/O request packets (IRPs), memory management, security, interrupt handling, and direct memory access (DMA). It describes the standard routines and interfaces that kernel-mode drivers use to interface with the Windows kernel and handle I/O requests. It also provides guidance on techniques for asynchronous I/O, queueing IRPs, canceling IRPs, and accessing device configuration spaces.

Uploaded by

conys voms
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
839 views1,279 pages

Kernel-Mode Driver Architecture Design Guide (Microsoft)

This document provides an overview of the key components and architecture for developing kernel-mode drivers in Windows, including driver objects, device objects, I/O request packets (IRPs), memory management, security, interrupt handling, and direct memory access (DMA). It describes the standard routines and interfaces that kernel-mode drivers use to interface with the Windows kernel and handle I/O requests. It also provides guidance on techniques for asynchronous I/O, queueing IRPs, canceling IRPs, and accessing device configuration spaces.

Uploaded by

conys voms
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1279

Contents

Kernel-Mode Driver Architecture Design Guide


Overview
Windows Components
Types of Windows Drivers
Design Goals for Kernel-Mode Drivers
Portable
Configurable
Always Preemptible and Always Interruptible
Multiprocessor-Safe
Object-Based
Packet-Driven I/O with Reusable IRPs
Supporting Asynchronous I/O
Sample Kernel-Mode Drivers
Surface Team Driver Development Best Practices
Components
Windows Kernel-Mode Object Manager
Windows Kernel-Mode Memory Manager
Windows Kernel-Mode Process and Thread Manager
Windows Kernel-Mode I/O Manager
Windows Kernel-Mode Plug and Play Manager
Windows Kernel-Mode Power Manager
Windows Kernel-Mode Configuration Manager
Windows Kernel-Mode Kernel Transaction Manager
Windows Kernel-Mode Security Reference Monitor
Windows Kernel-Mode Kernel Library
Windows Kernel-Mode Executive Support Library
Windows Kernel-Mode Run-Time Library
Windows Kernel-Mode Safe String Library
Windows Kernel-Mode DMA Library
Windows Kernel-Mode HAL Library
Windows Kernel-Mode CLFS Library
Windows Kernel-Mode WMI Library
Writing WDM Drivers
Windows Driver Model (WDM)
Introduction to WDM
Types of WDM Drivers
Bus Drivers
Function Drivers
Filter Drivers
WDM Driver Layers: An Example
Device Configurations and Layered Drivers
Sample Device and Driver Configuration
Points to Consider When Adding Drivers
Determining the WDM Version
Differences in WDM Versions
Kernel-Mode Driver Components
Introduction to Standard Driver Routines
Standard Driver Routine Requirements
Introduction to Driver Objects
Driver Entry Points in Driver Objects
Other Standard Driver Routines
Writing a DriverEntry Routine
DriverEntry's Required Responsibilities
DriverEntry's Optional Responsibilities
DriverEntry Return Values
Writing a Reinitialize Routine
Writing an AddDevice Routine
AddDevice Routines in Function or Filter Drivers
AddDevice Routines in Bus Drivers
Guidelines for Writing AddDevice Routines
Writing Dispatch Routines
Dispatch Routine Functionality
Required Dispatch Routines
Optional Dispatch Routines
Dispatch Routines and IRQLs
When to Check the Driver's I/O Stack Location
DispatchCreate, DispatchClose, and DispatchCreateClose Routines
Separate DispatchCreate and DispatchClose Routines
A Single DispatchCreateClose Routine
Rules for Implementing DispatchCreate, DispatchClose, and DispatchCreateClose
Routines
DispatchCleanup Routines
DispatchRead, DispatchWrite, and DispatchReadWrite Routines
Handling Transfers Asynchronously
DispatchReadWrite Using Buffered I/O
DispatchReadWrite Using Direct I/O
DispatchReadWrite in Higher-Level Drivers
Summary of Read/Write Dispatch Routines
DispatchDeviceControl and DispatchInternalDeviceControl Routines
DispatchDeviceControl in Lowest-Level Drivers
DispatchDeviceControl in Higher-Level Drivers
Dispatch(Internal)DeviceControl in Class/Port Drivers
Guidelines for Writing Dispatch(Internal)DeviceControl Routines
DispatchPnP Routines
DispatchPower Routines
DispatchQueryInformation Routines
DispatchSetInformation Routines
DispatchFlushBuffers Routines
DispatchShutdown Routines
DispatchSystemControl Routines
Writing an Unload Routine
Unload Routine Environment
Unload Routine Functionality
PnP Driver's Unload Routine
Non-PnP Driver's Unload Routine
Releasing Driver-Allocated Resources
Releasing Device and Controller Objects
Device Objects and Device Stacks
Introduction to Device Objects
Types of WDM Device Objects
Example WDM Device Objects
When Are WDM Device Objects Created?
Example WDM Device Stack
Creating a Device Object
Initializing a Device Object
Named Device Objects
NT Device Names
Introduction to MS-DOS Device Names
Local and Global MS-DOS Device Names
Device Extensions
Properties of Device Objects
Specifying Device Types
Specifying Device Characteristics
Specifying Exclusive Access to Device Objects
Setting Device Object Properties in the Registry
Setting Device Object Registry Properties During Installation
Setting Device Object Registry Properties After Installation
Points to Consider About Device Objects
Managing Kernel Objects
Object Names
Object Directories
Life Cycle of an Object
Object Handles
Memory Management
Memory Management for Windows Drivers
Overview of Windows Memory Space
Allocating System-Space Memory
Map Registers
Mapping Bus-Relative Addresses to Virtual Addresses
Using the Kernel Stack
Using Lookaside Lists
Making Drivers Pageable
When Should Code and Data Be Pageable?
Detecting Code That Can Be Pageable
Isolating Pageable Code
Locking Pageable Code or Data
Paging an Entire Driver
Accessing Read-Only System Memory
Accessing User-Space Memory
No-Execute (NX) Nonpaged Pool
NX and Execute Pool Types
NX Pool Compatibility Issues
NX Pool Opt-In Mechanisms
Single Binary Opt-In: POOL_NX_OPTIN
Multiple Binary Opt-In: POOL_NX_OPTIN_AUTO
Selective Opt-Out: POOL_NX_OPTOUT
Section Objects and Views
File-Backed and Page-File-Backed Sections
Managing Memory Sections
Security Issues for Section Objects and Views
Using MDLs
Security
Controlling Device Access
Controlling Device Namespace Access
SDDL for Device Objects
Access Rights
Security Descriptors
Privileges
I/O
Handling IRPs
Overview of the Windows I/O Model
End-User I/O Requests and File Objects
Example I/O Request - An Overview
Example I/O Request - The Details
Driver Thread Context
Points to Consider about User I/O Requests
IRP Major Function Codes
IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
IRP_MJ_QUERY_INFORMATION
IRP_MJ_READ
IRP_MJ_SET_INFORMATION
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE
I/O Stack Locations
I/O Status Blocks
Passing IRPs down the Driver Stack
Creating IRPs for Lower-Level Drivers
Queuing and Dequeuing IRPs
Writing a StartIo Routine
StartIo Routines in Lowest-Level Drivers
StartIo Routines in Higher-Level Drivers
Points to Consider For StartIo Routines
Driver-Managed IRP Queues
Setting Up and Using Device Queues
Managing Device Queues
Setting Up and Using Interlocked Queues
Managing Interlocked Queues with a Driver-Created Thread
Cancel-Safe IRP Queues
Completing IRPs
When to Complete an IRP
How to Complete an IRP in a Dispatch Routine
When to Complete an IRP in a Dispatch Routine
Using IoCompletion Routines
Registering an IoCompletion Routine
Implementing an IoCompletion Routine
Canceling IRPs
Introduction to Cancel Routines
Registering a Cancel Routine
Points to Consider When Canceling IRPs
Synchronizing IRP Cancellation
Using the System's Cancel Spin Lock
Synchronizing Cancellation in Driver Routines that Process IRPs
Synchronizing Cancellation in Higher-Level Drivers without Cancel Routines
Using a Driver-Supplied Spin Lock
Implementing a Cancel Routine
Cancel Routines in Drivers with StartIo Routines
Cancel Routines in Drivers without StartIo Routines
Reusing IRPs
Device Type-Specific I/O Requests
Introduction to I/O Control Codes
Creating IOCTL Requests in Drivers
Defining I/O Control Codes
Buffer Descriptions for I/O Control Codes
Security Issues for I/O Control Codes
Using IRP Priority Hints
Processing IRPs in a Lowest-Level Driver
Processing IRPs in an Intermediate-Level Driver
Different ways of handling IRPs - Cheat sheet
I/O Programming Techniques
General I/O Programming Techniques
Synchronous I/O Programming
Asynchronous I/O Programming
Restricting Waits in Vista
Avoid Polling Devices
Maintaining Cache Coherency
Methods for Accessing Data Buffers
Using Buffered I/O
Using Direct I/O
Using Direct I/O with DMA
Using Direct I/O with PIO
Using Neither Buffered Nor Direct I/O
DMA Programming Techniques
Flushing Cached Data during DMA Operations
Splitting DMA Transfer Requests
DMA
Introduction to Adapter Objects
Getting an Adapter Object
Using Scatter/Gather DMA
Writing AdapterControl Routines
Storage Requirements for AdapterControl Routines
Setting Up AdapterControl Routines
AdapterControl Routine Requirements
Using Packet-Based System DMA
Allocating an Adapter Channel for Packet-Based DMA
Setting Up the System DMA Controller for Packet-Based DMA
Using Common-Buffer System DMA
Allocating an Adapter Channel for Common-Buffer System DMA
Setting Up the System DMA Controller for Common-Buffer DMA
Using Bus-Master DMA
Using Packet-Based Bus-Master DMA
Allocating the Bus-Master Adapter Object
Setting Up a Transfer Operation
Using Common-Buffer Bus-Master DMA
Version 3 of the DMA Operations Interface
Basic Calling Pattern for Version-3 DMA Routines
Using the MapTransferEx Routine
PIO Techniques
Flushing Cached Data during PIO Operations
Accessing Device Configuration Space
Obtaining Device Configuration Information at IRQL = PASSIVE_LEVEL
Obtaining Device Configuration Information at IRQL = DISPATCH_LEVEL
Obtaining Configuration Information from Other Driver Stacks
Controller Objects
Introduction to Controller Objects
Creating Controller Objects and Controller Extensions
Allocating Controller Objects for I/O Operations
Writing ControllerControl Routines
Storage Requirements for ControllerControl Routines
Setting Up ControllerControl Routines
ControllerControl Routine Requirements
Interrupt Service Routines (ISRs)
Introduction to Interrupt Service Routines
Removing an ISR
Making an ISR Active or Inactive
Interrupt Affinity
Providing ISR Context Information
Writing an ISR
Synchronizing Access to Device Data
Registering an ISR
Using the CONNECT_LINE_BASED Version of IoConnectInterruptEx
Using the CONNECT_MESSAGE_BASED Version of IoConnectInterruptEx
Using the CONNECT_FULLY_SPECIFIED Version of IoConnectInterruptEx
Using Passive-Level Interrupt Service Routines
Using IoConnectInterruptEx Prior to Windows Vista
Message-Signaled Interrupts (MSIs)
Introduction to Message-Signaled Interrupts
Enabling Message-Signaled Interrupts in the Registry
Using Interrupt Resource Descriptors
Dynamically Configuring MSI-X
Deferred Procedure Calls (DPCs)
Introduction to DPC Objects
Introduction to DPCs
Which Type of DPC Should You Use?
Registering and Queuing a DpcForIsr Routine
Registering and Queuing a CustomDpc Routine
Handling Overlapped I/O Operations
Writing DPC Routines
Guidelines for Writing DPC Routines
Organization of DPC Queues
Introduction to Threaded DPCs
Synchronization and Threaded DPCs
Converting an Ordinary DPC to a Threaded DPC
Using Critical Sections
Introduction to SynchCritSection Routines
Writing SynchCritSection Routines
Programming a Device for an I/O Operation
Accessing Shared State Information
Managing Hardware Priorities
Plug and Play (PnP)
Introduction to Plug and Play
Introduction to Plug and Play
PnP Components
Levels of Support for PnP
PnP Driver Design Guidelines
Device Tree
Hardware Resources
State Transitions for PnP Devices
Adding a PnP Device to a Running System
Plug and Play Minor IRPs
IRP_MN_CANCEL_REMOVE_DEVICE
IRP_MN_CANCEL_STOP_DEVICE
IRP_MN_DEVICE_ENUMERATED
IRP_MN_DEVICE_USAGE_NOTIFICATION
IRP_MN_EJECT
IRP_MN_FILTER_RESOURCE_REQUIREMENTS
IRP_MN_QUERY_BUS_INFORMATION
IRP_MN_QUERY_CAPABILITIES
IRP_MN_QUERY_DEVICE_RELATIONS
IRP_MN_QUERY_DEVICE_TEXT
IRP_MN_QUERY_ID
IRP_MN_QUERY_INTERFACE
IRP_MN_QUERY_LEGACY_BUS_INFORMATION
IRP_MN_QUERY_PNP_DEVICE_STATE
IRP_MN_QUERY_REMOVE_DEVICE
IRP_MN_QUERY_RESOURCE_REQUIREMENTS
IRP_MN_QUERY_RESOURCES
IRP_MN_QUERY_STOP_DEVICE
IRP_MN_READ_CONFIG
IRP_MN_REMOVE_DEVICE
IRP_MN_SET_LOCK
IRP_MN_START_DEVICE
IRP_MN_STOP_DEVICE
IRP_MN_SURPRISE_REMOVAL
IRP_MN_WRITE_CONFIG
Passing PnP IRPs Down the Device Stack
Postponing PnP IRP Processing Until Lower Drivers Finish
Starting a Device
Starting a Device in a Function Driver
Starting a Device in a Filter Driver
Starting a Device in a Bus Driver
Design Guidelines for Starting Devices
Stopping a Device
Stopping a Device to Rebalance Resources
Stopping a Device to Disable It (Windows 98/Me)
Stopping a Device after a Failed Start (Windows 98/Me)
Handling Stop IRPs (Windows 2000 and Later)
Handling an IPR_MN_QUERY_STOP_DEVICE Request
Handling an IRP_MN_STOP_DEVICE Request
Handling an IRP_MN_CANCEL_STOP_DEVICE Request
Holding Incoming IRPs When A Device Is Paused
Resetting and recovering a device
Removing a Device
Understanding When Remove IRPs Are Issued
Handling an IRP_MN_QUERY_REMOVE_DEVICE Request
Handling an IRP_MN_REMOVE_DEVICE Request
Removing a Device in a Function Driver
Removing a Device in a Filter Driver
Removing a Device in a Bus Driver
Handling an IRP_MN_CANCEL_REMOVE_DEVICE Request
Handling an IRP_MN_SURPRISE_REMOVAL Request
Using Remove Locks
Using PnP Notification
PnP Notification Overview
Guidelines for Writing PnP Notification Callback Routines
Using PnP Device Interface Change Notification
Registering for Device Interface Change Notification
Handling Device Interface Change Events
Using PnP Target Device Change Notification
Registering for Target Device Change Notification
Handling a GUID_TARGET_DEVICE_QUERY_REMOVE Event
Handling a GUID_TARGET_DEVICE_REMOVE_COMPLETE Event
Handling a GUID_TARGET_DEVICE_REMOVE_CANCELLED Event
Using PnP Hardware Profile Change Notification
Registering for Hardware Profile Change Notification
Handling Hardware Profile Change Events
Using PnP Custom Notification
Power Management
Introduction to Power Management
Industry Initiatives for Power Management
Support for Power Management
System-Wide Overview of Power Management
Power States
ACPI BIOS
Acpi.sys: The Windows ACPI Driver
Power Manager
Driver Role in Power Management
ACPI notifications
Device power management (DPM) notifications
Processor power management (PPM) notifications
PPM power control codes
Power Management Responsibilities for Drivers
Reporting Device Power Capabilities
DeviceD1 and DeviceD2
WakeFromD0, WakeFromD1, WakeFromD2, and WakeFromD3
DeviceState
SystemWake
DeviceWake
D1Latency, D2Latency, and D3Latency
Setting Device Object Flags for Power Management
Handling Power IRPs
Power IRPs for the System
Power IRPs for Individual Devices
Power Management Minor IRPs
IRP_MN_POWER_SEQUENCE
IRP_MN_QUERY_POWER
IRP_MN_SET_POWER
IRP_MN_WAIT_WAKE
Powering Up a Device
Powering Down a Device
Enabling Device Wake-Up
Managing Device Performance States
Distinguishing Fast Startup from Wake-from-Hibernation
Calling IoCallDriver vs. Calling PoCallDriver
Calling PoStartNextPowerIrp
Calling PoStartNextPowerIrp from a Filter Driver
Calling PoStartNextPowerIrp from a Device Power Policy Owner
Calling PoStartNextPowerIrp from a Bus Driver
Passing Power IRPs
Queuing I/O Requests While a Device Is Sleeping
Handling Unsupported or Unrecognized Power IRPs
Calling ExSetTimerResolution While Processing a Power IRP
Device Power States
Device Working State D0
Device Low-Power States
Required Support for Device Power States
Managing Device Power Policy
Handling IRP_MN_SET_POWER for Device Power States
Handling Device Power-Down IRPs
Handling Device Power-Up IRPs
IoCompletion Routines for Device Power IRPs
Handling IRP_MN_QUERY_POWER for Device Power States
Sending IRP_MN_QUERY_POWER or IRP_MN_SET_POWER for Device Power States
Detecting an Idle Device
Using Power Manager Routines for Idle Detection
Performing Device-Specific Idle Detection
Supporting D3cold in a Driver
Enabling Transitions to D3cold
D3cold Capabilities of a Device
Using the GUID_D3COLD_SUPPORT_INTERFACE Driver Interface
Surprise Wake-Up
Handling System Power State Requests
System Power States
System Working State S0
System Sleeping States
System Shutdown State S5
System Power Actions
System Power Policy
Preventing System Power State Changes
Handling IRP_MN_QUERY_POWER for System Power States
Handling a System Query-Power IRP in a Device Power Policy Owner
Handling a System Query-Power IRP in a Filter or Function Driver
Failing a System Query-Power IRP in a Filter or Function Driver
Handling a System Query-Power IRP in a Bus Driver
Handling IRP_MN_SET_POWER for System Power States
Handling a System Set-Power IRP in a Device Power Policy Owner
Determining the Correct Device Power State
Sending a Device Set-Power IRP in Response to a System Set-Power IRP
Handling a System Set-Power IRP in a Bus Driver
Handling a System Set-Power IRP in a Filter Driver
Overview of the Power Management Framework
Device power management reference
Component-Level Power Management
Component-Level Performance State Management
Introduction to the Directed Power Management Framework
Platform Extension Plug-ins (PEPs)
Using PEPs for ACPI services
Platform Performance Thresholds
Supporting Devices that Have Wake-Up Capabilities
Overview of Wait/Wake Operation
Determining Whether a Device Can Wake the System
Understanding the Path of Wait/Wake IRPs through a Device Tree
Overview of Wait/Wake IRP Completion
Receiving a Wait/Wake IRP
Handling a Wait/Wake IRP in a Function (FDO) or Filter Driver (Filter DO)
Handling a Wait/Wake IRP in a Bus Driver (PDO)
IoCompletion Routines for Wait/Wake IRPs
Sending a Wait/Wake IRP
Determining When to Send a Wait/Wake IRP
Wait/Wake IRP Requests
Wait/Wake Callback Routines
Canceling a Wait/Wake IRP
Improving System Startup Performance
Sharing Processor Resources During Startup from a Low-Power State
Fast Startup from a Low-Power State
Device-Level Thermal Management
Passive and Active Cooling Modes
Global thermal management
Windows Management Instrumentation (WMI)
Implementing WMI
Introduction to WMI
WMI Architecture
WMI Requirements for WDM Drivers
MOF Syntax for WMI Data and Event Blocks
WMI Class Qualifiers
WMI Class Names and Base Classes
Required Items in WMI Classes
WMI Property Qualifiers
Driver-Defined WMI Data Items
WMI Class Examples
Designing WMI Data and Event Blocks
Supporting Standard WMI Blocks
Implementing Custom WMI Blocks
Defining WMI Instance Names
Publishing a WMI Schema
Compiling a Driver's MOF File
Setting the MofImagePath Registry Value
Implementing Dynamic MOF Data
Localizing MOF Files
Creating the Localized MOF File
Building and Deploying the Localized MOF File
Registering as a WMI Data Provider
Using the WMI Library to Register Blocks
Handling IRP_MN_REGINFO and IRP_MN_REGINFO_EX to Register Blocks
WMI Registration Flags
Updating WMI Registration Information
Handling WMI Requests
WMI Minor IRPs
IRP_MN_CHANGE_SINGLE_INSTANCE
IRP_MN_CHANGE_SINGLE_ITEM
IRP_MN_DISABLE_COLLECTION
IRP_MN_DISABLE_EVENTS
IRP_MN_ENABLE_COLLECTION
IRP_MN_ENABLE_EVENTS
IRP_MN_EXECUTE_METHOD
IRP_MN_QUERY_ALL_DATA
IRP_MN_QUERY_SINGLE_INSTANCE
IRP_MN_REGINFO
IRP_MN_REGINFO_EX
Calling WmiSystemControl to Handle WMI IRPs
Processing WMI IRPs in a DispatchSystemControl Routine
WMI WNODE_XXX Structures
Sending WMI Events
Sending an Event with WmiFireEvent
Sending an Event with IoWMIWriteEvent
Using Custom WMI Events
WMI Property Sheets
WMI Generic Property Page Provider
WMI and the Power Management Tab
Using Wmimofck.exe
WMI Event Tracing
General Techniques for Testing WMI Driver Support
Troubleshooting Specific WMI Problems
Programming Techniques
Using Nt and Zw Versions of the Native System Services Routines
PreviousMode
Libraries and Headers
What Does the Zw Prefix Mean?
Specifying Access Rights
NtXxx Routines
Synchronization
Kernel Dispatcher Objects
Introduction to Kernel Dispatcher Objects
Timer Objects
KeXxxTimer Routines, KTIMER Objects, and DPCs
Using Timer Objects
Timer Accuracy
Registering and Queuing a CustomTimerDpc Routine
Providing CustomTimerDpc Context Information
Using a CustomTimerDpc Routine
ExXxxTimer Routines and EX_TIMER Objects
High-Resolution Timers
No-Wake Timers
Deleting a System-Allocated Timer Object
Event Objects
Defining and Using an Event Object
Standard Event Objects
Semaphore Objects
Introduction to Mutex Objects
Alternatives to Mutex Objects
Introduction to Thread Objects
Thread Priorities
Device-Dedicated Threads
System Worker Threads
Waits and APCs
Callback Objects
Defining a Callback Object
Using a Driver-Defined Callback Object
Using a System-Defined Callback Object
Spin Locks
Introduction to Spin Locks
Providing Storage for Spin Locks and Protected Data
Initializing Spin Locks
Calling Support Routines That Use Spin Locks
Using Spin Locks: An Example
Preventing Errors and Deadlocks While Using Spin Locks
Queued Spin Locks
Reader/Writer Spin Locks
Fast Mutexes and Guarded Mutexes
ERESOURCE Structures
Introduction to ERESOURCE Routines
IoTimer Routines
Registering and Enabling an IoTimer Routine
Providing IoTimer Context Information
Using an IoTimer Routine
Counters
Asynchronous Procedure Calls
Types of APCs
Disabling APCs
Critical Regions and Guarded Regions
Acquire and Release Semantics
Run-Down Protection
Using Common Log File System
Introduction to the Common Log File System
CLFS Terminology
CLFS Log Sequence Numbers
CLFS Marshalling Areas
Writing Data Records to a CLFS Stream
Writing Restart Records to a CLFS Stream
Reading Data Records from a CLFS Stream
Reading Restart Records from a CLFS Stream
CLFS Stable Storage
Dedicated CLFS Logs
Multiplexed CLFS Logs
CLFS Support for Archiving
Kernel Transaction Manager
Introduction to KTM
When to Use Kernel-Mode KTM
Transaction Processing Terms
Understanding TPS Components
Additional Transactional Interfaces
KTM Objects
Transaction Manager Objects
Resource Manager Objects
Transaction Objects
Enlistment Objects
Using KTM
Creating a Resource Manager
Creating a Transactional Client
Creating a Superior Transaction Manager
Handling Transaction Operations
Handling Commit Operations
Handling Rollback Operations
Handling Recovery Operations
Transaction Notifications
Using Log Streams with KTM
Using Virtual Clock Values
Using TmXxx Routines
Dynamic Hardware Partitioning Techniques
Introduction to Dynamic Hardware Partitioning
Dynamic Hardware Partitioning Architecture
Critical Issues for Device Drivers
Changes to the Number of Processors
Changes to the Amount of Physical Memory
Hot Replace of Partition Units
Driver Notification
Introduction to Driver Notification
Registering for Synchronous Driver Notification
Processing a Synchronous Driver Notification
Registering for Asynchronous Driver Notification
Processing an Asynchronous Driver Notification
Application Notification
Introduction to Application Notification
Registering for Application Notification
Processing an Application Notification
NTSTATUS Values
Using NTSTATUS Values
Defining New NTSTATUS Values
Singly and Doubly Linked Lists
Handling Exceptions
Logging Errors
Writing to the System Event Log
Defining Custom Error Types
Registering as a Source of Error Messages
Writing a Bug Check Reason Callback Routine
Using Safe String Functions
Summary of Kernel-Mode Safe String Functions
Importing Kernel-Mode Safe String Functions
Using Safe Integer Functions
Summary of Kernel-Mode Safe Integer Functions
Importing Kernel-Mode Safe Integer Functions
Determining Whether the Operating System Is Running in Safe Mode
Using GUIDs in Drivers
Defining and Exporting New GUIDs
Including GUIDs in Driver Code
Using Floating Point in a WDM Driver
Using Files In A Driver
Opening a Handle to a File
Using a File Handle
Using the Current File Position
Registry Key Object Routines
Opening a Handle to a Registry-Key Object
Using a Handle to a Registry-Key Object
Registry Run-Time Library Routines
Plug and Play Registry Routines
Supporting Removable Media
Responding to Check-Verify Requests from the File System
Notifying the File System of Possible Media Changes
Checking Flags in the Device Object
Setting up IRPs in Intermediate Drivers
Creating Export Drivers
Creating Reliable Kernel-Mode Drivers
Failure to Validate Device Objects
Failure to Validate Object Handles
Errors in a Multiprocessor Environment
Failure to Check a Driver's State
Errors in Buffered I/O
Failure to Check the Size of Buffers
Failure to Initialize Output Buffers
Failure to Validate Variable-Length Buffers
Errors in Direct I/O
Errors in Referencing User-Space Addresses
Errors in Handling Cleanup and Close Operations
Additional Errors in Handling IRPs
Hiding Devices from Device Manager
Filtering Registry Calls
Registering for Notifications
Handling Notifications
Supporting Layered Registry Filtering Drivers
Specifying Context Information
Obtaining Additional Registry Information
Invalid Key Object Pointers in Registry Notifications
Filtering Registry Operations on Application Hives
Object Reference Tracing with Tags
Porting Your Driver to 64-Bit Windows
What's Changed
The New Data Types
64-Bit Compiler
Performing DMA in 64-Bit Windows
Using extended processor features in Windows drivers
Porting Issues Checklist
Supporting 32-Bit I/O in Your 64-Bit Driver
Why Thunking Is Necessary
Which Data Types Need Thunking
How Drivers Identify 32-Bit Callers
Technique 1: Defining a 64Bit Field
Technique 2: Using IoIs32bitProcess
Extended Example: Defining a 64Bit Field
Extended Example: Using IoIs32bitProcess
Avoiding Misalignment of Fixed-Precision Data Types
Driver x64 Restrictions
Windows kernel obsolete macros
Windows kernel obsolete routines
Windows kernel routines reserved for system use
Windows kernel run-time library obsolete routines
Windows kernel global variables
Windows kernel macros
Windows kernel opaque structures
Working with the GUID_DEVICE_RESET_INTERFACE_STANDARD
Kernel-Mode Driver Architecture Design Guide
6/25/2019 • 2 minutes to read • Edit Online

This section includes general concepts to help you understand kernel-mode programming and describes specific
techniques of kernel programming. This section is divided into four parts:
Introduction to Windows Drivers provides a general overview of Windows components, lists the types of
device drivers used in Windows, discusses the goals of Windows device drivers, and discusses generic
sample device drivers included in the kit.
Kernel-Mode Managers and Libraries lists the primary kernel-mode components of the Windows operating
system.
Writing WDM Drivers provides information needed to write drivers using the Windows Driver Model
(WDM ).
Driver Programming Techniques describes techniques that you can use to program Windows kernel-mode
device drivers.
Note For information about programming interfaces that your driver can implement or call, see Kernel-
Mode Driver Reference.
Overview of Windows Components
10/7/2019 • 2 minutes to read • Edit Online

The following figure shows the major internal components of the Windows operating system.

As the figure shows, the Windows operating system includes both user-mode and kernel-mode components. For
more information about Windows user and kernel modes, see User Mode and Kernel Mode.
Drivers call routines that are exported by various kernel components. For example, to create a device object, you
would call the IoCreateDevice routine which is exported by the I/O manager. For a list of kernel-mode routines
that drivers can call, see Driver Support Routines.
In addition, drivers must respond to specific calls from the operating system and can respond to other system calls.
For a list of kernel mode routines that drivers may need to support, see Standard Driver Routines.
Not all kernel-mode components are pictured in the figure above. For a list of kernel mode components, see
Kernel-Mode Managers and Libraries.
Types of Windows Drivers
10/7/2019 • 2 minutes to read • Edit Online

There are two basic types of Microsoft Windows drivers:


User-mode drivers execute in user mode, and they typically provide an interface between a Win32
application and kernel-mode drivers or other operating system components.
For example, in Windows Vista, all printer drivers execute in user mode. For more information about printer
driver components, see Introduction to Printing.
Kernel-mode drivers execute in kernel mode as part of the executive, which consists of kernel-mode
operating system components that manage I/O, Plug and Play memory, processes and threads, security, and
so on. Kernel-mode drivers are typically layered. Generally, higher-level drivers typically receive data from
applications, filter the data, and pass it to a lower-level driver that supports device functionality.
Some kernel-mode drivers are also WDM drivers, which conform to the Windows Driver Model (WDM ). All
WDM drivers support Plug and Play, and power management. WDM drivers are source-compatible (but
not binary-compatible) across Windows 98/Me and Windows 2000 and later operating systems.
Like the operating system itself, kernel-mode drivers are implemented as discrete, modular components that
have a well-defined set of required functionalities. All kernel-mode drivers supply a set of system-defined
standard driver routines.
The following figure divides kernel-mode drivers into several types.

As shown in the figure, there are three basic types of kernel-mode drivers in a driver stack: highest-level,
intermediate, and lowest-level. Each type differs only slightly in structure but greatly in functionality:
1. Highest-level drivers. Highest-level drivers include file system drivers (FSDs) that support file systems, such
as:
NTFS
File allocation table (FAT)
CD -ROM file system (CDFS )
Highest-level drivers always depend on support from underlying lower-level drivers, such as intermediate-
level function drivers and lowest-level hardware bus drivers.
2. Intermediate drivers, such as a virtual disk, mirror, or device-type-specific class driver. Intermediate drivers
depend on support from underlying lower-level drivers. Intermediate drivers are subdivided further as
follows:
Function drivers control specific peripheral devices on an I/O bus.
Filter drivers insert themselves above or below function drivers.
Software bus drivers present a set of child devices to which still higher-level class, function, or filter
drivers can attach themselves.
For example, a driver that controls a multifunction adapter with an on-board set of heterogeneous
devices is a software bus driver.
Any system-supplied class driver that exports a system-defined class/miniclass interface is, in effect,
an intermediate driver with one or more linked miniclass drivers (sometimes called minidrivers). Each
linked class/minidriver pair provides functionality that is equivalent to that of a function driver or a
software bus driver.
3. Lowest-level drivers control an I/O bus to which peripheral devices are connected. Lowest-level drivers do
not depend on lower-level drivers.
Hardware bus drivers are system-supplied and usually control dynamically configurable I/O buses.
Hardware bus drivers work with the Plug and Play manager to configure and reconfigure system
hardware resources, for all child devices that are connected to the I/O buses that the driver controls.
These hardware resources include mappings for device memory and interrupt requests (IRQs).
(Hardware bus drivers subsume some of the functionality that the HAL component provided in
releases of the Windows NT-based operating system earlier than Windows 2000.)
Legacy drivers that directly control a physical device are lowest-level drivers.
Design Goals for Kernel-Mode Drivers
12/5/2018 • 2 minutes to read • Edit Online

Kernel-mode drivers share many of the design goals of the operating system, particularly those of the system I/O
manager. Kernel-mode drivers are designed to be:
Portable from one platform to another.
Configurable to various hardware and software platforms.
Always preemptible and always interruptible.
Multiprocessor-safe on multiprocessor platforms.
Object-based.
Packet-driven I/O with reusable IRPs.
Capable of supporting asynchronous I/O.
Portable
6/25/2019 • 2 minutes to read • Edit Online

All drivers must be portable across all Windows-supported hardware platforms. To achieve cross-platform
portability, driver writers should:
Code in C (no assembly language).
Interact with Windows by only using the programming interfaces and headers that are supplied in the
WDK.
Coding Drivers in C
All kernel-mode drivers should be written in C so that they can be recompiled with a system-compatible C
compiler, relinked, and run on different Microsoft Windows platforms without rewriting or replacing any code.
Most operating system components are coded entirely in C, with only small pieces of the HAL and kernel
components written in assembly language, so that the operating system is readily portable across hardware
platforms. You cannot use many C++ language constructs in kernel-mode drivers, so you should carefully evaluate
using such constructs. For more information about issues that arise when drivers include C++ features, see the
C++ for Kernel Mode Drivers: Pros and Cons white paper.
Drivers should not rely on the features of any particular system-compatible C compiler or C support library if
those features are not guaranteed to be supported by other system-compatible compilers. In general, driver code
should conform to the ANSI C standard and not depend on anything that this standard describes as
"implementation-defined."
To write portable drivers, it is best to avoid:
Dependencies on data types that can vary in size or layout from one platform to another.
Calling any standard C runtime library function that maintains state.
Calling any standard C runtime library function for which the operating system provides an alternative
support routine.
Using WDK -Supplied Interfaces
Each Windows NT executive component exports a set of kernel-mode driver support routines that drivers and all
other kernel-mode components call. If the underlying implementation of a support routine changes over time, its
callers remain portable because the interface to the defining component does not change.
The WDK supplies a set of header files that define system-specific data types and constants that drivers (and all
other kernel-mode components) use to help maintain portability from one platform to another. All kernel-mode
drivers include one of the master WDK kernel-mode header files, Wdm.h or Ntddk.h. The master header files pull
in not only system-supplied headers that define the basic kernel-mode types, but also appropriate selections from
any processor-architecture-specific headers when a driver is compiled with the corresponding compiler directive.
Some drivers, such as SCSI miniport drivers, NDIS drivers, and video miniport drivers, include other system-
supplied header files.
If a driver requires platform-dependent definitions, it is best to isolate those definitions within #ifdef statements,
so that each driver can be compiled and linked for the appropriate hardware platform. However, you can almost
always avoid implementing any platform-specific, conditionally compiled code in a driver by using the support
routines, macros, constants, and types that the WDK master header files provide.
Kernel-mode drivers can use kernel-mode RtlXxx routines that are documented in the WDK. Kernel-mode drivers
cannot call user-mode RtlXxx routines.
Configurable
12/5/2018 • 2 minutes to read • Edit Online

Today's peripheral devices must be hardware-configurable, and their drivers must be software-configurable.
A device is hardware-configurable if it can accept different assignments of the system's hardware resources, such
as I/O port numbers, without being physically modified. For example, if a set of hot-pluggable Plug and Play disks
are connected in a redundant array of independent disks (RAID ) configuration, a user can swap disks while the
system is running. If a device is hardware-configurable, its drivers cannot contain hard-coded, system-dependent
values for the device's hardware resources.
A driver is software-configurable if:
It can receive and change its device's hardware resources dynamically.
Drivers that support Plug and Play do not contain hard-coded values for a device's hardware resources, nor
does the driver poll the device to determine its resource assignments. Instead, the system dynamically
assigns resources to the device, and then supplies resource values to the driver.
It was written with no assumptions about other drivers that might reside above or below it in its driver
stack.
For example, the design of a lower-level device driver for a disk must be flexible enough to support multiple
file systems that are implemented by multiple high-level file system drivers, possibly on a single computer.
Additionally, if a computer has sufficient mass storage capacity, that same lower-level disk driver must not
interfere with an intermediate driver's support for fault tolerance (implemented as mirrored partitions,
stripe sets, or volume sets) within a file system.
The PnP manager and each PnP hardware bus driver work together to provide an interface between the platform's
hardware for a specific type of I/O bus and the system's software. The PnP manager builds a device tree, with
nodes that represent all the devices on the system, including buses. For each device, the PnP manager maintains
two lists:
A list of the hardware resources that the device is capable of using.
A list of the hardware resources that are actually assigned to the device.
Device drivers assist the PnP manager in creating these lists, which are maintained in the registry. As devices are
added to and removed from the system, the PnP manager reassigns resources as necessary and updates the lists.
The system's hardware abstraction layer (HAL ) component, which is implemented as a dynamic-link library, is
responsible for some of the hardware-level, platform-specific support that is needed by other system components,
including kernel-mode drivers.
Always Preemptible and Always Interruptible
2/14/2019 • 3 minutes to read • Edit Online

The goal of the preemptible, interruptible design of the operating system is to maximize system performance. Any
thread can be preempted by a thread with a higher priority, and any driver's interrupt service routine (ISR ) can be
interrupted by a routine that runs at a higher interrupt request level (IRQL ).
The kernel component determines when a code sequence runs, according to one of these prioritizing criteria:
The kernel-defined run-time priority scheme for threads.
Every thread in the system has an associated priority attribute. In general, most threads have variable
priority attributes: they are always preemptible and are scheduled to run round-robin with all other threads
that are currently at the same priority level. Some threads have real-time priority attributes: these time-
critical threads run to completion unless they are preempted by a thread that has a higher real-time priority
attribute. The Microsoft Windows architecture does not provide an inherently real-time system.
Whatever its priority attribute, any thread in the system can be preempted when hardware interrupts and
certain types of software interrupts occur.
The kernel-defined interrupt request level (IRQL ) to which a particular interrupt vector is assigned on a
given platform.
The kernel prioritizes hardware and software interrupts so that some kernel-mode code, including most
drivers, runs at higher IRQLs, thereby making it have a higher scheduling priority than other threads in the
system. The particular IRQL at which a piece of kernel-mode driver code executes is determined by the
hardware priority of its underlying device.
Kernel-mode code is always interruptible: an interrupt with a higher IRQL value can occur at any time,
thereby causing another piece of kernel-mode code that has a higher system-assigned IRQL to be run
immediately on that processor. However, when a piece of code runs at a given IRQL, the kernel masks all
interrupt vectors with a lesser or equal IRQL value on the processor.
The lowest IRQL level is called PASSIVE_LEVEL. At this level, no interrupt vectors are masked. Threads generally
run at IRQL=PASSIVE_LEVEL. The next higher IRQL levels are for software interrupts. These levels include
APC_LEVEL, DISPATCH_LEVEL or, for kernel debugging, WAKE_LEVEL. Device interrupts have still higher IRQL
values. The kernel reserves the highest IRQL values for system-critical interrupts, such as those from the system
clock or bus errors.
Some system support routines run at IRQL=PASSIVE_LEVEL, either because they are implemented as pageable
code or access pageable data, or because some kernel-mode components set up their own threads.
Similarly, some standard driver routines usually run at IRQL=PASSIVE_LEVEL. However, several standard driver
routines run either at IRQL=DISPATCH_LEVEL or, for a lowest-level driver, at device IRQL (also called DIRQL). For
more information about IRQLs, see Managing Hardware Priorities.
Every routine in a driver is interruptible. This includes any routine that is running at a higher IRQL than
PASSIVE_LEVEL. Any routine that is running at a particular IRQL retains control of the processor only if no
interrupt for a higher IRQL occurs while that routine is running.
Unlike the drivers in some older personal computer operating systems, a Microsoft Windows driver's ISR is never
a large, complex routine that does most of the driver's I/O processing. This is because any driver's interrupt service
routine (ISR ) can be interrupted by another routine (for example, by another driver's ISR ) that runs at a higher
IRQL. Thus, the driver's ISR does not necessarily retain control of a CPU, uninterrupted, from the beginning of its
execution path to the end.
In Windows drivers, an ISR typically saves hardware state information, queues a deferred procedure call (DPC ),
and then quickly exits. Later, the system dequeues the driver's DPC so that the driver can complete I/O operations
at a lower IRQL (DISPATCH_LEVEL ). For good overall system performance, all routines that run at high IRQLs
must relinquish control of the CPU quickly.
In Windows, all threads have a thread context. This context consists of information that identifies the process that
owns the thread, plus other characteristics such as the thread's access rights.
In general, only a highest-level driver is called in the context of the thread that is requesting the driver's current I/O
operation. An intermediate-level or lowest-level driver can never assume that it is executing in the context of the
thread that requested its current I/O operation.
Consequently, driver routines usually execute in an arbitrary thread context—the context of whatever thread is
current when a standard driver routine is called. For performance reasons (to avoid context switches), very few
drivers set up their own threads.
Multiprocessor-Safe
1/11/2019 • 3 minutes to read • Edit Online

The Microsoft Windows NT-based operating system is designed to run uniformly on uniprocessor and symmetric
multiprocessor (SMP ) platforms, and kernel-mode drivers should be designed to do likewise.
In any Windows multiprocessor platform, the following conditions exist:
All CPUs are identical, and either all or none of the processors must have identical coprocessors.
All CPUs share memory and have uniform access to memory.
In a symmetric platform, every CPU can access memory, take an interrupt, and access I/O control registers.
(By contrast, in an asymmetric multiprocessor machine, one CPU takes all interrupts for a set of subordinate
CPUs.)
To run safely on an SMP platform, an operating system must guarantee that code that executes on one processor
does not simultaneously access and modify data that another processor is accessing and modifying. For example, if
a lowest-level driver's ISR is handling a device interrupt on one processor, it must have exclusive access to device
registers or critical, driver-defined data, in case its device interrupts simultaneously on another processor.
Furthermore, drivers' I/O operations that are serialized in a uniprocessor machine can be overlapped in an SMP
machine. That is, a driver's routine that processes incoming I/O requests can be executing on one processor while
another routine that communicates with the device executes concurrently on another processor. Whether kernel-
mode drivers are executing on a uniprocessor or symmetric multiprocessor machine, they must synchronize access
to any driver-defined data or system-provided resources that are shared among driver routines, and synchronize
access to the physical device, if any.
The Windows NT kernel component exports a synchronization mechanism, called a spin lock, that drivers can use
to protect shared data (or device registers) from simultaneous access by one or more routines that are running
concurrently on a symmetric multiprocessor platform. The kernel enforces two policies regarding the use of spin
locks:
Only one routine can hold a particular spin lock at any given moment. Before accessing shared data, each
routine that must reference the data must first attempt to acquire the data's spin lock. To access the same
data, another routine must acquire the spin lock, but the spin lock cannot be acquired until the current
holder releases it.
The kernel assigns an IRQL value to each spin lock in the system. A kernel-mode routine can acquire a
particular spin lock only when the routine is run at the spin lock's assigned IRQL.
These policies prevent a driver routine that usually runs at a lower IRQL but currently holds a spin lock from being
preempted by a higher-priority driver routine that is trying to acquire the same spin lock. Thus, a deadlock is
avoided.
The IRQL that is assigned to a spin lock is generally that of the highest-IRQL routine that can acquire the spin lock.
For example, a lowest-level driver's ISR frequently shares a state area with the driver's DPC routine. The DPC
routine calls a driver-supplied critical section routine to access the shared area. The spin lock that protects the
shared area has an IRQL equal to the DIRQL at which the device interrupts. As long as the critical-section routine
holds the spin lock and accesses the shared area at DIRQL, the ISR cannot be run in either a uniprocessor or SMP
machine.
The ISR cannot be run in a uniprocessor machine because the device interrupt is masked, as described in
Always Preemptible and Always Interruptible.
In an SMP machine, the ISR cannot acquire the spin lock that protects the shared data while the critical-
section routine holds the spin lock and accesses the shared data at DIRQL.
A set of kernel-mode threads can synchronize access to shared data or resources by waiting for one of the kernel's
dispatcher objects: an event, mutex, semaphore, timer, or another thread. However, most drivers do not set up their
own threads because they have better performance when they avoid thread-context switches. Whenever time-
critical kernel-mode support routines and drivers run at IRQL = DISPATCH_LEVEL or at DIRQL, they must use
the kernel's spin locks to synchronize access to shared data or resources.
For more information, see Spin Locks, Managing Hardware Priorities, and Kernel Dispatcher Objects.
Object-Based
6/25/2019 • 2 minutes to read • Edit Online

The Microsoft Windows NT-based operating system is object-based. Various components in the executive define
one or more object types. Each component exports kernel-mode support routines that manipulate instances of its
object types. No component can directly access another component's objects. To use another component's objects,
a component must call the exported support routines.
This design allows the operating system to be both portable and flexible. For example, it is possible for a future
release of the operating system to contain a recoded kernel component that defines the same object types, but
with entirely different internal structures. If this hypothetical recoded version of the kernel exports a set of support
routines that have the same names and parameters as the existing set, the internal changes would have no effect
on the portability of any other executive component in the existing system.
Likewise, to remain portable and configurable, drivers must communicate with the operating system and with
each other by using only the support routines and other interfaces that are described in the WDK.
Like the operating system, drivers are also object-based. For example:
File objects represent a user-mode application's connection to a device.
Device objects represent each driver's logical, virtual, or physical devices.
Driver objects represent each driver's load image.
The I/O manager defines the structure and interfaces for file objects, device objects, and driver objects.
Like any other executive component, drivers use objects by calling kernel-mode support routines that the I/O
manager and other system components export. Kernel-mode support routines generally have names that identify
the specific object that each routine manipulates and the operation that each routine performs on that object.
These support routine names have the following form:
PrefixOperationObject
where
Prefix Identifies the kernel-mode component that exports the support routine and, usually, the component that
defined the object type. Most prefixes have two letters.
Operation Describes what is done to the object.
Object Identifies the type of object.
For example, the I/O manager's IoCreateDevice routine creates a device object to represent a physical, logical, or
virtual device as the target of I/O requests.
One system component can export routines that call another component's support routines. This can reduce the
number of calls that a driver must make. The I/O manager, in particular, exports certain routines that make it easier
to develop drivers. For example, IoConnectInterruptEx, calls the kernel support routines for interrupt objects.
Object Opacity
Some system-defined objects are opaque: only the defining system component is aware of such an object's
internal structure and can directly access all of the data that an object contains. The system component that defines
an opaque object exports support routines that drivers and other kernel-mode components can call to manipulate
that object. Drivers never access opaque object structures directly.
Note To maintain driver portability, drivers must use the system-supplied support routines to manipulate
system-defined objects. The defining system component can change the internal structure of its object types at any
time.
Packet-Driven I/O with Reusable IRPs
12/5/2018 • 2 minutes to read • Edit Online

The I/O manager, Plug and Play manager, and power manager useI/O request packets (IRPs) to communicate
with kernel-mode drivers, and to allow drivers to communicate with each other.
The I/O manager performs the following steps:
Accepts I/O requests, which usually originate from user-mode applications.
Creates IRPs to represent the I/O requests.
Routes the IRPs to the appropriate drivers.
Tracks the IRPs until they are completed.
Returns the status to the original requester of each I/O operation.
An IRP might be routed to more than one driver. For example, a request to open a file on a disk might go first to a
file system driver, through an intermediate mirror driver, and ultimately to a disk driver and, possibly, to a PnP
hardware bus driver. This set of drivers is known as a driver stack.
Therefore, each IRP has a fixed part, plus one driver-specific I/O stack location for each driver that controls the
device:
In the fixed part (or header), the I/O manager maintains information about the original request, such as the
caller's thread ID and parameters, the address of the device object on which a file is open, and so forth. The
fixed part also contains an I/O status block, in which drivers set information about the status of the
requested I/O operation.
In the highest-level driver's I/O stack location, the I/O manager, Plug and Play manager, or power manager
sets driver-specific parameters, such as the function code of the requested operation and the context that
the corresponding driver uses to determine what it should do. In turn, each driver sets up the I/O stack
location of the next-lower driver in the driver stack.
As each driver processes an IRP, it can access its I/O stack location in the IRP, thereby reusing the IRP at each
stage of the driver's operations. In addition, higher-level drivers can create (or reuse) IRPs to send requests down
to even lower-level drivers.
For a detailed discussion of IRPs, see Handling IRPs.
Supporting Asynchronous I/O
12/5/2018 • 2 minutes to read • Edit Online

The I/O manager provides asynchronous I/O support so that the originator of an I/O request (usually a user-
mode application but sometimes another driver) can continue executing, rather than wait for its I/O request to be
completed. Asynchronous I/O support improves both the overall system throughput and the performance of any
code that makes an I/O request.
With asynchronous I/O support, kernel-mode drivers do not necessarily process I/O requests in the same order in
which they were sent to the I/O manager. The I/O manager, or a higher-level driver, can reorder I/O requests as
they are received. A driver can split a large data transfer request into smaller transfer requests. Moreover, a driver
can overlap I/O request processing, particularly in a symmetric multiprocessor platform, as mentioned in
Multiprocessor-Safe.
Furthermore, a kernel-mode driver's processing of an individual I/O request is not necessarily serialized. That is, a
driver does not necessarily process each IRP to completion before it starts processing the next incoming I/O
request.
When a driver receives an IRP, it responds by carrying out as much IRP -specific processing as it can. If the driver
supports asynchronous IRP processing, it can send an IRP to the next driver, if necessary, and begin processing the
next IRP without waiting for the first one to be completed. The driver can register a "completion routine," which
the I/O manager calls when another driver has finished processing an IRP. Drivers provide a status value in the
IRP's I/O status block, which other drivers can access to determine the status of an I/O request.
Drivers can maintain state information about their current I/O operations in a special part of their device objects,
called a device extension.
For more information, see Handling IRPs and Input/Output Techniques.
Sample Kernel-Mode Drivers
6/25/2019 • 2 minutes to read • Edit Online

The WDK provides various sample kernel-mode drivers. After you have installed the WDK, the src\general
subdirectory contains sample driver code that is applicable to all kernel-mode drivers. The samples are also
maintained online. These samples include the following:
DCHU
Applies the DCHU design principles (Declarative, Componentized, Hardware Support Apps [HSA], and Universal
API compliance). You can use it as a model for your own universal driver package.
PLX9x5x
This sample demonstrates how to write driver for a generic PCI device using Windows Driver Framework.
SimpleMediaSource
This sample demonstrates how to create a custom media source and driver package that can be installed as a
Camera and produce frames.
SystemDma/wdm
This sample demonstrates the usage of V3 System DMA. It shows how a driver could use a system DMA controller
supported by Windows to write data to a hardware location using DMA.
WinHEC 2017 Lab
WinHEC 2017/Optimizing Windows Performance
cancel
Demonstrates the use of cancel-safe IRP queues.
echo
event
Demonstrates techniques that kernel-mode drivers can use to notify applications of hardware events, if the
application requests notification. One technique uses event objects and the other relies on queuing the notification
request until an event occurs.
filehistory
The FileHistory sample is a console application that starts the file history service, if it is stopped, and schedules
regular backups. The application requires, as a command-line parameter, the path name of a storage device to use
as the default backup target.
IOCTL sample
Demonstrates how drivers should support I/O control codes.
obcallback
The ObCallback sample driver demonstrates the use of registered callbacks for process protection. The driver
registers control callbacks which are called at process creation.
pcidrv
This sample demonstrates how to write a KMDF driver for a PCI device. The sample works with the Intel
82557/82558 based PCI Ethernet Adapter (10/100) and Intel compatibles.
perfcounters/kcs
The Kcs sample driver demonstrates the use of the kernel-mode performance library.
registry/regfltr
The RegFltr sample shows how to write a registry filter driver.
toaster
Provides sample code for a set of drivers that conform to the Windows Driver Model (WDM ). This sample also
includes sample installation software.
tracedrv
Shows how to use WPP software tracing.
UMDF Driver Skeleton Sample
This sample demonstrates how to use version 1 of the User-Mode Driver Framework to write a minimal driver.
Firefly KMDF filter driver for a HID device Along with illustrating how to write a filter driver, this sample shows
how to use remote I/O target interfaces to open a HID collection in kernel-mode and send IOCTL requests to set
and get feature reports, as well as how an application can use WMI interfaces to send commands to a filter driver.
Other subdirectories of the \src directory contain sample code for kernel-mode drivers for various types of
hardware.

See also
Microsoft Windows driver samples on GitHub
Surface Team Driver Development Best Practices
8/21/2019 • 10 minutes to read • Edit Online

Introduction
These driver development guidelines were developed over many years by driver developers at Microsoft. Over
time when drivers misbehaved and lessons were learned, those lessons were captured and evolved to be this set of
guidance. These best practices are used by the Microsoft Surface Hardware team to develop and maintain the
device driver code that support the unique Surface hardware experiences.
Like any set of guidelines, there will be legitimate exceptions and alternative approaches that will be equally valid.
Consider incorporating these guidelines into your development standards or using them to start your domain
specific guidelines for your development environment and your unique requirements.

Common mistakes made by driver developers


Handling I/O
1. Accessing buffers retrieved from IOCTLs without validating the length. See Failure to Check the Size of Buffers.
2. Performing blocking I/O in the context of a user thread or random thread context. See Introduction to Kernel
Dispatcher Objects.
3. Sending synchronous I/O to another driver without timeout. See Sending I/O Requests Synchronously.
4. Using neither-io IOCTLs without understanding security implications. See Using Neither Buffered Nor Direct
I/O.
5. Not checking the return status of WdfRequestForwardToIoQueue or not handling failure correctly and resulting
in abandoned WDFREQUESTs.
6. Keeping the WDFREQUEST outside the queue in a non-cancelable state. See Managing I/O Queues,
Completing I/O Requests and Canceling I/O Requests.
7. Trying to manage cancelation using Mark/UnmarkCancelable function instead of using IoQueues. See
Framework Queue Objects.
8. Not knowing the difference between file handle Cleanup and Close operations. See Errors in Handling Cleanup
and Close Operations.
9. Overlooking potential recursions with I/O completion and resubmission from the completion routine.
10. Not being explicit about the power management attributes of WDFQUEUEs. Not documenting the power
management choice clearly. This is the primary cause of Bug Check 0x9F: DRIVER_POWER_STATE_FAILURE in
WDF drivers. When the device is removed, the framework purges IO from the power managed queue and non-
power managed queue in different stages of removal process. Non power managed queues are purged when
the final IRP_MN_REMOVE_DEVICE is received. So if you are holding I/O in an non-power managed queue,
it’s a good practice to explicitly purges the I/O in the context of EvtDeviceSelfManagedIoFlush to avoid
deadlock.
11. Not following the rules of handling IRPs. See Errors in Handling Cleanup and Close Operations.
Synchronization
1. Holding locks for code that doesn't need protection. Do not hold a lock for an entire function when only a small
number of operations needs to be protected.
2. Calling out of drivers with locks held. This is the primary causes of deadlocks.
3. Using interlocked primitives to create a locking scheme instead of using appropriate system provided locking
primitives such as mutex, semaphore and spinlocks. See Introduction to Mutex Objects, Semaphore Objects and
Introduction to Spin Locks.
4. Using a spinlock where some type of passive lock would be more appropriate. See Fast Mutexes and Guarded
Mutexes and Event Objects. For additional perspective on locks review the OSR Article - The State of
Synchronization.
5. Opting into WDF synchronization and execution level model without full understanding of implications. See
Using Framework Locks. Unless your driver is monolithic top-level driver directly interacting with the hardware,
avoid opting into WDF synchronization as it can lead to deadlocks due to recursion.
6. Acquiring KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex in the context of multiple threads without
entering critical region. Doing this can lead to DOS attack because a thread holding one of these locks can be
suspended. See Synchronization Techniques.
7. Allocating KEVENT on thread stack and returning to the caller while the EVENT is still in use. Typically done
when used with IoBuildSyncronousFsdRequest or IoBuildDeviceIoControlRequest. Caller of these calls should
make sure that they don't unwind from the stack until I/O manager has signaled the event when the IRP is
completed.
8. Indefinitely waiting in dispatch routines. In general, any kind of wait in dispatch routine is a bad practice.
9. Inappropriately checking the validity of an object (if blah == NULL ) before deleting it. This typically means the
author doesn't have full understanding of the code that controls the lifetime of the object.
Object Management
1. Not explicitly parenting WDF objects. See Introduction to Framework Objects.
2. Parenting WDF object to WDFDRIVER instead of parenting to an object that provides better lifetime
management and optimizes memory usage. For example, parenting WDFREQUEST to a WDFDEVICE instead
of IOTARGET. See Using General Framework Objects, Framework Object Life Cycle and Summary of
Framework Objects.
3. Not doing rundown protection of shared memory resources accessed across drivers. See
ExInitializeRundownProtection function.
4. Mistakenly queuing the same work item while the previous one is already in the queue or already running. This
is can be a problem if the client makes an assumption that every work item queued is going to get executed. See
Using Framework WorkItems. For more information about queuing WorkItems, see the
DMF_QueuedWorkitem module in the Driver Module Framework (DMF ) project -
https://github.com/Microsoft/DMF.
5. Queuing timer before posting the message the timer is expected to process. See Using Timers.
6. Performing an operation in a workitem that can block or take indefinitely long time to complete.
7. Designing a solution that results in a flood of work items to be queued. It can lead to unresponsive system or
DOS attack if the bad guy can control the action (e.g. pumping I/O in to a driver that queues a new work item
for every I/O ). See Using Framework Work Items.
8. Not ensuing that work item DPC callbacks have run to completion before deleting the object. See Guidelines for
Writing DPC Routines and the WdfDpcCancel function.
9. Creating threads instead of using work items for short duration/non-polling tasks. See System Worker Threads.
10. Not ensuring threads have run to completion before deleting or unload driver. For more information about
thread rundown synchronization, look at the code associated with look at the code associated with DMF_Thread
module in the Driver Module Framework (DMF ) project - https://github.com/Microsoft/DMF.
11. Using a single driver to manage devices that are different but interdependent and using global variables to
share information.
Memory
1. Not marking passive-execution code as PAGEABLE, when possible. Paging driver code can reduce the size of
the driver's code footprint, thus freeing system space for other uses. Be cautious marking code pageable that
raises IRQL >= DISPATCH_LEVEL or could be called at raised IRQL. See When Should Code and Data Be
Pageable and Making Drivers Pageable and Detecting Code That Can Be Pageable.
2. Declaring large structures on the stack, Should use the heap/poolinstead. See Using the KernelStack and
Allocating System-Space Memory.
3. Unnecessarily zeroing WDF Object context. This can indicate a lack of clarity on when memory will be zeroed
out automatically.
General Driver Guidelines
1. Mixing WDM and WDF primitives. Using WDM primitives where WDF primitives can be used. Using WDF
primitives protects you from gotchas, improves debugging and more importantly makes your driver portable to
usermode.
2. Naming FDOs and creating symbolic links when not needed. See Manage driver access control.
3. Copy pasting and using GUIDs and other constant values from sample drivers.
4. Consider the use of the Driver Module Framework (DMF ) open source code in your driver project. DMF is an
extension to WDF that enables extra functionality for a WDF driver developer. See Introducing Driver Module
Framework.
5. Using registry as an inter-process notification mechanism or as a mailbox. For an alternative, see
DMF_NotifyUserWithEvent and DMF_NotifyUserWithRequest modules available in the DMF project -
https://github.com/Microsoft/DMF.
6. Assuming all parts of registry will be available for access during the early boot phase of the system.
7. Taking dependency on the load order of another driver or service. As the load order can be changed outside of
the control of your driver, this can result in a driver that works initially, but later fails in an unpredictable pattern.
8. Recreating driver libraries that are already available, such as WDF provides for PnP described in Supporting
PnP and Power Management in Your Driver or those provided in the bus interface as described in the OSR
article Using Bus Interfaces for Driver to Driver Communication.
PnP/Power
1. Interfacing with another driver in a non-pnp friendly way - not registering for pnp device change notifications.
See Registering for Device Interface Change Notification.
2. Creating ACPI nodes to enumerate devices and creating power dependencies among them instead of using bus
driver or system provided software device creation interfaces to PNP and power dependencies in an elegant
way. See Supporting PnP and Power Management in Function Drivers.
3. Marking the device not-disableable - forcing a reboot on driver update.
4. Hiding the device in the device manager. See Hiding Devices from Device Manager.
5. Making assumptions that driver will be used for only one instance of the device.
6. Making assumptions that driver will never get unloaded. See PnP Driver's Unload Routine.
7. Not handling spurious interface arrival notification. This can happen and drivers are expected to handle this
condition safely.
8. Not implementing a S0 Idle power policy, which is important for devices that are DRIPS constraints or children
thereof. See Supporting Idle Power-Down.
9. Not checking WdfDeviceStopIdle return status leads to power reference leak due to
WdfDeviceStopIdle/ResumeIdle imbalance and eventually 9F bug check.
10. Not knowing that PrepareHardware/ReleaseHardware can be called more than once due to resource
rebalancing. These callbacks should be restricted to initializing hardware resources. See
EVT_WDF_DEVICE_PREPARE_HARDWARE.
11. Using PrepareHardware/ReleaseHardware for allocating software resources. Software resource allocation static
to the device should be done either in AddDevice or in SelfManagedIoInit if the allocation of resources required
interacting with hardware. See EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.
Coding Guidelines
1. Not using safe string and integer functions. See Using Safe String Functions and Using Safe Integer Functions.
2. Not using typedefs for defining constants.
3. Using globals and static variables. Avoid storing per device context in globals. Globals are meant for sharing
information across multiple instances of devices. As an alternative, consider using WDFDRIVER object context
for sharing information across multiple instances of devices.
4. Not using descriptive names for variables.
5. Not being consistent in naming variables - case consistency. Not following the existing style of coding when
making updates to existing code. For example, using different variable names for common structures in
different functions.
6. Not commenting important design choices - power management, locks, state management, use of workitems,
DPCs, timers, global resource usage, resource pre-allocation, complex expressions/conditional statements.
7. Commenting about things that are obvious from the name of the API being called. Making your comment the
English language equivalent of the function name (such as writing the comment “Create the Device Object”
when calling WdfDeviceCreate).
8. Don’t create macros that have a return call. See Functions (C++).
9. No or incomplete Source Code Annotations (SAL ). See SAL 2.0 Annotations for Windows Drivers.
10. Using macros instead of inline functions.
11. Using macros for constants in place of constexpr when using C++
12. Compiling your driver with the C compiler, instead of the C++ compiler to ensure you get strong type checking.
Error Handling
1. Not reporting critical driver errors and gracefully marking the device non-functional.
2. Not returning appropriate NT error status that translates to meaningful WIN32 error status. See Using
NTSTATUS Values.
3. Not using NTSTATUS macros to check the returned status of system functions.
4. Not asserting on state variables or flags where needed.
5. Checking to see if the pointer is valid before accessing it to work around race conditions.
6. ASSERTING on NULL pointers. If you attempt to use a NULL pointer to access memory Windows will bug
check. The parameters of the bug check will provide the necessary information to fix the null pointer. Overtime,
when many unneeded ASSERT statements are added to the code, they consume memory, slow the system and
make checked build binaries unusable. Note that asserts are not included in the free retail build.
7. ASSERTING on object context pointer. The driver framework guarantees that object will always get allocated
with context.
Tracing
1. Not defining WPP custom types and using it in trace calls to get human readable traces messages. See Adding
WPP Software Tracing to a Windows Driver.
2. Not using IFR tracing. See Using Inflight Trace Recorder (IFR ) in KMDF and UMDF 2 Drivers.
3. Calling out function names in WPP trace calls. WPP already tracks function names and line numbers.
4. Not using ETW events to measure performance and other critical user experience impacting events. See Adding
Event Tracing to Kernel-Mode Drivers.
5. Not reporting critical errors in eventlog and gracefully marking the device non-functional.
Verification
1. Not running driver verifier with both standard and advanced settings during development and testing. See
Driver Verifier. In the advanced settings, it is recommended to enable all rules, except those rules that are
related to low resource simulation. It is preferable to run the low resource simulation tests in isolation to make it
easier to debug issues.
2. Not running DevFund test on the driver or the device class the driver is part of with advanced verifier settings
enabled. See How to run the DevFund Tests via the command-line.
3. Not verifying that the driver is HVCI compliant. See Evaluate HVCI driver compatibility.
4. Not running AppVerifier on WUDFhost.exe during development and testing of user mode drivers. See
Application Verifier.
5. Not checking usage of memory using the !wdfpoolusage debugger extension at runtime to make sure WDF
objects are not abandoned. Memory, requests and workitems are common victims of these issues.
6. Not using the !wdfkd debugger extension to inspect the object tree to make sure objects are parented correctly
and checking the attributes of major objects such WDFDRIVER, WDFDEVICE, IO.
Windows Kernel-Mode Object Manager
6/25/2019 • 2 minutes to read • Edit Online

The Windows kernel-mode object manager component manages objects. Files, devices, synchronization
mechanisms, registry keys, and so on, are all represented as objects in kernel mode. Each object has a header
(containing information about the object such as its name, type, and location), and a body (containing data in a
format determined by each type of object).
Windows has more than 25 types of objects. A few of the types are:
Files
Devices
Threads
Processes
Events
Mutexes
Semaphores
Registry keys
Jobs
Sections
Access tokens
Symbolic links
The object manager manages the objects in Windows by performing the following major tasks:
Managing the creation and destruction of objects.
Keeping an object namespace database for tracking object information.
Keeping track of resources assigned to each process.
Tracking access rights for specific objects to provide security.
Managing the lifetime of an object and determining when an object will be automatically destroyed to
recycle resource space.
For more information about objects in Windows, see Device Objects and Device Stacks.
Routines that provide a direct interface to the object manager are usually prefixed with the letters "Ob"; for
example, ObGetObjectSecurity. For a list of object manager routines, see Object Manager Routines.
Note that Windows uses objects as an abstraction for resources. However, Windows is not object-oriented in the
classical C++ meaning of the term. Windows is object-based. For more information on what object-based means
for Windows, see Object-Based.
Windows Kernel-Mode Memory Manager
6/25/2019 • 2 minutes to read • Edit Online

The Windows kernel-mode memory manager component manages physical memory for the operating system.
This memory is primarily in the form of random access memory (RAM ).
The memory manager manages memory by performing the following major tasks:
Managing the allocation and deallocation of memory virtually and dynamically.
Supporting the concepts of memory-mapped files, shared memory, and copy-on-write.
For more detailed information about memory management for drivers, see Memory Management for Windows
Drivers.
Routines that provide a direct interface to the memory manager are usually prefixed with the letters "Mm"; for
example, MmGetPhysicalAddress. For a list of memory manager routines, see Memory Manager Routines.
For lists of memory manager routines sorted by functionality, see Memory Allocation and Buffer Management.
Windows Kernel-Mode Process and Thread Manager
10/7/2019 • 3 minutes to read • Edit Online

A process is a software program that is currently running in Windows. Every process has an ID, a number that
identifies it. A thread is an object that identifies which part of the program is running. Each thread has an ID, a
number that identifies it.
A process may have more than one thread. The purpose of a thread is to allocate processor time. On a machine
with one processor, more than one thread can be allocated, but only one thread can run at a time. Each thread only
runs a short time and then the execution is passed on to the next thread, giving the user the illusion that more than
one thing is happening at once. On a machine with more than one processor, true multi-threading can take place. If
an application has multiple threads, the threads can run simultaneously on different processors.
The Windows kernel-mode process and thread manager handles the execution of all threads in a process. Whether
you have one processor or more, great care must be taken in driver programming to make sure that all threads of
your process are designed so that no matter what order the threads are handled, your driver will operate properly.
If threads from different processes attempt to use the same resource at the same time, problems can occur.
Windows provides several techniques to avoid this problem. The technique of making sure that threads from
different processes do not touch the same resource is called synchronization. For more information about
synchronization, see Synchronization Techniques.
Routines that provide a direct interface to the process and thread manager are usually prefixed with the letters "Ps";
for example, PsCreateSystemThread. For a list of kernel DDIs, see Windows kernel.
This set of guidelines applies to these callback routines:
PCREATE_PROCESS_NOTIFY_ROUTINE
PCREATE_PROCESS_NOTIFY_ROUTINE_EX
PCREATE_THREAD_NOTIFY_ROUTINE
PLOAD_IMAGE_NOTIFY_ROUTINE
Keep notify routines short and simple.
Do not make calls into a user mode service to validate the process, thread, or image.
Do not make registry calls.
Do not make blocking and/or Interprocess Communication (IPC ) function calls.
Do not synchronize with other threads because it can lead to reentrancy deadlocks.
Use System Worker Threads to queue work especially work involving:
Slow API’s or API’s that call into other process.
Any blocking behavior which could interrupt threads in core services.
Be considerate of best practices for kernel mode stack usage. For examples, see How do I keep my driver from
running out of kernel-mode stack? and Key Driver Concepts and Tips.

Subsystem Processes
Starting in Windows 10, the Windows Subsystem for Linux (WSL ) enables a user to run native Linux ELF64
binaries on Windows, alongside other Windows applications. For information about WSL architecture and the
user-mode and kernel-mode components that are required to run the binaries, see the posts on the Windows
Subsystem for Linux blog.
One of the components is a subsystem process that hosts the unmodified user-mode Linux binary, such as
/bin/bash. Subsystem processes do not contain data structures associated with Win32 processes, such as Process
Environment Block (PEB ) and Thread Environment Block (TEB ). For a subsystem process, system calls and user
mode exceptions are dispatched to a paired driver.
Here are the changes to the Process and Thread Manager Routines in order to support subsystem processes:
The WSL type is indicated by the SubsystemInformationTypeWSL value in the
SUBSYSTEM_INFORMATION_TYPE enumeration. Drivers can call NtQueryInformationProcess and
NtQueryInformationThread to determine the underlying subsystem. Those calls return
SubsystemInformationTypeWSL for WSL.
Other kernel mode drivers can get notified about subsystem process creation/deletion by registering their
callback routine through the PsSetCreateProcessNotifyRoutineEx2 call. To get notifications about thread
creation/deletion, drivers can call PsSetCreateThreadNotifyRoutineEx, and specify
PsCreateThreadNotifySubsystems as the type of notification.
The PS_CREATE_NOTIFY_INFO structure has been extended to include a IsSubsystemProcess member that
indicates a subsystem other than Win32. Other members such as FileObject, ImageFileName,
CommandLine indicate additional information about the subsystem process. For information about the
behavior of those members, see SUBSYSTEM_INFORMATION_TYPE.
Windows Kernel-Mode I/O Manager
6/25/2019 • 2 minutes to read • Edit Online

A computer consists of various devices that provide input and output (I/O ) to and from the outside world. Typical
devices are keyboards, mice, audio controllers, video controllers, disk drives, networking ports, and so on. Device
drivers provide the software connection between the devices and the operating system. For this reason, I/O is very
important to the device driver writer.
The Windows kernel-mode I/O manager manages the communication between applications and the interfaces
provided by device drivers. Because devices operate at speeds that may not match the operating system, the
communication between the operating system and device drivers is primarily done through I/O request packets
(IRPs). These packets are similar to network packets or Windows message packets. They are passed from
operating system to specific drivers and from one driver to another.
The Windows I/O system provides a layered driver model called stacks. Typically IRPs go from one driver to
another in the same stack to facilitate communication. For example, a joystick driver would need to communicate
to a USB hub, which in turn would need to communicate to a USB host controller, which would then need to
communicate through a PCI bus to the rest of the computer hardware. The stack consists of joystick driver, USB
hub, USB host controller, and the PCI bus. This communication is coordinated by having each driver in the stack
send and receive IRPs.
It cannot be stressed enough that your driver must send and receive IRPs on a timely basis for the whole stack to
operate efficiently. If your driver is part of a stack and does not properly receive, handle, and pass on the
information, your driver may cause system crashes.
For more information about IRPs, see Handling IRPs.
For more information about driver stacks, see Device Objects and Device Stacks.
For programming techiques related to I/O management, see I/O Manager Programming Techniques.
Routines that provide a direct interface to the I/O manager are usually prefixed with the letters "Io"; for example,
IoCreateDevice. For a list of I/O manager routines, see I/O Manager Routines.
For lists of routines that relate to IRPS, see IRPs.
The I/O manager has two subcomponents: the Plug and Play manager and power manager. They manage the I/O
functionality for the technologies of Plug and Play and power management. For more information about
Plug and Play management, see Windows Kernel-Mode Plug and Play Manager and for more information about
power management, see Windows Kernel-Mode Power Manager.
Windows Kernel-Mode Plug and Play Manager
6/25/2019 • 2 minutes to read • Edit Online

Plug and Play (PnP ) is a combination of hardware technology and software techniques that enables a PC to
recognize when a device is added to the system. With PnP, the system configuration can change with little or no
input from the user. For example, when a USB thumb drive is plugged in, Windows can detect the thumb drive and
add it to the file system automatically. However, to do this, the hardware must follow certain requirements and so
must the driver.
For more information about PnP for drivers, see Plug and Play.
The PnP manager is actually a subsystem of the I/O manager. For more information about the I/O manager, see
Windows Kernel-Mode I/O Manager.
For lists of PnP routines, see Plug and Play Routines.
Note that there are no routines that provide a direct interface to the PnP manager; that is, there are no "Pp"
routines.
The Windows Driver Frameworks (WDF ) provide a set of libraries to make PnP management much easier. For
more information about WDF, see Kernel-Mode Driver Framework Overview.
Windows Kernel-Mode Power Manager
6/25/2019 • 2 minutes to read • Edit Online

Windows uses power management technology to reduce power consumption for PCs in general and for battery-
powered laptops in particular. For example, a Windows computer can be put in a sleep or hibernation state. A
complex power management system for computer devices has evolved so that when the computer begins to shut
down or go to lower power consumption, the attached devices can also be powered down in a proper manner so
that no data is lost. But these devices need a warning that the power status in changing and they may also need to
be part of a communications loop that tells the controlling device to wait until they can shut down properly.
The Windows kernel-mode power manager manages the orderly change in power status for all devices that
support power state changes. This is often done through a complex stack of devices controlling other devices. Each
controlling device is called a node and must have a driver that can handle the communication of power state
changes up and down through a device stack.
If you are writing a driver that can be affected by power-state changes, you must be able to process the following
types of information in your driver code:
System activity level.
System battery level.
Current requests to shut down, sleep, or hibernate.
User actions such as pressing a power button.
Control panel settings, such as automatically shutting down at 10 percent battery power.
The power manager handles these requests using IRPs. For more information about IRPs, see Handling IRPs.
The power manager works in combination with policy management to handle power management and coordinate
power events, and then generates power management IRPs. The power manager collects requests to change the
power state, decides which order the devices must have their power state changed, and then send the appropriate
IRPs to tell the appropriate drivers to make the changes (which in turn may tell subdevices to make the change as
well). The policy manager monitors activity in the system and integrates user status, application status, and device
driver status into power policy.
For more detailed information about power management, see Power Management for Windows Drivers.
The power manager is considered a subcomponent of the I/O manager. For more information, see Windows I/O
Manager.
Routines that provide a direct interface to the power manager are usually prefixed with "Po"; for example,
PoSetPowerState. For a list of power manager routines, see Power Manager Routines.
The Windows Driver Frameworks (WDF ) provides a set of libraries to make power management much easier. For
more information about WDF, see Kernel-Mode Driver Framework Overview.
Windows Kernel-Mode Configuration Manager
6/25/2019 • 2 minutes to read • Edit Online

In the earlier days of Microsoft Windows, applications and the operating system stored configuration values in
"INI" (initialization) files. This provided a simple way to store state values that could be preserved from one
Windows session to the next. However, as the Windows environment became more complex, a new system of
storing persistent information about the operating system and applications was needed. The Windows Registry
was created to store data about hardware and software.
The Windows kernel-mode configuration manager manages the registry. If your driver needs to know about
changes in the registry, it can use the routines of the configuration manager to do so by registering callbacks on
specific registry data. Then, when the data in the registry changes, the callback is triggered and you can run code to
process the callback information in your driver.
Routines that provide a direct interface to the configuration manager are prefixed with the letters "Cm"; for
example, CmRegisterCallback. For a list of configuration manager routines, see Configuration Manager Routines.
In addition to directly calling the configuration manager, there are other ways you will want to work with the
registry in your driver. For more information about using the registry in a driver, see Using the Registry in a Driver
and Registry Keys for Drivers.
Windows Kernel-Mode Kernel Transaction Manager
12/21/2018 • 2 minutes to read • Edit Online

When you are dealing with multiple reads and writes on one or more data stores, and the operations must all
atomically succeed or fail to preserve the integrity of the data, you might want to group the operations together as
a single transaction. If all of the operations within the transaction succeed, the transaction can be committed so that
all the changes persist as an atomic unit. If a failure occurs, the transaction can be rolled back so that the data
stores are restored to their original state.
The kernel transaction manager (KTM ) is the Windows kernel-mode component that implements transaction
processing in kernel mode. KTM allows kernel mode components, such as drivers, to perform transactions. In
addition, KTM is the platform on which user-mode Transactional NTFS (TxF ) is based.
For information about how to use KTM in kernel-mode components, see Kernel Transaction Manager.
Windows Kernel-Mode Security Reference Monitor
6/25/2019 • 2 minutes to read • Edit Online

An increasingly important aspect of operating systems is security. Before an action can take place, the operating
system must be sure that the action is not a violation of system policy. For example, a device may or may not be
accessible to all requests. When creating a driver, you may want to allow some requests to succeed or fail,
depending on the permission of the entity making the request.
Windows uses an access control list (ACL ) to determine which objects have what security. The Windows kernel-
mode security reference monitor provides routines for your driver to work with access control. For more
information about the ACL, see Access Control List.
Routines that provide a direct interface to the security reference monitor are prefixed with the letters "Se"; for
example, SeAccessCheck. For a list of security reference monitor routines, see Security Reference Monitor
Routines.
Windows Kernel-Mode Kernel Library
6/25/2019 • 2 minutes to read • Edit Online

The kernel of an operating system implements the core functionality that everything else in the operating system
depends upon. The Microsoft Windows kernel provides basic low -level operations such as scheduling threads or
routing hardware interrupts. It is the heart of the operating system and all tasks it performs must be fast and
simple.
Routines that provide a direct interface to the kernel library are usually prefixed with "Ke", for example,
KeGetCurrentThread. For a list of kernel library routines, see Kernel Library Support Routines.
Note The term microkernel does not apply to the current kernel used in the Windows operating system.
Windows Kernel-Mode Executive Support Library
6/25/2019 • 2 minutes to read • Edit Online

The Windows operating system uses the term executive layer to refer to kernel-mode components that provide a
variety of services to device drivers, including:
Object management
Memory management
Process and thread management
Input/output management
Configuration management
Each of the above managers provides direct interfaces to their individual technologies, as do several libraries.
However, routines that are grouped together as a generic interface to the Executive Library are usually prefixed
with "Ex", for example, ExGetCurrentResourceThread. For a list of executive library routines, see Executive
Library Support Routines.
Note that the executive layer components are part of Ntoskrnl.exe, but that drivers and the HAL are not part of the
executive layer.
Windows Kernel-Mode Run-Time Library
6/25/2019 • 2 minutes to read • Edit Online

Windows provides a set of common utility routines needed by various kernel-mode components. For example,
RtlCheckRegistryKey is used to see if a given key is in the registry.
Most of the run-time library (RTL ) routines are prefixed with the letters "Rtl"; for a list of the run-time library
routines for the kernel, see Run-Time Library (RTL ) Routines.
There is also a different kernel-mode library specifically designed for safe string handling. For more information
about the safe string library, see Windows Kernel-Mode Safe String Library. Note that safe string library routines
are also usually prefixed by "Rtl" but are not part of the run-time library (RTL ).
Windows Kernel-Mode Safe String Library
12/5/2018 • 2 minutes to read • Edit Online

One of the major problems in software security is related to the vulnerability of working with strings. To provide
greater security, Windows provides a safe string library.
Safe string library routines are prefixed with the letters "Rtl"; for a list of all safe string library routines for the
kernel, see Safe String Functions for Unicode and ANSI Characters and Safe String Functions for
UNICODE_STRING Structures.
For more information about using safe strings, see Using Safe String Functions.
Note that there is also a separate run-time library for general C programming in the kernel that has string
functionality as well. For more information about the run-time library (RTL ), see Windows Kernel-Mode Run-Time
Library. Note that even though both libraries are prefixed with "Rtl" they are not the same library.
Windows Kernel-Mode DMA Library
6/25/2019 • 2 minutes to read • Edit Online

To enhance performance, a device may need direct access to memory in a way that bypasses the central processing
unit (CPU ). This technology is called direct memory access (DMA). Windows provides a DMA library for device
driver developers.
For more information about DMA for drivers, see DMA.
For a listing of DMA routines, see Direct Memory Access (DMA) Library Routines.
Note that DMA is a technology for communicating directly between device and memory and is not the same as
Device Memory Access, which is a set of macros provided to read and write to I/O ports and CPU registers.
Windows Kernel-Mode HAL Library
6/25/2019 • 2 minutes to read • Edit Online

Windows runs on many different configurations of the personal computer. Each configuration requires a layer of
software that interacts between the hardware and the rest of the operating system. Because this layer abstracts
(hides) the low -level hardware details from drivers and the operating system, it is called the hardware abstraction
layer (HAL ).
Developers are not encouraged to write their own HAL. If you need hardware access, the HAL library provides
routines that can be used for that purpose. Routines that interface with the HAL directly are prefixed with the
letters "Hal"; for a list of HAL routines, see Hardware Abstraction Layer (HAL ) Library Routines.
Windows Kernel-Mode CLFS Library
6/25/2019 • 2 minutes to read • Edit Online

Windows provides a transactional logging system for system files. This system is called the Common Log File
System (CLFS ). For more information about CLFS, see Common Log File System.
Routines that provide a direct interface for CLFS are prefixed with the letters "Clfs"; for a list of CLFS library
routines, see Common Log File System (CLFS ) Library Routines. CLFS also provides a list of routines that you can
implement to manage a CLFS; for more information on CLFS management, see CLFS Management Library
Routines.
CLFS is a technology that is related to transacted file systems; for more information about transactions, see
Windows Kernel-Mode Transaction Manager.
Windows Kernel-Mode WMI Library
6/25/2019 • 2 minutes to read • Edit Online

Windows provides a general mechanism for managing components. This system is called Windows Management
Instrumentation (WMI). To satisify Windows Driver Model (WDM ) requirements, you should implement WMI for
your driver so that your driver can be managed by the system.
For more information on WMI, see Windows Management Instrumentation.
Routines that provide a direct interface to the WMI library are prefixed with the letters "Wmi"; for a list of WMI
routines, see Windows Management Instrumentation (WMI) Library Routines.
For a list of WMI callbacks, see WMI Library Callback Routines.
Communication with WMI is done with IRPs. For a list of routines that your driver can use to receive IRPs, see
WMI IRP Processing Routines. For a list of routines that your driver can use to send WMI IRPs, see WMI IRP
Sending Routines. For a list of IRPs that are used with WMI, see WMI Minor IRPs.
Writing WDM Drivers
6/25/2019 • 2 minutes to read • Edit Online

This section discusses the Microsoft Windows Driver Model (WDM ) architecture. This architecture started in
Windows 2000 as an enhancement to previous Windows NT device drivers.
Note Drivers for versions of Windows NT-based operating systems before Windows 2000 are not supported, and
you should update these drivers. The WDM architecture does not support drivers for non-Windows NT-based
operating systems (such as Windows 98), and you should rewrite such drivers.
This section is divided into three parts:
Windows Driver Model describes the Windows Driver Model (WDM ), including types of WDM drivers,
device configuration, and WDM versioning.
Device Objects and Device Stacks describes device objects and device stacks. The section includes
information about physical device objects (PDOs), functional device objects (FDOs), and filter device objects
(filter DOs). Drivers are often built from a set of device objects that work together. This set of device objects
is called a stack. Stacks can help you understand the flow of information to and from a driver and how
different parts of the driver communicate internally.
Kernel-Mode Driver Components describes which routines you must implement to have a functional driver
and which routines are optional.
A device driver is a set of software code that must integrate into the operating system. To complete this
integration, you must write a set of handler routines in your driver that process calls from the operating
system. These routines can be simple function calls, but many of them implement the processing of I/O
request packets (IRPs), which facilitate communication between drivers and the operating system.
Note WDM drivers can also use the Windows Driver Frameworks (WDF ) library to make some parts of a device
driver easier to write. Specifically, kernel-mode drivers can use the Kernel-Mode Driver Framework (KMDF ), which
is part of WDF. For more information about KMDF for kernel-mode drivers, see Kernel-Mode Driver Framework
Overview. Note that KMDF does not replace WDM. You must still understand many parts of WDM to write a
KMDF driver.
Introduction to WDM
6/25/2019 • 2 minutes to read • Edit Online

NOTE
This section contains guidance on WDM drivers, which is no longer the recommended driver model. For guidance on
choosing a driver model, see Choosing a driver model.

To allow driver developers to write device drivers that are source-code compatible across all Microsoft Windows
operating systems, the Windows Driver Model (WDM ) was introduced. Kernel-mode drivers that follow WDM
rules are called WDM drivers.
All WDM drivers must do the following:
Include Wdm.h, not Ntddk.h. (Note that Wdm.h is a subset of Ntddk.h.)
Be designed as a bus driver, a function driver, or a filter driver, as described inTypes of WDM Drivers.
Create device objects as described in WDM Device Objects and Device Stacks.
Support Plug and Play (PnP ).
Support power management.
Support Windows Management Instrumentation (WMI).
Should You Write a WDM Driver?
If you are writing a new driver, consider using the Kernel-Mode Driver Framework (KMDF ). KMDF provides
interfaces that are simpler to use than WDM interfaces.
Do not write a WDM driver if the driver will be inserted into a stack of non-WDM drivers. Please read the
documentation for device type-specific Microsoft-supplied drivers to determine how new drivers must interface
with Microsoft-supplied drivers. For more device type-specific information, see Device and Driver Technologies.)
Types of WDM Drivers
10/7/2019 • 2 minutes to read • Edit Online

There are three kinds of WDM drivers: bus drivers, function drivers, and filter drivers.
A bus driver drives an individual I/O bus device and provides per-slot functionality that is device-independent.
Bus drivers also detect and report child devices that are connected to the bus.
A function driver drives an individual device.
A filter driver filters I/O requests for a device, a class of devices, or a bus.
In this context, a bus is any device to which other physical, logical, or virtual devices are attached; a bus includes
traditional buses such as SCSI and PCI, as well as parallel ports, serial ports, and i8042 ports.
It is important for driver developers to understand the different kinds of WDM drivers and to know which kind of
driver they are writing. For example, whether a driver handles each Plug and Play IRP and how to handle such
IRPs depends on what kind of driver is being written (bus driver, function driver, or filter driver).
The following figure shows the relationship between the bus driver, function driver, and filter drivers for a device.

Each device typically has a bus driver for the parent I/O bus, a function driver for the device, and zero or more filter
drivers for the device. A driver design that requires many filter drivers does not yield optimal performance.
The drivers in the previous figure are the following:
1. A bus driver services a bus controller, adapter, or bridge. Bus drivers are required drivers; there is one bus
driver for each type of bus on a machine. Microsoft provides bus drivers for most common buses. IHVs and
OEMs can provide other bus drivers.
2. A bus filter driver typically adds value to a bus and is supplied by Microsoft or a system OEM. There can be
any number of bus filter drivers for a bus.
3. Lower-level filter drivers typically modify the behavior of device hardware. They are optional and are
typically supplied by IHVs. There can be any number of lower-level filter drivers for a device.
4. A function driver is the main driver for a device. A function driver is typically written by the device vendor
and is required (unless the device is being used in raw mode).
5. Upper-level filter drivers typically provide added-value features for a device. They are optional and are
typically provided by IHVs.
The following topics describe the three general types of WDM drivers—bus drivers, function drivers, filter drivers
—in detail. Also included is an example of WDM driver layering that uses sample USB drivers.

In this section
Bus Drivers
Function Drivers
Filter Drivers
WDM Driver Layers: An Example
Bus Drivers
1/11/2019 • 2 minutes to read • Edit Online

A bus driver services a bus controller, adapter, or bridge (see the Possible Driver Layers figure). Microsoft provides
bus drivers for most common buses, such as PCI, PnpISA, SCSI, and USB. Other bus drivers can be provided by
IHVs or OEMs. Bus drivers are required drivers; there is one bus driver for each type of bus on a machine. A bus
driver can service more than one bus if there is more than one bus of the same type on the machine.
The primary responsibilities of a bus driver are to:
Enumerate the devices on its bus.
Respond to Plug and Play IRPs and power management IRPs.
Multiplex access to the bus (for some buses).
Generically administer the devices on its bus.
Bus drivers are essentially function drivers that also enumerate children.
During enumeration, a bus driver identifies the devices on its bus and creates device objects for them. (For
information about device objects, see Device Objects and Device Stacks.) The method a bus driver uses to identify
connected devices depends on the particular bus.
A bus driver performs certain operations on behalf of the devices on its bus, including accessing device registers
to physically change the power state of a device. For example, when the device goes to sleep, the bus driver sets
device registers to put the device in the proper device power state.
Note, however, that a bus driver does not handle read and write requests for the child devices that are connect to
its bus. Read and write requests to a child device are handled by the child device's function driver does the parent
bus driver handle reads and writes for the device.
Because a bus driver acts as the function driver for its controller, adapter, or bridge, it also manages device power
policy for these components.
A bus driver can be implemented as a driver/minidriver pair, the way a SCSI port/miniport driver pair drives a
SCSI host bus adapter (HBA). In such driver pairs, the minidriver is linked to the second driver, which is a DLL.
Function Drivers
1/11/2019 • 2 minutes to read • Edit Online

A function driver is the main driver for a device (see the Possible Driver Layers). The PnP manager loads at most
one function driver for a device. A function driver can service one or more devices.
A function driver provides the operational interface for its device. Typically the function driver handles reads and
writes to the device and manages device power policy.
The function driver for a device can be implemented as a driver/minidriver pair, such as a port/miniport driver
pair or a class/miniclass driver pair. In such driver pairs, the minidriver is linked to the second driver, which is a
DLL.
If a device is being driven in raw mode, it has no function driver and no upper or lower-level filter drivers. All raw -
mode I/O is done by the bus driver and optional bus filter drivers.
Filter Drivers
10/7/2019 • 2 minutes to read • Edit Online

Filter drivers are optional drivers that add value to or modify the behavior of a device. A filter driver can service
one or more devices.
Bus Filter Drivers
Bus filter drivers typically add value to a bus and are supplied by Microsoft or a system OEM (see the Possible
Driver Layers figure). Bus filter drivers are optional. There can be any number of bus filter drivers for a bus.
A bus filter driver could, for example, implement proprietary enhancements to standard bus hardware.
For devices described by an ACPI BIOS, the power manager inserts a Microsoft-supplied ACPI filter (bus filter
driver) above the bus driver for each such device. The ACPI filter carries out device power policy and powers on
and off devices. The ACPI filter is transparent to other drivers and is not present on non-ACPI machines.
Lower-Level Filter Drivers
Lower-level filter drivers typically modify the behavior of device hardware (see the Possible Driver Layers figure).
They are typically supplied by IHVs and are optional. There can be any number of lower-level filter drivers for a
device.
A lower-level device filter driver monitors and/or modifies I/O requests to a particular device. Typically, such
filters redefine hardware behavior to match expected specifications.
A lower-level class filter driver monitors and/or modifies I/O requests for a class of devices. For example, a lower-
level class filter driver for mouse devices could provide acceleration, performing a nonlinear conversion of mouse
movement data.
Upper-Level Filter Drivers
Upper-level filter drivers typically provide added-value features for a device (see the Possible Driver Layers figure).
Such drivers are usually provided by IHVs and are optional. There can be any number of upper-level filter drivers
for a device.
An upper-level device filter driver adds value for a particular device. For example, an upper-level device filter
driver for a keyboard could enforce additional security checks.
An upper-level class filter driver adds value for all devices of a particular class.
WDM Driver Layers: An Example
12/5/2018 • 2 minutes to read • Edit Online

This section describes a possible set of WDM drivers for USB hardware to illustrate WDM driver layers.
The following figure shows a sample PnP hardware configuration for a USB joystick.

In this figure, the USB joystick plugs into a port on a USB hub. The USB hub in this example resides on the USB
Host Controller board and is plugged into the single port on the USB host controller board. The USB host
controller plugs into a PCI bus. From a PnP perspective, the USB hub, the USB host controller, and the PCI bus are
all bus devices because they each provide ports. The joystick is not a bus device.
The following figure shows a sample set of drivers that might be loaded for the USB joystick hardware in the
previous figure.

Starting at the bottom of the previous figure, the drivers in the sample stack include:
A PCI driver that drives the PCI bus. This is a PnP bus driver. The PCI bus driver is provided with the system
by Microsoft.
The bus driver for the USB host controller is implemented as a class/miniclass driver pair. The USB host
controller class and miniclass drivers are provided with the system by Microsoft.
The USB hub bus driver that drives the USB hub. The USB hub driver is provided with the system by
Microsoft.
Three drivers for the joystick device; one of them is a class/miniclass pair.
The function driver, the main driver for the joystick device, is the HID class driver/HID USB miniclass driver
pair. (HID represents "Human Interface Device".) The HID USB miniclass driver supports the USB -specific
semantics of HID devices, relying on the HID class driver DLL for general HID support.
A function driver can be specific to a particular device, or, as in the case of HID, a function driver can service
a group of devices. In this example, the HID class driver/HID USB miniclass driver pair services any HID -
compliant device in the system on a USB bus. A HID class driver/HID 1394 miniclass driver pair would
service any HID -compliant device on a 1394 bus.
A function driver can be written by the device vendor or by Microsoft. In this example, the function driver
(the HID class/HID USB miniclass driver pair) is written by Microsoft.
There are two filter drivers for the joystick device in this example: an upper-level class filter that adds a
macro button feature and a lower-level device filter that enables the joystick to emulate a mouse device.
The upper-level filter is written by someone who needs to filter the joystick I/O and the lower-level filter
driver is written by the joystick vendor.
The kernel-mode and user-mode HID clients and the application are not drivers but are shown for
completeness.
Device Configurations and Layered Drivers
12/5/2018 • 2 minutes to read • Edit Online

For the most common kinds of devices, the Windows Driver Kit (WDK) supplies a sample set of fully functional
system drivers. Individual sample drivers can be used as models when developing new drivers for similar kinds of
devices. However, the system's drivers had an additional design requirement: to make it easy to develop new
device drivers. Consequently, many of the system's drivers have a layered architecture so that certain drivers can
be reused to support new drivers for similar devices.
In most cases, the WDK-supplied reusable drivers are WDM drivers that support PnP and handle hardware-
independent operations for a system-supplied device-specific lowest-level (PnP bus) driver. In some cases, such as
the parallel port and SCSI port drivers, these reusable drivers provide support for higher-level, device-type-specific
class drivers. Note that none of the system's reusable drivers precludes the development of new intermediate
drivers to be added to a chain of existing drivers.
Where a new (or replacement) driver fits in the chain of drivers for a device depends partly on the hardware
configuration of devices in a given Windows platform, and partly on how much support a new driver can get from
existing system drivers.

In this section
Sample Device and Driver Configuration
Points to Consider When Adding Drivers
Sample Device and Driver Configuration
1/11/2019 • 2 minutes to read • Edit Online

This section illustrates the relationship between the hardware and driver configurations, using the keyboard and
mouse devices as an example. Configurations differ for other devices. For complete information about any device
configuration, see the device-specific documentation in the Windows Driver Kit (WDK).
The following figure shows two possible hardware configurations for the keyboard and mouse devices:
Each connected directly somewhere on the system bus
Both connected through a keyboard and auxiliary device controller

The following figure illustrates the corresponding layered drivers for I/O operations on the devices shown in the
previous figure.

Note that drivers of keyboard and mouse devices, whatever the hardware configuration, can use the system's
keyboard class and mouse class drivers to handle hardware-independent operations. These are called class drivers
because each supplies system-required but hardware-independent support for a particular class of device.
A corresponding port driver implements the device-specific support to carry out required I/O operations on each
physical device. The system's (i8042) keyboard and auxiliary device port driver for x86-based platforms manages
device-specific operations for both mouse and keyboard. In a hardware configuration where each device is
separately connected, as shown in the figure illustrating the keyboard and mouse hardware configurations, each
system class driver can be layered over separate device-specific port drivers, or a single driver for each device
could be implemented as a separate, monolithic (lowest-level) driver.
A new intermediate driver, such as a PnP filter driver, could be added to the configuration in the figure illustrating
the keyboard and mouse driver layers. For example, a filter driver added above the keyboard class driver might
filter keyboard input in a platform-specific manner before passing it through the I/O services to the subsystem that
requested it. Such a filter driver must recognize the same IRPs and IOCTLs as the keyboard class driver.
Points to Consider When Adding Drivers
12/5/2018 • 2 minutes to read • Edit Online

Keep the following points in mind when designing a kernel-mode driver:


The system-supplied SCSI and video port drivers cannot be replaced.
A replacement lowest-level driver must implement the same functionality as the driver it replaces. For
example, a replacement keyboard or mouse port driver must use the system-defined interface between
itself and a system-supplied class driver that it reuses, and vice versa.
A new intermediate driver, inserted between any pair of system-supplied drivers, must interoperate with
those drivers so that the functionality of the upper and lower drivers is not reduced.
Determining the WDM Version
6/25/2019 • 2 minutes to read • Edit Online

A cross-system WDM driver should use the IoIsWdmVersionAvailable routine to determine which version of
WDM is supported by the system on which it is running. The reference page for IoIsWdmVersionAvailable
provides a list of WDM version numbers.
For information about differences in WDM that drivers should handle, see Differences in WDM Versions.
Differences in WDM Versions
6/25/2019 • 2 minutes to read • Edit Online

The simplest way to ensure cross-system compatibility is to write a driver that uses only features that are
supported by the lowest-numbered version of WDM. However, this is not always possible. Sometimes, drivers
require additional code to take advantage of the features that are available in later versions of WDM, or to
compensate for differences between Windows operating systems.
WDM Differences in Driver Support Routines
The Windows Driver Kit (WDK) reference page for each driver support routine indicates if the routine is restricted
to specific versions of WDM, or if its behavior is different on different operating system versions. Before using any
driver support routine in a cross-system driver, be sure to understand any version-specific restrictions or
behaviors.
WDM Differences in Plug and Play
The following Plug and Play I/O request packet (IRP ) is supported only in Windows 2000 and later versions of the
NT-based operating system (WDM version 1.10 and later):
IRP_MN_SURPRISE_REMOVAL
In addition, the following IRPs work differently on Windows 98/Me from how they work on the NT-based
operating system:
IRP_MN_STOP_DEVICE and IRP_MN_REMOVE_DEVICE
IRP_MN_QUERY_REMOVE_DEVICE
WDM Differences in Power Management
The following power management functions and I/O requests differ in operation between the Windows 98/Me
operating system and the NT-based operating system:
PoSetPowerState
PoRequestPowerIrp
PoRegisterDeviceForIdleDetection
IRP_MN_QUERY_POWER
IRP_MN_SET_POWER
When completing power IRPs, drivers on Windows 98/Me must complete power IRPs at IRQL =
PASSIVE_LEVEL, while drivers on the NT-based operating system can complete such IRPs at IRQL =
PASSIVE_LEVEL or IRQL = DISPATCH_LEVEL.
The DO_POWER_PAGABLE flag in the DEVICE_OBJECT structure is used differently on the Windows 98/Me
operating system than on the NT-based operating system.
WDM Differences in Kernel-Mode Driver Operation
Kernel-mode WDM drivers for Windows 98/Me must follow certain guidelines for using floating-point operations,
MMX, 3DNOW!, or Intel's SSE extensions. For more information, see Using Floating Point or MMX in a WDM
Driver.
Windows 98/Me provides a fixed number of worker threads that might not be adequate for some drivers.
Kernel-Mode Driver Components
12/5/2018 • 2 minutes to read • Edit Online

This section introduces the standard routines contained in kernel-mode drivers. Some of these standard driver
routines are required; others are optional. The section also introduces driver objects, which contain pointers to each
driver's standard routines.
Some drivers interact with a system-supplied port driver or class driver that defines much of the driver's required
functionality. For example, a SCSI miniport driver primarily interacts with the SCSI port driver. For such drivers,
see the class-specific documentation for details of required and optional driver support.
This section includes:
Introduction to Standard Driver Routines
Standard Driver Routine Requirements
Introduction to Driver Objects
Writing a DriverEntry Routine
Writing a Reinitialize Routine
Writing an AddDevice Routine
Writing Dispatch Routines
Writing an Unload Routine
Introduction to Standard Driver Routines
6/25/2019 • 2 minutes to read • Edit Online

Each kernel-mode driver is constructed around a set of system-defined, standard driver routines. Kernel-mode
drivers process I/O request packets (IRPs) within these standard routines by calling system-supplied driver
support routines.
All drivers, regardless of their level in a chain of attached drivers, must have a basic set of standard routines in
order to process IRPs. Whether a driver must implement additional standard routines depends on whether the
driver controls a physical device or is layered over a physical device driver, as well as on the nature of the
underlying physical device. Lowest-level drivers that control physical devices have more required routines than
higher-level drivers, which typically pass IRPs to a lower driver for processing.
Standard driver routines can be divided into two groups: those that each kernel-mode driver must have, and those
that are optional, depending on the driver type and location in the device stack.
This section describes required standard routines. Other sections describe the optional routines.
Following are two tables. The first table lists required standard routines. The second lists most of the optional
routines.

REQUIRED STANDARD DRIVER ROUTINES PURPOSE WHERE DESCRIBED

DriverEntry Initializes the driver and its driver Writing a DriverEntry Routine
object.

AddDevice Initializes devices and creates device Writing an AddDevice Routine


objects.

Dispatch Routines Receive and process IRPs. Writing Dispatch Routines

Unload Release system resources acquired Writing an Unload Routine


by the driver.

OPTIONAL STANDARD DRIVER ROUTINES PURPOSE WHERE DESCRIBED

Reinitialize Completes driver initialization if Writing a Reinitialize Routine


DriverEntry cannot.

StartIo Starts an I/O operation on a Writing a StartIo Routine


physical device.

Interrupt Service Routine Saves the state of a device when it Writing an ISR
interrupts.
OPTIONAL STANDARD DRIVER ROUTINES PURPOSE WHERE DESCRIBED

Deferred Procedure Calls Completes the processing of a DPC Objects and DPCs
device interrupt after an ISR saves
the device state.

SynchCritSection Synchronizes access to driver data. Using Critical Sections

AdapterControl Initiates DMA operations. Adapter Objects and DMA

IoCompletion Completes a driver's processing of Completing IRPs


an IRP.

Cancel Cancels a driver's processing of an Canceling IRPs


IRP.

CustomTimerDpc, IoTimer Timing and synchronizing events. Synchronization Techniques

The current IRP and target device object are input parameters to many standard routines. Every driver processes
each IRP in stages through its set of standard routines.
By convention, the system-supplied drivers prepend an identifying, driver-specific or device-specific prefix to the
name of every standard routine except DriverEntry. As an example, this documentation uses "DD", as shown in
the driver object illustration. Following this convention makes it easier to debug and maintain drivers.
Standard Driver Routine Requirements
6/25/2019 • 2 minutes to read • Edit Online

Keep the following points in mind when designing a kernel-mode driver:


Each driver must have a DriverEntry routine, which initializes driver-wide data structures and resources.
The I/O manager calls the DriverEntry routine when it loads the driver.
Every driver must have at least one dispatch routine that receives and processes I/O request packets (IRPs).
Each driver must place a dispatch routine's entry point in its DRIVER_OBJECT structure, for each IRP
major function code that the driver can receive. A driver can have a separate dispatch routine for each IRP
major function code, or it can have one or more dispatch routines that handle several function codes.
Every WDM driver must have an Unload routine. The driver must place the Unload routine's entry point in
the driver's driver object. The responsibilities of a PnP driver's Unload routine are minimal, but a non-PnP
driver's unload routine is responsible for releasing any system resources that the driver is using.
Every WDM driver must have an AddDevice of the driver object. An AddDevice routine is responsible for
creating and initializing device objects for each PnP device the driver controls.
A driver can have a StartIo routine, which the I/O manager calls to start I/O operations for IRPs the driver
has queued to a system-supplied IRP queue. Any driver that does not have a StartIo routine must either set
up and manage internal queues for the IRPs it receives, or it must complete every IRP within its dispatch
routines. Higher-level drivers might not have a StartIo routine, if they simply pass IRPs to lower-level
drivers directly from their dispatch routines.
Certain miniport drivers are exceptions to the preceding requirements. See the device-type-specific
documentation in the Windows Driver Kit (WDK) for information about the requirements for miniport
drivers.
Whether a driver has any other kind of standard routine depends on its functionality and on how that driver
fits into the system (for example, whether it interoperates with system-supplied drivers). See the device-type
specific documentation in the WDK for details.
Introduction to Driver Objects
6/25/2019 • 2 minutes to read • Edit Online

The I/O manager creates a driver object for each driver that has been installed and loaded. Driver objects are
defined using DRIVER_OBJECT structures.
When the I/O manager calls a driver's DriverEntry routine, it supplies the address of the driver's driver object. The
driver object contains storage for entry points to many of a driver's standard routines. The driver is responsible for
filling in these entry points.

The following figure illustrates a driver object, with the set of system-defined standard routines that lowest-level
and higher-level drivers can or must have.
Each standard routine with an asterisk beside its name receives an I/O request packet (IRP ) as input. Each of these
standard routines also receives a pointer to the target device object for the I/O request.

The I/O manager defines the driver object type and uses driver objects to register and track information about the
loaded images of drivers. Note that the dispatch entry points (DDDispatchXxx through DDDispatchYyy) in the
driver object correspond to the major function codes (IRP_MJ_XXX) that are passed in the I/O stack locations of
IRPs.
The I/O manager routes each IRP first to a driver-supplied dispatch routine. A lowest-level driver's dispatch
routine usually calls an I/O support routine (IoStartPacket) to queue (or pass on) each IRP that has valid
arguments to the driver's StartIo routine. The StartIo routine starts the requested I/O operation on a particular
device. Higher-level drivers usually do not have StartIo routines, but they can.
Driver Entry Points in Driver Objects
6/25/2019 • 2 minutes to read • Edit Online

A kernel-mode driver must specify the following entry points in its driver object:
At least one dispatch routine's entry point, in order to get IRPs requesting PnP, power, and I/O operations.
The entry point of its AddDevice routine, at DriverObject -> DriverExtension -> AddDevice.
The entry point of its StartIo routine, if it manages its own queue of IRPs.
If the driver can be loaded and/or replaced dynamically, an Unload entry point in order to free any system
resources, such as system objects or memory, that the driver has allocated. (Drivers that cannot be replaced
while the system is running, such as a keyboard driver, need not supply an Unload routine.)
These requirements do not apply to some miniport drivers, for which the corresponding class or port driver
defines the entry points in the driver object. See the device-type-specific documentation for details.
The I/O manager maintains information about driver-created device objects in the corresponding driver object.
When a driver is loaded, its DriverEntry routine is called with a pointer to the driver object. When a driver's
DriverEntry routine is called, it sets Dispatch, StartIo (if any), and Unload (if any) entry points directly in the driver
object as follows:

DriverObject->MajorFunction[IRP_MJ_xxx] = DDDispatchXxx;
: :
DriverObject->MajorFunction[IRP_MJ_yyy] = DDDispatchYyy;
: :
DriverObject->DriverStartIo = DDStartIo;
DriverObject->DriverUnload = DDUnload;
: :

The DriverEntry routine also sets the entry point of its AddDevice routine, in the DriverExtension of its driver
object, as follows:

DriverObject->DriverExtension->AddDevice = DDAddDevice;

A DriverEntry or optional Reinitialize routine also can use a field in the driver object (not shown in the driver
object illustration) to get information from and/or set information in the configuration manager's registry database.
For more information, see Registry Keys for Drivers.
The I/O manager exports no support routines to manipulate driver objects, which are DRIVER_OBJECT
structures. Driver objects are used by the I/O manager to keep track of currently loaded drivers. Some members of
a driver object are used only by the I/O manager. Others members are also used by driver writers; for example, you
must know certain member names to define AddDevice, Dispatch, StartIo, and Unload entry points. You should
neither attempt to use undocumented members within a DRIVER_OBJECT structure, nor make assumptions
about the locations of any driver object members that are named in this documentation. Otherwise, you cannot
maintain the portability of a driver from one Windows platform to another.
Other Standard Driver Routines
12/5/2018 • 2 minutes to read • Edit Online

As the driver object illustration shows, kernel-mode drivers have other standard routines along with those for
which they set entry points in their respective driver objects. Most standard driver routines and some of the
configuration-dependent objects they use are defined by the I/O manager. The ISR, SynchCritSection routine, and
those shown in the Driver Object figure with names containing the word "custom" are defined by the NT kernel.
Most drivers use the device extension of each device object they create to maintain device-specific state about their
I/O operations and to store pointers to any system resources that they must allocate in order to have other
standard routines. For example, the DDCustomTimerDpc routine shown in the Driver Object figure requires the
driver to supply storage for kernel-defined timer and DPC objects.
The set of standard driver routines for lowest-level drivers shown on the left in the driver object illustration is
necessarily different from the set for higher-level drivers. Some of the routines shown in this figure are device-
dependent or configuration-dependent requirements. Others are optional: you may choose to implement such a
routine depending on the nature or configuration of the driver's devices, on the driver's design, and on the driver's
position in a chain of layered drivers.
Writing a DriverEntry Routine
6/25/2019 • 2 minutes to read • Edit Online

Each driver must have a DriverEntry routine, which initializes driver-wide data structures and resources. The I/O
manager calls the DriverEntry routine when it loads the driver.
In a driver that supports Plug and Play (PnP ), as all drivers should, the DriverEntry routine is responsible for
driver initialization, while the AddDevice routine (and, possibly, the dispatch routine that handles a PnP
IRP_MN_START_DEVICE request) is responsible for device initialization. Driver initialization includes exporting
the driver's other entry points, initializing certain objects the driver uses, and setting up various per-driver system
resources. (Non-PnP drivers have significantly different requirements, as described in the Driver Development Kit
[DDK] for Microsoft Windows NT 4.0 and earlier.)
DriverEntry routines are called in the context of a system thread at IRQL = PASSIVE_LEVEL.
A DriverEntry routine can be pageable and should be in an INIT segment so it will be discarded. Use an
alloc_text pragma directive, as illustrated in the sample drivers that are provided with the Windows Driver Kit
(WDK).
This section contains the following topics:
DriverEntry's Required Responsibilities
DriverEntry's Optional Responsibilities
DriverEntry Return Values
DriverEntry's Required Responsibilities
6/25/2019 • 2 minutes to read • Edit Online

The required, ordered responsibilities of a DriverEntry routine are as follows:


1. Supply entry points for the driver's standard routines.
The driver stores entry points for many of its standard routines in the driver object or driver extension. Such
entry points include those for the driver's AddDevice routine, dispatch routines, StartIo routine, and Unload
routine. For example, a driver would set the entry points for its AddDevice, DispatchPnP, and
DispatchPower routines with statements like the following (Xxx is a placeholder for a vendor-supplied prefix
identifying the driver):

:
DriverObject->DriverExtension->AddDevice = XxxAddDevice;
DriverObject->MajorFunction[IRP_MJ_PNP] = XxxDispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = XxxDispatchPower;
:

Additional standard routines, such as ISRs or IoCompletion routines, are specified by calling system
support routines. For more information, see the descriptions of individual standard driver routines.
2. Create and/or initialize various driver-wide objects, types, or resources the driver uses. Note that most
standard routines use objects on a per-device basis, so drivers should set up such objects in their AddDevice
routines or after receiving an IRP_MN_START_DEVICE request.
If the driver has a device-dedicated thread or waits on any kernel-defined dispatcher objects, the
DriverEntry routine might initialize kernel dispatcher objects. (Depending on how the driver uses the
object(s), it might instead perform this task in its AddDevice routine or after receiving an
IRP_MN_START_DEVICE request.)
3. Free any memory that it allocated and is no longer required.
4. Return NTSTATUS indicating whether the driver successfully loaded and can accept and process requests
from the PnP manager to configure, add, and start its devices. (See DriverEntry Return Values.)
DriverEntry's Optional Responsibilities
6/25/2019 • 2 minutes to read • Edit Online

Depending on the position of a particular driver in a chain of layered drivers, the nature of the underlying device,
and the design of the driver, a DriverEntry routine also can be responsible for the following:
Calling IoAllocateDriverObjectExtension to create and initialize a driver object extension, if the driver
requires storage for data on a driver-wide basis. The driver object extension is a driver-specific data
structure. For example, a driver might use its driver object extension to store a registry path or other global
information.
Calling PsCreateSystemThread to create executive worker threads, if the driver is a highest-level driver
(such as a file system driver) that uses such threads. In this case, the driver must also have a callback routine
of type WORKER_THREAD_ROUTINE, which takes a single input PVOID Parameter.
Registering a Reinitialize routine. (See Writing a Reinitialize Routine.)
Handling class-specific initialization requirements that differ from those discussed here, such as those that a
device-specific miniport or miniclass driver working in tandem with a port or class driver might have. See
the device-type specific documentation in the Windows Driver Kit (WDK) for details.
Providing Storage for System Resources
Per-device objects should be allocated in the AddDevice routine or in the Dispatch routine that handles the PnP
IRP_MN_START_DEVICE request, not in DriverEntry.
However, a driver might need to allocate additional system-space memory for other driver-wide uses. If so, the
DriverEntry routine can call one (or more) of the following routines:
IoAllocateDriverObjectExtension, to create a context area associated with the driver object
ExAllocatePoolWithTag for paged or nonpaged system-space memory
MmAllocateNonCachedMemory or MmAllocateContiguousMemory for cache-aligned nonpaged
system-space memory (used for I/O buffers)
Every DriverEntry routine is run in the context of a system thread at IRQL = PASSIVE_LEVEL. Therefore, any
memory allocated with ExAllocatePoolWithTag for use exclusively during initialization can be from paged pool,
as long as the driver does not control the device that holds the system page file. The allocated memory must be
released with ExFreePool before DriverEntry returns control. However, a driver that sets a Reinitialize routine
can pass a pointer to this memory when it calls IoRegisterDriverReinitialization, thus making the driver's
Reinitialize routine responsible for freeing the memory allocation.
Claiming Hardware Resources
Older, non-PnP drivers claimed resources from the registry. PnP drivers, on the other hand, neither claim device
resources from nor directly write resource requirements to the registry. Instead, these drivers report requirements
in response to certain PnP IRPs, as part of the PnP manager's enumeration process. A PnP driver receives its
allocated resources in a PnP IRP_MN_START_DEVICE request.
Drivers that do not interact directly with the PnP manager, such as certain miniport drivers, might have different
reporting requirements imposed by a class or port driver that does interact with the PnP manager. Such
requirements are specific to the device class. For device-specific and class-specific details, see the documentation
for the relevant device class in the Windows Driver Kit (WDK).
Using the Registry
A DriverEntry routine might use the registry to get some of the information it needs to initialize the driver, or it
might set information in the registry for other drivers or protected subsystems to use. The nature of the
information depends on the type of device. Drivers can access the registry using ZwXxx and RtlXxx routines. The
DriverEntry routine's RegistryPath parameter points to a counted Unicode string that specifies a path to the
driver's registry key, \Registry\Machine\System\CurrentControlSet\Services\*DriverName. The routine
should save a copy of the string, not the pointer itself, since the pointer is no longer valid after *DriverEntry returns.
DriverEntry Return Values
6/25/2019 • 2 minutes to read • Edit Online

A DriverEntry routine returns an NTSTATUS value, either STATUS_SUCCESS or an appropriate error status.
The DriverEntry routine should postpone any call to IoRegisterDriverReinitialization until just before it
returns STATUS_SUCCESS. It must not make this call unless it will return STATUS_SUCCESS.
If a DriverEntry routine returns an NTSTATUS value that is not a success or informational value, such as
STATUS_SUCCESS, the driver for that DriverEntry routine is not loaded.
A DriverEntry routine that will fail initialization must free any system objects, system resources, and registry
resources it has already set up before it returns control. It should reset the driver's dispatch entry points in the
driver object for IRP_MJ_FLUSH_BUFFERS and IRP_MJ_SHUTDOWN to NULL if the driver supports these
requests.
If a driver will fail initialization, the DriverEntry routine also should log an error before returning control. See
Logging Errors.
Note that a driver's Unload routine is not called if a driver's DriverEntry routine returns a failure status.
Writing a Reinitialize Routine
6/25/2019 • 2 minutes to read • Edit Online

Any driver that needs to initialize itself in stages can contain a Reinitialize routine. A Reinitialize routine is called
after the DriverEntry routine has returned control and other drivers have initialized themselves. Typically, the
Reinitialize routine performs tasks that must be done after another driver starts.
For example, the system's keyboard class driver,kbdclass, supports both PnP and legacy keyboard ports. If a
system includes one or more legacy ports that the PnP manager cannot detect, the keyboard class driver must
nevertheless create a device object for each port and layer itself over lower-level drivers for the port.
Consequently, the class driver has a Reinitialize routine to be called after its DriverEntry and AddDevice routines
have been called and other drivers have been loaded. The Reinitialize routine detects the port, creates a device
object for it, and layers the driver over other lower-level drivers for the device.
A driver's DriverEntry routine calls IoRegisterDriverReinitialization to queue a Reinitialize routine for
execution. The Reinitialize routine can also call IoRegisterDriverReinitialization itself, which causes the routine
to be requeued. One of the parameters to Reinitialize indicates the number of times it has been called.
The call to IoRegisterDriverReinitialization can include a pointer to driver-defined context data, which the
system supplies as input to Reinitialize. If the Reinitialize routine uses the registry, the context data should include
the RegistryPath pointer that was passed to the DriverEntry routine because this pointer is not an input
parameter to the Reinitialize routine.
The Reinitialize routine will not be called if DriverEntry does not return STATUS_SUCCESS.
Usually, a driver with a Reinitialize routine is a higher-level driver that controls both PnP and legacy devices. In
addition to creating device objects for the devices that the PnP manager detects (and for which the PnP manager
calls the driver's AddDevice routine), the driver must also create device objects for legacy devices that the PnP
manager does not enumerate. The Reinitialize routine creates those device objects and layers the driver over the
next-lower driver for the underlying device.
If a driver has a Reinitialize routine, it initializes in the same basic steps described in Writing a DriverEntry Routine,
and it also has the same basic requirements as its DriverEntry routine.
Writing an AddDevice Routine
6/25/2019 • 2 minutes to read • Edit Online

Any driver that supports PnP must have an AddDevice routine. The AddDevice routine creates one or more
device objects representing the physical, logical, or virtual devices for which the driver carries out I/O requests. It
also attaches the device object to the device stack, so the device stack will contain a device object for each driver
associated with the device.
The PnP manager calls a driver's AddDevice routine for each device controlled by the driver. AddDevice routines
are called during system initialization (when devices are first enumerated), and any time a new device is
enumerated while the system is running.
This section contains the following topics:
AddDevice Routines in Function or Filter Drivers
AddDevice Routines in Bus Drivers
Guidelines for Writing AddDevice Routines
AddDevice Routines in Function or Filter Drivers
6/25/2019 • 3 minutes to read • Edit Online

An AddDevice routine in a function or filter driver should take the following steps:
1. Call IoCreateDevice to create a functional or filter device object (an FDO or filter DO ) for the device being
added.
Do not specify a DeviceName for the device object, because doing so bypasses the PnP manager's security.
If a user-mode component needs a symbolic link to the device, register a device interface (see the next step
below ). If a kernel-mode component needs a legacy device name, the driver must name the device object,
but naming is not recommended.
Include FILE_DEVICE_SECURE_OPEN in the DeviceCharacteristics parameter. This characteristic directs
the I/O manager to perform security checks against the device object for all open requests, including
relative opens and trailing file name opens.
2. [optional] Create one or more symbolic links to the device.
Call IoRegisterDeviceInterface to register device functionality and create a symbolic link that applications
or system components can use to open the device. The driver should enable the interface by calling
IoSetDeviceInterfaceState when it handles the IRP_MN_START_DEVICE request. For more
information, see Device Interface Classes.
3. Store the pointer to the device's PDO in the device extension.
The PnP manager supplies a pointer to the PDO as the PhysicalDeviceObject parameter to AddDevice.
Drivers use the PDO pointer in calls to routines such as IoGetDeviceProperty.
4. Define flags in the device extension to track certain PnP states of the device, such as device paused,
removed, and surprise-removed.
For example, define one flag to indicate that incoming IRPs should be held while the device is in a paused
state. Create a queue for holding IRPs, if the driver does not already have a mechanism for queuing IRPs.
See Queuing and Dequeuing IRPs for more information.
Also allocate an IO_REMOVE_LOCK structure in the device extension and call IoInitializeRemoveLock
to initialize this structure. For more information, see Using Remove Locks.
5. Set the DO_BUFFERED_IO or DO_DIRECT_IO flag bit in the device object to specify the type of buffering
that the I/O manager is to use for I/O requests that are sent to the device stack. Higher-level drivers OR this
member with the same value as the next-lower driver in the stack, except possibly for highest-level drivers.
For more information, see Initializing a Device Object.
6. Set the DO_POWER_INRUSH or DO_POWER_PAGABLE flag for power management, if necessary. Drivers
that are pageable must set the DO_POWER_PAGABLE flag. The device object flags are typically set by the
bus driver when it creates the PDO for the device. However, higher-level drivers may occasionally need to
alter the values of these flags in their AddDevice routines when they create the FDO or filter DO. See
Setting Device Object Flags for Power Management for details.
7. Create and/or initialize any other software resources the driver uses to manage this device, such as events,
spin locks, or other objects. (Hardware resources, such as I/O ports, are configured later, in response to an
IRP_MN_START_DEVICE request.)
Because an AddDevice routine runs in a system thread context at IRQL = PASSIVE_LEVEL, any memory
allocated with ExAllocatePoolWithTag for use exclusively during initialization can be from paged pool, as
long as the driver does not control the device that holds the system page file. Such a memory allocation
must be released with ExFreePool before AddDevice returns control.
8. Attach the device object to the device stack (IoAttachDeviceToDeviceStack).
Specify a pointer to the device's PDO in the TargetDevice parameter.
Store the pointer returned by IoAttachDeviceToDeviceStack. This pointer, which points to the device
object of the next-lower driver for the device, is a required parameter to IoCallDriver and PoCallDriver
when passing IRPs down the device stack.
9. Clear the DO_DEVICE_INITIALIZING flag in the FDO or filter DO with a statement like the following:

FunctionalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

10. Be prepared to handle PnP IRPs for the device (such as


IRP_MN_QUERY_RESOURCE_REQUIREMENTS and IRP_MN_START_DEVICE ).
A driver must not start controlling the device until it receives an IRP_MN_START_DEVICE containing the list of
hardware resources assigned to the device by the PnP manager.
AddDevice Routines in Bus Drivers
12/5/2018 • 2 minutes to read • Edit Online

A PnP bus driver has an AddDevice routine, but it is called when the bus driver is acting as the function driver for
its controller or adapter. For example, the PnP manager calls the USB Hub bus driver's AddDevice routine to add
the hub device. The hub driver's AddDevice routine is not called for a child of the hub (a device that plugs into the
hub).
Guidelines for Writing AddDevice Routines
6/25/2019 • 2 minutes to read • Edit Online

Consider the following design guidelines when writing an AddDevice routine:


If a filter driver determines its AddDevice routine was called for a device it does not need to service, the
filter driver must return STATUS_SUCCESS to allow the rest of the device stack to be loaded for the device.
The filter driver does not create a device object nor attach it to the device stack; the filter driver just returns
success and allows the rest of the drivers to be added to the stack.
A driver must provide storage, usually in the device extension of a device object, for any kernel-defined
objects and executive spin locks it uses. A driver also must provide storage for pointers to certain objects
obtained from the I/O manager or other system components.
You might decide to allocate additional system-space memory for the driver's needs, such as for long-term
I/O buffers or a lookaside list. If so, an AddDevice routine can call the following routines:
ExAllocatePoolWithTag for paged or nonpaged system-space memory
ExInitializePagedLookasideList or ExInitializeNPagedLookasideList to initialize a paged or nonpaged
lookaside list
If the driver has a device-dedicated thread or waits on any kernel-defined dispatcher objects, the AddDevice
routine might initialize kernel dispatcher objects.
If the driver uses any executive spin locks or provides the storage for an interrupt spin lock, the AddDevice
routine might initialize these spin locks. See Spin Locks for more information.
Tighten file-open security when calling IoCreateDevice.
Specify the FILE_DEVICE_SECURE_OPEN characteristic on the call to IoCreateDevice. This characteristic
is supported on Windows NT 4.0 SP5 and later. It directs the I/O manager to perform security checks
against the device object for all open requests. Vendors should specify this characteristic on calls to
IoCreateDevice if the FILE_DEVICE_SECURE_OPEN characteristic is not set in the device's class-installer
INF or the device's INF and the drivers do not perform their own security checks on opens. (For more
information, see Controlling Device Namespace Access.)
If a driver sets the FILE_DEVICE_SECURE_OPEN characteristic when it calls IoCreateDevice, the I/O
manager applies the security descriptor of the device object to any relative opens or trailing-filename opens.
For example, if FILE_DEVICE_SECURE_OPEN is set for \Device\foo, and if \Device\foo can only be opened
by the administrator, then \Device\foo\abc can also be opened by the administrator. The I/O manager,
however, prevents a normal user from opening \Device\foo and \Device\foo\abc.
If one driver for a device sets this characteristic, the PnP manager propagates it to all the device objects for
the device.
Writing Dispatch Routines
6/25/2019 • 2 minutes to read • Edit Online

Processing any I/O request packet (IRP ) begins in a dispatch routine that the driver registers to handle an IRP
major function code (IRP_MJ_*XXX ). The driver's DriverEntry routine exports entry points for dispatch routines in
a dispatch table within the driver's DRIVER_OBJECT* structure.
A driver can provide a separate dispatch routine for each major I/O function code that it handles. Alternatively,
dispatch routines can be written to handle multiple I/O function codes.
This section contains the following topics:
Dispatch Routine Functionality
Required Dispatch Routines
Optional Dispatch Routines
Dispatch Routines and IRQLs
When to Check the Driver's I/O Stack Location
DispatchCreate, DispatchClose, and DispatchCreateClose Routines
DispatchCleanup Routines
DispatchRead, DispatchWrite, and DispatchReadWrite Routines
DispatchDeviceControl and DispatchInternalDeviceControl Routines
DispatchPnP Routines
DispatchPower Routines
DispatchQueryInformation Routines
DispatchSetInformation Routines
DispatchFlushBuffers Routines
DispatchShutdown Routines
DispatchSystemControl Routines
Dispatch Routine Functionality
12/5/2018 • 2 minutes to read • Edit Online

The required functionality of a particular dispatch routine varies, depending on the I/O function code it handles, on
the individual driver's position in a chain of drivers, and on the type of underlying physical device.
Most dispatch routines process incoming I/O request packets (IRPs) as follows:
1. Check the driver's I/O stack location in the IRP to determine what to do and check the parameters, if any, for
validity.
Whether a driver must check its I/O stack location to determine what to do and to check parameters
depends on the given IRP_MJ_XXX, as well as on whether that driver set up a separate Dispatch routine for
each IRP_MJ_XXX that the driver handles.
2. Satisfy the request and complete the IRP if possible; otherwise, pass it on for further processing by lower-
level drivers or by other device driver routines.
Whether a driver must pass on an IRP for further processing depends on the validity of the parameters, if
any, as well as on the IRP_MJ_XXX and on the driver's level, if any, in a chain of layered drivers.
For more information about IRPs, see Handling IRPs.
Required Dispatch Routines
6/25/2019 • 2 minutes to read • Edit Online

Most drivers must handle the following Dispatch routines:


DispatchPnP
IRP_MJ_PNP indicates a request involving PnP device recognition, hardware configuration, or resource
allocation. Such requests are typically sent to a device driver from the PnP manager or from a closely
coupled higher-level driver.
DispatchPower
IRP_MJ_POWER indicates a request pertaining to the power state of either the device or the system. Such
requests are sent to the device driver by either the power manager or a closely coupled higher-level driver.
DispatchCreate
IRP_MJ_CREATE indicates either that a user-mode protected subsystem, possibly on behalf of an
application or subsystem-specific driver, has requested a handle for the file object associated with the target
device object, or that a higher-level driver is connecting or attaching its device object to the target device
object.
DispatchClose
IRP_MJ_CLOSE indicates that the last handle of the file object that was associated the target device object
has been closed and released. All I/O requests have been completed or canceled, so there are no
outstanding references to the file object pointer.
DispatchRead
IRP_MJ_READ indicates an I/O request to transfer data from the underlying physical device to the system.
DispatchWrite
IRP_MJ_WRITE indicates an I/O request to transfer data from the system to the underlying physical
device.
DispatchDeviceControl
IRP_MJ_DEVICE_CONTROL indicates a request that contains a system-defined, device-type-specific I/O
control code specifying a device type-specific operation. Higher-level drivers pass these IRPs on to their
underlying device drivers, which typically process the request by accessing the device.
DispatchInternalDeviceControl
IRP_MJ_INTERNAL_DEVICE_CONTROL indicates a request sent to the device driver, in most cases from
a closely coupled higher-level driver, usually with a privately defined, driver-specific and device-type-specific
or device-specific I/O control code requesting a device-type-specific or device-specific operation.
Only certain kinds of drivers are required to handle system-defined internal device I/O control requests,
including certain SCSI drivers, keyboard or mouse device drivers, and parallel drivers that interoperate with
system-supplied drivers.
DispatchSystemControl
IRP_MJ_SYSTEM_CONTROL is used to specify WMI requests to drivers. For more information about
WMI, see Windows Management Instrumentation.
The dispatch routines that a driver must provide vary according to the type and functionality of the underlying
physical device. For device-type-specific information about IRP major function codes that drivers must handle, see
the device-type specific documentation in the Windows Driver Kit (WDK).
Optional Dispatch Routines
6/25/2019 • 2 minutes to read • Edit Online

Drivers might include the following dispatch routines:


DispatchCleanup
IRP_MJ_CLEANUP indicates that the last handle for a file object that is associated with the target device
object is being closed. Outstanding I/O requests for the file object might still exist. Drivers can implement a
DispatchCleanup routine to perform cleanup that is not specific to any particular file handle. Drivers can
also use their DispatchClose routine for the same purpose.
DispatchQueryInformation, DispatchSetInformation
Some highest-level drivers might have to process IRP_MJ_QUERY_INFORMATION and
IRP_MJ_SET_INFORMATION IRPs. Such requests indicate that a user-mode application, kernel-mode
component, or driver has requested information about the length of the file object (representing the driver's
device object) for which the user-mode requester has a handle, or that the user-mode requester is
attempting to set an end-of-file on that file object.
Parallel class and serial device drivers handle these requests by setting the
FILE_STANDARD_INFORMATION or FILE_POSITION_INFORMATION length or position to zero.
Other highest-level device drivers should support these requests, particularly if a user-mode application or
kernel-mode driver might call C runtime functions to manipulate the file object. File system drivers must
support these requests more fully than these highest-level device drivers.
DispatchFlushBuffers
A driver that caches data in a device or buffers data internally in driver-allocated memory might receive
IRP_MJ_FLUSH_BUFFERS. Receipt of this request indicates that the driver should write its buffered data
or flush the cached data out to the device, or should discard buffered or cached data that was read from the
device.
For example, the system keyboard and mouse class drivers, which have internal ring buffers for input data
from their devices, support the flush request. Drivers of mass-storage devices and drivers layered above
them also support this request.
DispatchShutdown
Any driver that is likely to be called before the system shuts down must handle IRP_MJ_SHUTDOWN.
The DispatchShutdown routine should do whatever driver-determined cleanup is necessary before the
power manager sends a system set-power IRP to shut down the system. A driver can call
IoRegisterShutdownNotification or IoRegisterLastChanceShutdownNotification to register for
shutdown notification.
Drivers for mass-storage devices and intermediate drivers layered over them can rely on a highest-level file system
driver to send them shutdown IRPs when the system is about to shut down. That is, the FSD is responsible for
making sure that any cached file data is written out to peripheral devices, calling underlying drivers to flush data
from their device caches or buffers (if any), and so forth before the system is shut down.
The driver of a mass-storage device that caches data internally must provide DispatchShutdown and
DispatchFlushBuffers routines. If a mass-storage driver buffers data in memory but its device has no internal
cache, it also must provide DispatchShutdown and DispatchFlushBuffers routines.
Any intermediate driver layered above a driver that handles IRP_MJ_FLUSH_BUFFERS and
IRP_MJ_SHUTDOWN requests also provide DispatchShutdown and DispatchFlushBuffers routines.
Dispatch Routines and IRQLs
6/25/2019 • 2 minutes to read • Edit Online

Most drivers' dispatch routines are called in an arbitrary thread context at IRQL = PASSIVE_LEVEL, with the
following exceptions:
Any highest-level driver's dispatch routines are called in the context of the thread that originated the I/O
request, which is commonly a user-mode application thread.
In other words, the dispatch routines of file system drivers and other highest-level drivers are called in a
nonarbitrary thread context at IRQL = PASSIVE_LEVEL.
The DispatchRead, DispatchWrite, and DispatchDeviceControl routines of lowest-level device drivers, and
of intermediate drivers layered above them in the system paging path, can be called at IRQL = APC_LEVEL
and in an arbitrary thread context.
The DispatchRead and/or DispatchWrite routines, and any other routine that also processes read and/or
write requests in such a lowest-level device or intermediate driver, must be resident at all times. These
driver routines can neither be pageable nor be part of a driver's pageable-image section; they must not
access any pageable memory. Furthermore, they should not be dependent on any blocking calls (such as
KeWaitForSingleObject with a nonzero time-out).
The DispatchPower routine of drivers in the hibernation and/or paging paths can be called at IRQL =
DISPATCH_LEVEL. The DispatchPnP routines of such drivers must be prepared to handle PnP
IRP_MN_DEVICE_USAGE_NOTIFICATION requests.
The DispatchPower routine of drivers that require inrush power at start-up can be called at IRQL =
DISPATCH_LEVEL.
For additional information, see Managing Hardware Priorities.
When to Check the Driver's I/O Stack Location
6/25/2019 • 2 minutes to read • Edit Online

A major I/O function code is set in the driver's I/O stack location for each incoming IRP.
A driver's dispatch routine must check the driver's I/O stack location for the IRP to determine what to do if any of
the following conditions hold:
The dispatch routine handles more than one major I/O function code.
The dispatch routine must handle a set of minor function codes for certain major function codes. IRPs with
minor function codes include IRP_MJ_PNP and IRP_MJ_POWER, as well as certain IRPs that the SCSI
port driver and file system drivers must handle.
The dispatch routine of a device driver or of a closely coupled higher-level driver handles
IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL requests, which have an
associated set of I/O control codes.
To get a pointer to a driver's I/O stack location, its dispatch routine calls IoGetCurrentIrpStackLocation.
Higher-level drivers' dispatch routines always call IoGetCurrentIrpStackLocation and also call
IoGetNextIrpStackLocation to get a pointer to the next-lower driver's I/O stack location for IRPs that they set
up for the next-lower driver, when passing IRPs down the driver stack.
The DispatchDeviceControl routine or DispatchInternalDeviceControl routine of a device driver, or possibly of its
closely coupled class driver(s), must determine which I/O control code is set in the driver's I/O stack location at
Parameters.DeviceIoControl.IoControlCode for each request. The I/O control code is contained in the driver's
I/O stack location.
In most cases, the DispatchDeviceControl or DispatchInternalDeviceControl routine of a higher-level driver simply
passes an IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL request on to the next-
lower driver, after setting up its stack location in the IRP. However, SCSI class drivers must check for certain SCSI
Port I/O control codes so that they can set up the SCSI port driver's I/O stack location correctly before passing on
these requests.
DispatchCreate, DispatchClose, and
DispatchCreateClose Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DRIVER_DISPATCH IRPs with I/O function codes of IRP_MJ_CREATE and IRP_MJ_CLOSE,
respectively. Alternatively, a combined DispatchCreateClose routine can handle IRPs for both of these I/O function
codes.
A create request can originate either from a user-mode subsystem's attempt to get a handle to a file object
representing a device (possibly on behalf of an application or subsystem-level driver) or in a higher-level driver's
call to IoGetDeviceObjectPointer or IoAttachDevice.
A reciprocal close request originates from a user-mode subsystem's close of the file object handle associated with
the driver's device object.
Each of these requests is inherently synchronous.
Separate DispatchCreate and DispatchClose Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's Dispatch routines for IRP_MJ_CREATE and IRP_MJ_CLOSE requests might do nothing more than
complete the input IRP with STATUS_SUCCESS. For more information, see Completing IRPs.
Another driver's Dispatch routines for IRP_MJ_CREATE and IRP_MJ_CLOSE requests might do more work,
depending on the underlying device driver or on the underlying device. Consider the following scenarios:
On receipt of a create request, a class driver might initialize an internal queue and send an
IRP_MJ_INTERNAL_DEVICE_CONTROL request down to the corresponding port driver requesting
device configuration information or exclusive access to a controller port.
Receipt of IRP_MJ_CLOSE indicates that the last reference to the file object that is associated with the
target device object has been removed. This implies that all handles to the file object has been closed and all
outstanding I/O requests have been completed or canceled.
On receipt of a create request, a driver of an infrequently used device might call
MmLockPagableCodeSection to make resident some of the driver routines that process other
IRP_MJ_XXX requests. On receipt of a reciprocal close request, the driver might call
MmUnlockPagableImageSection to conserve system memory by having its pageable-image section
paged out when all file object handles for such a driver's device object(s) are closed.
Some drivers handle IRP_MJ_CLOSE requests only for symmetry because, after their device objects have been
opened by a protected subsystem or higher-level driver, the lower-level drivers' device objects are not closed until
the system itself is shut down. For example, keyboard and mouse drivers set up device objects representing
physical devices that must be functional while the system is running, so these drivers might have minimal
DispatchClose routines for symmetry, or they might have combined DispatchCreateClose routines.
If the device controlled by a lower-level driver must be available for the system to continue running, the driver's
DispatchClose routine generally will not be called. For example, some of the system disk drivers have no
DispatchClose routine, but these drivers usually have DispatchFlushBuffers and DispatchShutdown routines to
complete any outstanding file I/O operations before the system is shut down.
While you can implement separate DRIVER_DISPATCH and DispatchClose routines, drivers sometimes have a
single DispatchCreateClose routine for handling both create and close requests.
A Single DispatchCreateClose Routine
6/25/2019 • 2 minutes to read • Edit Online

Many drivers, particularly lower-level drivers in a chain of layered drivers, merely need to establish their existence
on receipt of a create request and merely need to acknowledge the receipt of a close request.
For example, a port driver for a device controller with one or more closely coupled class drivers that call
IoGetDeviceObjectPointer might have a minimal DispatchCreateClose routine. The routine might do nothing
more than complete the IRP as follows:

: :
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

This minimal DispatchCreateClose routine sets the Information member of the I/O status block to zero,
indicating the file object is opened for a create request; Information has no meaning for a close request. The
routine sets the Status member to STATUS_SUCCESS and also returns this status value, indicating that the driver
is ready to accept I/O requests.
This minimal DispatchCreateClose routine completes the create IRP without boosting the priority of the originator
of the IRP (IO_NO_INCREMENT), because the originator is assumed to wait for an indeterminate but very small
interval for the request to complete.
How much work a DispatchCreateClose routine does depends partly on the nature of the driver's device or the
underlying device and partly on the design of the driver. If a driver performs very different operations for create
and close requests, it should handle these requests in separate DispatchCreate and DispatchClose routines.
To handle a create request to open a file object representing a logical or physical device, a highest-level driver
should do the following:
1. Call IoGetCurrentIrpStackLocation to get a pointer to its I/O stack location in the IRP.
2. Check FileObject.FileName in the I/O stack location and complete the IRP with STATUS_SUCCESS if the
Unicode string at FileName has a zero length; otherwise, complete the IRP with
STATUS_INVALID_PARAMETER.
Following the preceding steps ensures that no attempt to open a pseudofile on a device can cause problems later.
For example, this prevents attempts to open a nonexistent \\device\parallel0\temp.dat.
Rules for Implementing DispatchCreate,
DispatchClose, and DispatchCreateClose Routines
12/5/2018 • 2 minutes to read • Edit Online

Keep the following points in mind when implementing DispatchCreate, DispatchClose, and DispatchCreateClose
routines:
At a minimum, the routine must do the following:
1. Set the Status field of the input IRP's I/O status block with an appropriate NTSTATUS, usually
STATUS_SUCCESS.
2. Set the Information field of the input IRP's I/O status block to zero.
3. Call IoCompleteRequest with the IRP and a PriorityBoost of IO_NO_INCREMENT.
4. Return the NTSTATUS that it set in the Status field of the IRP's I/O status block.
In a highest-level or intermediate driver, the routine might have to do additional work to process a create or
close request, depending on the nature of its device or of the underlying device, and on the design of the
driver.
For a create request to open a file object that represents a logical or physical device, a highest-level driver
should check the FileObject.FileName in the I/O stack location and complete the IRP with
STATUS_SUCCESS if the Unicode string at FileName has a zero length. Otherwise, it should complete the
IRP with STATUS_INVALID_PARAMETER.
The routines of lowest-level drivers are called only when the next-higher-level driver calls
IoAttachDeviceToDeviceStack, IoGetDeviceObjectPointer, or IoAttachDevice. The lowest-level
driver in a chain of layered drivers frequently does only the minimum required processing of a create or
close request.
DispatchCleanup Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchCleanup routine handles IRPs for the IRP_MJ_CLEANUP I/O function code.
Drivers can use a DispatchCleanup routine to perform any cleanup operations that are needed after all of the
handles to a file object have been closed. Note that DispatchCleanup is called in the process context of the process
that closed the final handle; this process might be different from the process that initially opened the handle.
(Typically this difference happens because another process uses the DuplicateHandle user-mode routine to
duplicate the processes handles.) Drivers that must perform cleanup in the original process context can use the
PsSetCreateProcessNotifyRoutine routine to register a callback routine for that purpose, but keep in mind that
such callbacks are a limited system resource.
In general, a DispatchCleanup routine must process an IRP_MJ_CLEANUP request by doing the following for
every IRP that is currently in the device queue (or in the driver's internal queue of IRPs), for the target device
object, and is associated with the file object:
Call IoSetCancelRoutine to set the Cancel routine pointer to NULL.
Cancel every IRP that is currently in the queue for the target device object, if the file object that is specified
in the driver's I/O stack location of the queued IRP matches the file object that was received in the I/O stack
location of the IRP_MJ_CLEANUP request.
Call IoCompleteRequest to complete the IRP, and return STATUS_SUCCESS.
While processing an IRP_MJ_CLEANUP request, a driver can receive additional requests, such as
IRP_MJ_READ or IRP_MJ_WRITE. Therefore, a driver that must deallocate resources must also synchronize
execution of its DispatchCleanup routine with other dispatch routines, such as DispatchRead and DispatchWrite.
DispatchRead, DispatchWrite, and
DispatchReadWrite Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchRead and DispatchWrite routines handle IRPs with I/O function codes of IRP_MJ_READ and
IRP_MJ_WRITE, respectively. Alternatively, a combined DispatchReadWrite routine can handle IRPs for both of
these I/O function codes.
Every driver of a device from which data can be transferred to the system must have a DispatchRead routine. Every
driver of a device to which data can be transferred from the system must have a DispatchWrite routine. Any driver
that transfers data in both directions can have a combined DispatchReadWrite routine.
Lower-level drivers handle IRP_MJ_READ and IRP_MJ_WRITE requests asynchronously. Therefore,
DispatchRead and/or DispatchWrite routines in highest-level drivers must pass these requests on for further
processing, provided that the request has valid parameters in that driver's I/O stack location of the IRP.
Whether a driver sets up its device objects for buffered or direct I/O affects how it handles transfer requests. In
particular, a driver that uses direct I/O to do DMA operations might need to split up large transfer requests into a
sequence of smaller transfer operations in order to satisfy an IRP_MJ_READ or IRP_MJ_WRITE request. For
more information, see Input/Output Techniques.
The following subsections discuss some of the design and implementation considerations for DispatchReadWrite
routines in lowest-level device drivers that use buffered I/O and direct I/O, as well as in higher-level drivers
layered above them:
Handling Transfers Asynchronously
DispatchReadWrite Using Buffered I/O
DispatchReadWrite Using Direct I/O
DispatchReadWrite in Higher-Level Drivers
Summary of Read/Write Dispatch Routines
Handling Transfers Asynchronously
6/25/2019 • 2 minutes to read • Edit Online

Except for highest-level drivers, all drivers handle IRP_MJ_READ and IRP_MJ_WRITE requests asynchronously.
The DispatchRead and DispatchWrite routines in even a highest-level driver cannot wait for lower-level drivers to
finish processing an asynchronous read or write request; they must pass such a request on to lower drivers and
return STATUS_PENDING.
Similarly, a lowest-level device driver's DispatchReadWrite routine must pass the transfer request on to other
driver routines that process device I/O requests and then return STATUS_PENDING.
A higher-level driver sometimes must set up partial-transfer IRPs and pass them on to lower drivers. The higher-
level driver can complete the original read/write IRP only when its partial-transfer requests have been completed
by the lower drivers.
For example, a SCSI class driver's DispatchReadWrite routine is required to split large transfer requests that
exceed the underlying HBA's transfer capabilities into a set of partial-transfer requests. The class driver must set up
the parameters in its partial-transfer IRPs so that the SCSI port/miniport drivers can satisfy each partial-transfer
request in a single DMA operation.
Other device drivers that use DMA or PIO also might need to split up large transfer requests for themselves.
For more information about using DMA and PIO, see Input/Output Techniques.
DispatchReadWrite Using Buffered I/O
6/25/2019 • 2 minutes to read • Edit Online

Any lowest-level device driver that sets up its device objects for buffered I/O satisfies a read request by returning
data transferred from its device into a locked down system-space buffer at Irp->AssociatedIrp.SystemBuffer. It
satisfies a write request by transferring data from the same buffer out to its device.
Consequently, the DispatchReadWrite routine of such a device driver usually does the following on receipt of a
transfer request:
1. Calls IoGetCurrentIrpStackLocation and determines the direction of the transfer request.
2. Checks the validity of the parameters for the request.
For a read request, the routine usually checks the driver's IoStackLocation-
>Parameters.Read.Length value to determine whether the buffer is large enough to receive data
transferred from the device.
For example, the system keyboard class driver processes read requests that come only from the
Win32 user input thread. This driver defines a structure, KEYBOARD_INPUT_DATA, in which to store
keystrokes from the device and, at any given moment, holds some number of these structures in an
internal ring buffer in order to satisfy read requests as they come in.
For a write request, the routine usually checks the value at Parameters.Write.Length, and checks
the data at Irp->AssociatedIrp.SystemBuffer for validity if necessary: that is, if its device accepts
only structured data packets containing members with defined value ranges.
3. If any parameters are invalid, the DispatchReadWrite routine completes the IRP immediately, as already
described in Completing IRPs. Otherwise, the routine passes the IRP on for further processing by other
driver routines, as described in Passing IRPs down the Driver Stack.
Lowest-level device drivers that use buffered I/O usually must satisfy a transfer request by reading or writing data
of a size specified by that the originator of the request. Such a driver is likely to define a structure for data coming
in from or being sent to its device and is likely to buffer structured data internally, as the system keyboard class
driver does.
Drivers that buffer data internally should support IRP_MJ_FLUSH_BUFFERS requests, and can also support
IRP_MJ_SHUTDOWN requests.
The highest-level driver in a chain is usually responsible for checking the input IRP's parameters before passing a
read/write request on to lower drivers. Consequently, many lower-level drivers can assume that their I/O stack
locations in a read/write IRP have valid parameters. If a lowest-level driver in a chain is aware of device-specific
constraints on data transfers, that driver is required to check the validity of the parameters in its I/O stack location.
DispatchReadWrite Using Direct I/O
6/25/2019 • 2 minutes to read • Edit Online

Any lower-level device driver that sets up its device objects for direct I/O satisfies a read request by returning data
transferred from its device to system physical memory, which is described by the MDL at Irp->MdlAddress. It
satisfies a write request by transferring data from system physical memory out to its device.
Lower-level drivers must handle read/write requests asynchronously. Therefore, every lower-level driver's
DispatchReadWrite routine must pass IRP_MJ_READ and IRP_MJ_WRITE IRPs with valid parameters on to
other driver routines, as described in Passing IRPs down the Driver Stack.
For read/write IRPs sent to lower-level drivers, the paged physical memory described by the MDL at Irp-
>MdlAddress has already been probed for the correct access rights to carry out the requested transfer and has
already been locked down by the highest-level driver in the chain or by the I/O manager. Any intermediate or
lowest-level driver that sets up its device objects for direct I/O should not call MmProbeAndLockPages because
this has already been done. A lowest-level driver calls MmGetSystemAddressForMdlSafe. (Drivers for
Windows 98 call MmGetSystemAddressForMdl instead. Drivers for Windows Me, Windows 2000 and later
versions of Windows should use MmGetSystemAddressForMdlSafe.)
Any intermediate or lowest-level device driver's DispatchReadWrite routine should validate the parameters in its
I/O stack location of read/write IRPs if it cannot trust a higher-level driver to pass down only IRPs with valid
parameters. If the DispatchReadWrite routine finds a parameter error, it should complete the IRP with an
appropriate error STATUS_XXX value as already described in Completing IRPs. If parameters are valid, an
intermediate driver's DispatchReadWrite routine must pass the request on for further processing, according to the
guidelines in DispatchReadWrite in Higher-Level Drivers.
A lowest-level device driver's DispatchReadWrite routine must call IoMarkIrpPending with the transfer request,
pass the IRP on for further processing by other driver routines, and return STATUS_PENDING, as described in
Passing IRPs down the Driver Stack.
Note that a device driver's DispatchReadWrite routine can control the order in which IRPs are queued to its device
for faster I/O throughput by calling IoStartPacket with a driver-determined Key value. Another routine in the
driver dequeues the IRP later, determines whether the requested length must be split into partial-transfer
operations, and programs the device to transfer data.
In general, a device driver that must split up large transfer requests to suit the limitations of its device should
postpone these operations until just before setting up the device for a given transfer request. Such a device driver's
DispatchReadWrite routine should not check the I/O stack location of incoming IRPs for any device-specific
transfer constraints, nor attempt to calculate partial-transfer ranges, when the driver can postpone these checks
until just before its StartIo (or other driver routine) programs the device for a transfer operation.
DispatchReadWrite in Higher-Level Drivers
6/25/2019 • 2 minutes to read • Edit Online

Except for file system drivers, a higher-level driver usually does not have any internal driver queues for IRPs. Such
a driver's DispatchReadWrite routine can pass IRPs with valid parameters on to lower drivers, possibly after
setting up its IoCompletion routine, as described in Passing IRPs down the Driver Stack.
However, a SCSI class driver's DispatchReadWrite routine is responsible for splitting up large transfer requests, if
necessary, before it sends an IRP with the major function code IRP_MJ_READ or IRP_MJ_WRITE to the SCSI
port/miniport driver pair. For more information, see Storage Class Driver's SplitTransferRequest Routine.
If a higher-level driver allocates one or more IRPs, which it sets up for the next-lower driver in its
DispatchReadWrite routine, to request some number of partial transfers, the DispatchReadWrite routine must call
IoSetCompletionRoutine with each driver-allocated IRP. The driver must register its IoCompletion routine to
track how much data is transferred in each partial-transfer operation so that the IoCompletion routine can release
all driver-allocated IRPs and, eventually, complete the original request.
If the underlying driver controls a removable-media device, any IRPs allocated by the higher-level driver must
have a thread context. To set up a thread context, the allocating driver must set the Irp->Tail.Overlay.Thread in
each newly allocated IRP from the same value in the incoming transfer IRP. For more information, see Supporting
Removable Media.
If the underlying device driver returns an IRP for a partial transfer with an error, the IoCompletion routine can
either retry the partial-transfer request or complete the original IRP with its I/O status block set with the returned
error, after freeing any IRPs and memory the higher-level driver has allocated.
If a higher-level driver's DispatchReadWrite routine allocates memory for partial-transfer operations and its
allocation will be accessed by the driver's IoCompletion routine (or by the underlying device driver), the
DispatchReadWrite routine must allocate that memory from nonpaged pool.
Summary of Read/Write Dispatch Routines
6/25/2019 • 2 minutes to read • Edit Online

Keep the following points in mind when implementing a DispatchRead, DispatchWrite, or DispatchReadWrite
routine:
It is the responsibility of the highest-level driver in a chain of layered drivers to check the parameters of
incoming read/write IRPs for validity before setting up the next-lower-level driver's I/O stack location in an
IRP.
Intermediate and lowest-level drivers generally can rely on the highest-level driver in their chain to pass
down transfer requests with valid parameters. However, any driver can perform sanity checks on the
parameters in its I/O stack location of an IRP, and each device driver should check the parameters for
conditions that might violate any restrictions imposed by its device.
If a DispatchReadWrite routine completes an IRP with an error, it should set the I/O stack location Status
member with an appropriate NTSTATUS -type value, set the Information member to zero, and call
IoCompleteRequest with the IRP and a PriorityBoost of IO_NO_INCREMENT.
If a driver uses buffered I/O, it might need to define a structure to contain data to be transferred and might
need to buffer some number of these structures internally.
If a driver uses direct I/O, it might need to check whether the MDL at Irp->MdlAddress describes a buffer
containing too much data (or too many page breaks) for the underlying device to handle in a single transfer
operation. If so, the driver must split up the original transfer request into a sequence of smaller transfer
operations.
A closely coupled class driver might split up such a request in its DispatchReadWrite routine for its
underlying port driver. SCSI class drivers, particularly for mass-storage devices, are required to do this. For
more information about requirements for SCSI drivers, see Storage Drivers.
A lower-level device driver's DispatchReadWrite routine should postpone splitting a large transfer request
into partial transfers until another driver routine dequeues the IRP to set up the device for the transfer.
If a lower-level device driver queues a read/write IRP for further processing by its own routines, it must call
IoMarkIrpPending before it queues the IRP. The DispatchReadWrite routine also must return control with
STATUS_PENDING in these circumstances.
If the DispatchReadWrite routine passes an IRP on to lower drivers, it must set up the I/O stack location for
the next-lower driver in the IRP. Whether the higher-level driver also sets an IoCompletion routine in the
IRP before passing it on with IoCallDriver depends on the design of the driver and of those layered under
it.
However, a higher-level driver must call IoSetCompletionRoutine before it calls IoCallDriver if it
allocates any resources, such as IRPs or memory. Its IoCompletion routine must free any driver-allocated
resources when lower drivers have completed the request but before the IoCompletion routine calls
IoCompleteRequest with the original IRP.
If a higher-level driver allocates IRPs for lower drivers that might include an underlying removable-media
device driver, the allocating driver must establish the thread context in each IRP it allocates.
DispatchDeviceControl and
DispatchInternalDeviceControl Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's dispatch routines (see DRIVER_DISPATCH ) handle IRPs with I/O function codes of
IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL, respectively.
For every common type of peripheral device, the system defines a set of I/O control codes for
IRP_MJ_DEVICE_CONTROL requests. New drivers for each type of device must support these requests. In most
cases, these public I/O control codes for each type of device are not exported to user-mode applications.
Some of these system-defined I/O control codes are used by higher-level drivers that create IRPs for the
underlying device driver by calling IoBuildDeviceIoControlRequest. Others are used by Win32 components to
communicate with an underlying device driver by calling the Win32 function DeviceIoControl (described in
Microsoft Windows SDK documentation) which, in turn, calls a system service. The I/O manager sets up an IRP,
and stores the major function code IRP_MJ_DEVICE_CONTROL and the given I/O control code in the
IO_STACK_LOCATION structure at Parameters.DeviceIoControl.IoControlCode. Then, the I/O manager calls
the DispatchDeviceControl routine of the highest-level driver in the chain.
For certain system-supplied drivers designed to interoperate with and support new drivers, the operating system
also defines a set of I/O control codes for IRP_MJ_INTERNAL_DEVICE_CONTROL requests. In most cases,
these public I/O control codes allow add-on higher-level drivers to interoperate with an underlying device driver.
As an example, the system-supplied parallel drivers support a set of internal I/O control codes that vendor-
supplied drivers send in IRP_MJ_INTERNAL_DEVICE_CONTROL requests. For more information, see Internal
Device Control Requests for Parallel Ports and Internal Device Control Requests for Parallel Devices.
Almost all operations requested through system-defined I/O control codes use buffered I/O, because this type of
request seldom requires the transfer of large amounts of data. That is, even drivers that set up their device objects
for direct I/O are sent IRPs for device control requests with data to be transferred into or out of the buffer at Irp-
>AssociatedIrp.SystemBuffer (except for certain types of highest-level device drivers with closely coupled
Win32 multimedia drivers).
In addition, a driver can define a set of private I/O control codes that other drivers can use to communicate with it.
New public I/O control codes can be added to the system only with the cooperation of Microsoft Corporation,
because public I/O control codes are built into the operating system itself.
For specific information about the set of public I/O control codes that different kinds of drivers must support and
about defining private I/O control codes, see the device-specific reference sections of the Windows Driver Kit
(WDK).
DispatchDeviceControl in Lowest-Level Drivers
6/25/2019 • 2 minutes to read • Edit Online

An IRP_MJ_DEVICE_CONTROL request for a lowest-level driver requires that the driver either change the state
of its device or provide information about the state of its device. Because most kinds of drivers are required to
handle a number of I/O control codes, their DispatchDeviceControl routines usually contain a switch statement
somewhat like the following:

: :
switch (irpSp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DeviceType_XXX:
case IOCTL_DeviceType_YYY:
if (irpSp->Parameters.DeviceIoControl.InputBufferLength <
(sizeof(IOCTL_XXXYYY_STRUCTURE)))
{
status = STATUS_BUFFER_TOO_SMALL;
break;
} else {
IoMarkIrpPending(Irp);
: : // pass IRP on for further processing
case ...
: :

As this code fragment shows, a DispatchDeviceControl routine also checks parameters, sometimes on each I/O
control code that the driver must support, sometimes on groups of these I/O control codes.
Consider the following implementation guidelines for device drivers' DispatchDeviceControl routines:
DispatchDeviceControl must check the parameters for validity, and immediately complete IRPs with
parameter errors, as described in Completing IRPs.
Grouping I/O control codes in a case statement (where practical) when testing for valid parameters is
economical in terms of driver performance and size and in code maintenance. As the preceding code
fragment suggests, I/O control codes that use a common structure are natural candidates for such a case
group.
Switching first on any I/O control codes for which the DispatchDeviceControl routine can satisfy and
complete the IRP improves performance because the driver can return control faster.
Switching later on I/O control codes that specify infrequently requested operations also can improve the
driver's performance in processing IRP_MJ_DEVICE_CONTROL requests.
For better performance, every lowest-level device driver's DispatchDeviceControl routine should satisfy any
device control request that it can, without queuing the IRP to other driver routines.
If the DispatchDeviceControl routine can complete the IRP, it should call IoCompleteRequest with a
PriorityBoost of IO_NO_INCREMENT. If the DispatchDeviceControl routine must queue the IRP for further
processing, it must call IoMarkIrpPending and return STATUS_PENDING.
DispatchDeviceControl in Higher-Level Drivers
6/25/2019 • 2 minutes to read • Edit Online

Usually, the DispatchDeviceControl routine of a higher-level driver simply sets up the I/O stack location for the
next-lower-level driver and passes the IRP on with IoCallDriver. The DispatchDeviceControl routine seldom
checks the validity of parameters in the input IRP because the underlying device driver is assumed to have better
information about how to handle each device-type-specific I/O control request.
A possible exception to this general rule is the DispatchDeviceControl routine in the class driver of a class/port
driver pair. For more information about handling device control requests in paired class/port drivers, see
Dispatch(Internal)DeviceControl in Class/Port Drivers.
Any new higher-level driver that is not closely associated with a particular device driver should simply set up the
I/O stack location for the next-lower-level driver and pass the IRP_MJ_DEVICE_CONTROL request on for further
processing.
A device control request is usually handled synchronously. That is, a higher-level driver's DispatchDeviceControl
routine can frequently return control to the system as follows:

: :
return IoCallDriver(DeviceObject->NextDeviceObject, Irp);

However, a higher-level driver cannot use the preceding technique if a lower driver might return
STATUS_PENDING for such a request. In that case, the higher-level driver should call IoSetCompletionRoutine
to register an IoCompletion routine. When the IoCompletion routine is called, it can check the I/O status block to
determine whether the IRP is still pending. If it is, the IoCompletion routine might retry the request or, possibly, call
IoMarkIrpPending before it calls IoCompleteRequest and returns STATUS_PENDING. A higher-level driver
must not complete an IRP with STATUS_PENDING unless it has called IoMarkIrpPending for that IRP first.
If the underlying device driver must process much data transferred from the device before it completes the request,
then a higher-level driver might handle such a device control request asynchronously. That is, the higher-level
driver might call IoSetCompletionRoutine to register an IoCompletion routine, pass the IRP on to lower drivers,
and return control from its own DispatchDeviceControl routine.
Almost all system-defined I/O control codes require the underlying device driver to transfer only modest amounts
of data, usually much less than a PAGE_SIZE amount. As a general rule, higher-level drivers should handle these
requests synchronously, as shown in the preceding code fragment, because the lower drivers return control so
quickly. That is, the overhead of calling the higher-level driver's IoCompletion routine does not compensate for
whatever additional IRP processing that driver can get done in such a short interval.
A higher-level driver that allocates IRPs with IoBuildDeviceIoControlRequest for an underlying device driver
can handle these device control requests synchronously. The higher-level driver can wait for an optional event
object to be passed to IoBuildDeviceIoControlRequest and associated with the driver-allocated IRP.
Dispatch(Internal)DeviceControl in Class/Port Drivers
6/25/2019 • 2 minutes to read • Edit Online

The higher-level driver of a class/port pair can sometimes complete IRPs in its DispatchDeviceControl routine. For
example a class driver could, during initialization, gather and store information about the features of the underlying
device, which might be sought in a subsequent IRP_MJ_DEVICE_CONTROL request, and thus save processing
time by satisfying the request without passing it on to the underlying device driver. A class driver might also be
designed to check the IRP's parameters and send only requests with valid parameters to the port driver.
Closely coupled class/port drivers also can define a set of driver-specific or device-specific internal I/O control
codes that the class driver can use for IRP_MJ_INTERNAL_DEVICE_CONTROL requests to the port driver.
For example, the DispatchCreateClose routines in the system keyboard and mouse class drivers send system-
defined internal device control requests to enable or disable keyboard and mouse interrupts to the underlying port
drivers. These system class drivers set up IRP_MJ_INTERNAL_DEVICE_CONTROL requests for an underlying
port driver. Any new keyboard or mouse port driver that interoperates with these system class drivers also must
support these public internal device control requests.
The system parallel class/port driver model has similar features. New parallel class drivers can get support from
the system parallel port driver by setting up IRPs for IRP_MJ_INTERNAL_DEVICE_CONTROL requests with
public IOCTL_PARALLEL_PORT_XXX control codes. You can replace the system parallel port driver, but any new
driver also must support this set of public internal device control requests.
For more information about these public internal device control requests, see device-specific documentation in the
Windows Driver Kit (WDK). For information about how to define private I/O control codes, see Using I/O Control
Codes.
For a closely coupled pair of port/class drivers, the class driver might handle the processing of certain device
control requests without passing them on to the port driver. In a new class/port driver pair, the class driver's
DispatchDeviceControl routine can do either of the following:
Check the validity of the parameters in its own I/O stack location, set the I/O status block if it finds any
parameter errors, and call IoCompleteRequest with a PriorityBoost of IO_NO_INCREMENT; otherwise,
call IoGetNextIrpStackLocation copy its own I/O stack location into the port driver's, and pass the IRP on
with IoCallDriver.
Or, do nothing more than set up the port driver's I/O stack location in the IRP without checking parameters
and pass it on to the port driver for processing.
SCSI class drivers have special requirements for handling device control requests. For more information about
these requirements, see Storage Drivers.
Guidelines for Writing
Dispatch(Internal)DeviceControl Routines
6/25/2019 • 2 minutes to read • Edit Online

Keep the following points in mind when writing a DispatchDeviceControl or DispatchInternalDeviceControl


routine:
At a minimum, a higher-level driver must copy the parameters for an IRP_MJ_DEVICE_CONTROL or
IRP_MJ_INTERNAL_DEVICE_CONTROL request from its own I/O stack location in the IRP to the next-lower-
level driver's I/O stack location. Then, it must call IoCallDriver with a pointer to the next-lower driver's device
object and the IRP.
The higher-level driver should propagate the status value returned by IoCallDriver or set it in the returned IRP's
I/O status block when it returns control for a request that lower drivers handle synchronously.
The underlying device driver must process device control requests unless it has a closely coupled class driver that
completes a subset of these requests on its behalf. A device driver's DispatchDeviceControl routine usually begins
processing these requests by turning on the Parameters.DeviceIoControl.IoControlCode in its I/O stack
location of each IRP.
A lower-level device driver should check the parameters passed in with the request and fail the IRP with an
appropriate error if any parameter is invalid. The most common check on the validity of parameters to these
requests has the form:

if (Irp->Parameters.DeviceIoControl.InputBufferLength <
(sizeof(IOCTL_SPECIFIC_STRUCTURE))) {
status = STATUS_XXX;

or

if (Irp->Parameters.DeviceIoControl.OutputBufferLength <
(sizeof(IOCTL_SPECIFIC_STRUCTURE))) {
status = STATUS_XXX;

where the status value set is one of STATUS_BUFFER_TOO_SMALL or STATUS_INVALID_PARAMETER. Every


device driver's DispatchDeviceControl or DispatchInternalDeviceControl routine must handle the receipt of an
unrecognized I/O control code by setting the I/O status block with an appropriate NTSTATUS value, setting its
Information field to zero, and completing the IRP with a PriorityBoost of IO_NO_INCREMENT.
The particular I/O control codes a device driver handles must include any device-type-specific, system-defined I/O
control codes for the same type of device. See the device-specific sections of the Windows Driver Kit (WDK) for
more information about the system requirements for each type of device and the corresponding (Windows SDK)
header files, each beginning with the prefix ntdd, for declarations of the system-defined structures for these I/O
control codes.
The class driver of a closely coupled class/port driver pair can process and complete a subset of device control
requests without passing them on to the underlying port driver. However, such a class driver must pass on all valid
device control requests that require a change of state for the device and those that require the return of volatile
information about the device, such as its current baud rate, volume, or video mode.
DispatchPnP Routines
6/25/2019 • 4 minutes to read • Edit Online

A driver's DispatchPnP routine supports Plug and Play by handling IRPs for the IRP_MJ_PNP I/O function code.
Associated with the IRP_MJ_PNP function code are several minor I/O function codes (see Plug and Play Minor
IRPs), some of which all drivers must handle and some of which can be optionally handled. The PnP manager uses
these minor function codes to direct drivers to start, stop, and remove devices and to query drivers about their
devices.
All drivers for a device must have the opportunity to handle PnP IRPs for the device, except in a few cases where a
function or filter driver is allowed to fail the IRP.
Each driver's DispatchPnP routine must follow these rules:
A function or filter driver must pass PnP IRPs down to the next driver in the device stack, unless the
function or filter driver handles the IRP and encounters a failure (due to insufficient resources, for example).
All drivers for a device must have the opportunity to handle PnP IRPs for the device unless one of the
drivers encounters an error. The PnP manager sends IRPs to the top driver in a device stack. Function and
filter drivers pass the IRP down to the next driver, and the parent bus driver completes the IRP. See Passing
PnP IRPs Down the Device Stack for more information.
A driver can fail an IRP if it tries to handle the IRP and encounters an error (such as insufficient resources).
If a driver receives an IRP with a code it does not handle, the driver must not fail the IRP. It must pass such
an IRP down to the next driver without modifying the IRP's status.
A driver must handle certain PnP IRPs and may optionally handle others.
Each PnP driver is required to handle certain IRPs, such as IRP_MN_REMOVE_DEVICE, and can
optionally handle others. See Plug and Play Minor IRPs for information about which IRPs are required and
optional for each kind of driver (function drivers, filter drivers, and bus drivers).
A driver can fail a required PnP IRP with an appropriate error status, but a driver must not return
STATUS_NOT_SUPPORTED for such an IRP.
If a driver handles a PnP IRP successfully, the driver sets the IRP status to success. It does not depend on
another driver in the stack to set the status.
A driver sets Irp->IoStatus.Status to STATUS_SUCCESS to inform the PnP manager that the driver
handled the IRP successfully. For some IRPs, a non-bus driver might be able to rely on its parent bus driver
to set the status to success. However, this is a risky practice. For consistency and robustness, a driver must
set the IRP status to success for each PnP IRP it handles successfully.
If a driver fails an IRP, the driver completes the IRP with an error status and does not pass the IRP down to
the next driver.
To fail an IRP like IRP_MN_QUERY_STOP_DEVICE, a driver sets Irp->IoStatus.Status to
STATUS_UNSUCCESSFUL. Additional error status values for other IRPs include
STATUS_INSUFFICIENT_RESOURCES and STATUS_INVALID_DEVICE_STATE.
Drivers do not set STATUS_NOT_SUPPORTED for IRPs that they handle. This is the initial status set by the
PnP manager. If an IRP is completed with this status, it means that no drivers in the stack handled the IRP;
all drivers just passed the IRP to the next driver.
A driver must handle a PnP IRP in its dispatch routine (on the IRP's way down the device stack), in an
IoCompletion routine (on the IRP's way back up the device stack), or both, as specified in the reference
page for the IRP.
Some PnP IRPs, such as IRP_MN_REMOVE_DEVICE, must be handled first by the driver at the top of the
device stack and then by each next-lower driver. Others, such as IRP_MN_START_DEVICE, must be
handled first by the parent bus driver. Still others, such as IRP_MN_QUERY_CAPABILITIES, can be
handled both on the way down the device stack and the way back up. See Plug and Play Minor IRPs for the
rules that apply to each PnP IRP. See Postponing PnP IRP Processing Until Lower Drivers Finish For
information about handling PnP IRPs that must be processed first by the parent bus driver.
A driver must add information to an IRP on the IRP's way down the device stack and modify or remove
information on the IRP's way back up.
When returning information in response to a PnP query IRP, a driver must follow this convention to enable
orderly information passing by the layered drivers for a device.
Except where explicitly documented, a driver must not depend on PnP IRPs being sent in any particular
order.
When a driver sends a PnP IRP, it must send the IRP to the top driver in the device stack.
Most PnP IRPs are sent by the PnP manager, but some can be sent by drivers (for example,
IRP_MN_QUERY_INTERFACE ). A driver must send a PnP IRP to the driver at the top of the device stack.
Call IoGetAttachedDeviceReference to get a pointer to the device object for the driver at the top of the
device stack.
You should test your drivers with a checked build of the operating system. The checked build of the system verifies
whether a driver follows many of the PnP rules listed above.
DispatchPower Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchPower routine supports power management by handling IRPs for the IRP_MJ_POWER I/O
function code. Associated with the IRP_MJ_POWER function code are several minor I/O function codes for
Power Management. The power manager uses these minor function codes to direct drivers to change power
states, to wait for and respond to system wake-up events, and to query drivers about their devices.
Each driver's DispatchPower routine performs the following tasks:
Handle the IRP if possible.
Pass the IRP to the next lower driver in the device stack, using PoCallDriver.
If a bus driver, perform the requested power operation on the device and complete the IRP.
All drivers for a device must have the opportunity to handle power IRPs for the device, except in a few cases where
a function or filter driver is allowed to fail the IRP. Most function and filter drivers either perform some processing
or set an IoCompletion routine for each power IRP, then pass the IRP down to the next lower driver without
completing it. Eventually the IRP reaches the bus driver, which physically changes the power state of the device if
required and completes the IRP.
When the IRP has been completed, the I/O manager calls any IoCompletion routines set by drivers as the IRP
traveled down the device stack. Whether a driver needs to set a completion routine depends upon the type of IRP
and the driver's individual requirements.
Power IRPs that power up a device must be handled first by the lowest driver in the device stack (the underlying
bus driver) and then by each successive driver up the stack. Power IRPs that power down a device must be
handled first by the driver at the top of the device stack and then by each successive driver going down the stack.
Special Handling for Removable Devices
In their DispatchPower routines, drivers of removable devices should check to see whether the device is still
present. If the device has been removed, the driver should not pass the IRP down to the next lower driver. Instead,
the driver should do the following:
Call PoStartNextPowerIrp to begin processing the next power IRP.
Set Irp->IoStatus.Status to STATUS_DELETE_PENDING.
Call IoCompleteRequest, specifying IO_NO_INCREMENT, to complete the IRP.
Return STATUS_DELETE_PENDING.
DispatchQueryInformation Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchQueryInformation routine handles IRPs for the IRP_MJ_QUERY_INFORMATION I/O


function code. Driver support for this I/O function code is optional, and typically appears in higher-level or file
system drivers. This request is sent by the I/O manager and other operating system components, as well as other
kernel-mode drivers. For example, it is sent when a user-mode application calls GetFileInformationByHandle,
and when a kernel-mode component calls ZwQueryInformationFile.
DispatchSetInformation Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchSetInformation routine handles IRPs for the IRP_MJ_SET_INFORMATION I/O function code.
Driver support for this I/O function code is optional, and typically appears in higher-level or file system drivers.
This request is sent by the I/O manager and other operating system components, as well as other kernel-mode
drivers. For example, it is sent when a user-mode application calls SetEndOfFile, and when a kernel-mode
component calls ZwSetInformationFile.
DispatchFlushBuffers Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchFlushBuffers routine handles IRPs for the IRP_MJ_FLUSH_BUFFERS I/O function code.
Driver support for this I/O function code is optional, but all file system and filter drivers that maintain internal data
buffers must handle it to preserve changes to file data or metadata across system shutdowns. This request is sent
by the I/O manager and other operating system components, as well as other kernel-mode drivers, when buffered
data needs to be flushed to disk. For example, it is sent when a user-mode application calls FlushFileBuffers.
DispatchShutdown Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchShutdown routine handles IRPs for the IRP_MJ_SHUTDOWN I/O function code. Drivers of
mass-storage devices that have internal caches for data must handle this request. Drivers of mass-storage devices
and intermediate drivers layered over them also must handle this request if an underlying driver maintains internal
buffers for data.
DispatchSystemControl Routines
6/25/2019 • 2 minutes to read • Edit Online

A driver's DispatchSystemControl routine handles IRPs for the IRP_MJ_SYSTEM_CONTROL I/O function code.
All drivers must provide a DispatchSystemControl routine. The purpose of this routine is to provide support for
Windows Management Instrumentation (WMI). Regardless of whether a driver supports WMI, this routine must
pass the IRP to the next-lower driver.
To learn how to implement a DispatchSystemControl routine, and how to support WMI in general, see Windows
Management Instrumentation.
Writing an Unload Routine
6/25/2019 • 2 minutes to read • Edit Online

Any driver that can be replaced, or unloaded and reloaded, while the system is running must have an Unload
routine. All WDM drivers must have Unload routines.
Although Unload routines are optional for non-WDM drivers, Driver Verifier will fail any driver that does not
provide an Unload routine.
This section contains the following topics:
Unload Routine Environment
Unload Routine Functionality
Unload Routine Environment
6/25/2019 • 2 minutes to read • Edit Online

The operating system unloads a driver when the driver is being replaced or when all of the devices that the driver
services have been removed. The PnP manager calls a PnP driver's Unload routine if the driver has no more
device objects after it handles an IRP_MN_REMOVE_DEVICE request.
At the start of the unloading sequence, the I/O manager or PnP manager marks the driver object and its device
objects as "Unload Pending". After a driver has been marked as "Unload Pending", no additional drivers can attach
to that driver, nor can any additional references be made to the driver's device objects. The driver can complete
outstanding IRPs, but the system will not send any new IRPs to the driver.
The I/O manager calls a driver's Unload routine when all of the following are true:
No references remain to any of the device objects the driver has created. In other words, no files associated
with the underlying device can be open, nor can any IRPs be outstanding for any of the driver's device
objects.
No other drivers remain attached to this driver.
The driver has called IoUnregisterPlugPlayNotification to unregister all PnP notifications for which it
previously registered.
Note that the Unload routine is not called if a driver's DriverEntry routine returns a failure status. In this case, the
I/O manager simply frees the memory space taken up by the driver.
Neither the PnP manager nor the I/O manager calls Unload routines at system shutdown time. A driver that must
perform shutdown processing should register a DispatchShutdown routine.
Unload Routine Functionality
6/25/2019 • 2 minutes to read • Edit Online

The responsibilities of a driver's Unload routine depend on whether the driver supports PnP or not.
Just as the DriverEntry routines of PnP drivers are usually simple, so are their Unload routines, as described in A
PnP Driver's Unload Routine.
A non-PnP driver's Unload routine must free device objects and release driver-allocated resources. In short, it
must undo the work performed by its corresponding DriverEntry and Reinitialize routines in initializing the driver,
its devices, and its resources. See A Non-PnP Driver's Unload Routine for details.
PnP Driver's Unload Routine
6/25/2019 • 2 minutes to read • Edit Online

A PnP driver must have an Unload routine that removes any driver-specific resources, such as memory, threads,
and events, that are created by the DriverEntry routine. If there are no driver-specific resources to remove, the
driver must still have an Unload routine but it can simply return.
A driver's Unload routine can be called at any time after all the driver's devices have been removed. The PnP
manager calls a driver's Unload routine in the context of a system thread at IRQL = PASSIVE_LEVEL.
PnP drivers free device-specific resources and device objects in response to PnP device-removal IRPs. The PnP
manager sends these IRPs on behalf of each PnP device it enumerates as well as any root-enumerated legacy
devices a driver reports using IoReportDetectedDevice.
Consequently, the Unload routines of PnP drivers are usually simple, often consisting only of a return statement.
However, if the driver allocated any driver-wide resources in its DriverEntry routine, it must deallocate those
resources in its Unload routine unless it has already done so. In general, the process of unloading a PnP driver is a
synchronous operation.
The I/O manager frees the driver object and any driver object extension that the driver allocated using
IoAllocateDriverObjectExtension.
Non-PnP Driver's Unload Routine
6/25/2019 • 2 minutes to read • Edit Online

Earlier drivers and high-level file system drivers, which do not handle PnP device-removal requests, must release
resources, delete device objects, and detach from the device stack in their Unload routines.
If it has not done so already, the first thing a legacy device driver should do in its Unload routine is to disable
interrupts from the device. Otherwise, its ISR might be called to handle a device interrupt while the Unload routine
is releasing resources in the device extension that the ISR needs to handle the interrupt. Even if its ISR runs
successfully in these circumstances, the DpcForIsr or CustomDpc routine that the ISR queues, and possibly other
driver routines that run at IRQL >= DISPATCH_LEVEL, will execute before the Unload routine regains control,
thereby increasing the likelihood that the Unload routine has deleted a resource that another driver routine
references. See Managing Hardware Priorities for more information.
After disabling interrupts, file system and legacy drivers must release resources and objects. For details, see the
following two sections:
Releasing Driver-Allocated Resources
Releasing Device and Controller Objects
Releasing Driver-Allocated Resources
6/25/2019 • 3 minutes to read • Edit Online

The specifics of how a driver uses the registry, sets up system objects and resources in its device extensions,
controller extension, or driver-allocated nonpaged pool varies from driver to driver. However, any Unload routine
must release the resources a driver is using in stages.
Any driver's Unload routine must ensure that no other driver routine is currently using or might shortly be using a
particular resource before it releases that resource.
In general, an Unload routine releases all driver-allocated resources in the following stages:
1. If the driver has not already done so, disable interrupts on any physical devices, if possible, and then call
IoDisconnectInterrupt as soon as interrupts are disabled.
2. Ensure that no other driver routine can reference the resources that the Unload routine intends to release.
For example, an Unload routine must call IoStopTimer if the driver's IoTimer routine is currently enabled
for a particular device object. It must ensure that no thread is waiting for any of the driver's dispatcher
objects and that its timer objects are not queued for calls to its CustomTimerDpc routines before it frees the
storage for its dispatcher objects. It must call KeRemoveQueueDpc if it has a CustomDpc routine that the
ISR might have queued, and so on.
If the driver called IoQueueWorkItem, it must ensure that the work item has completed.
IoQueueWorkItem takes out a reference on the associated device object; the driver cannot be unloaded if
any such references remain.
If the driver called PsCreateSystemThread, the Unload routine also must cause the driver-created thread
to be run so that the thread itself can call PsTerminateSystemThread before the driver is unloaded. A
driver cannot release a driver-created system thread by calling ZwClose with the ThreadHandle returned
by PsCreateSystemThread.
3. Release any device-specific resources that the driver allocated. Doing so might involve calling the following
system support routines:
IoDeleteSymbolicLink if the DriverEntry or Reinitialize routine called IoCreateSymbolicLink or
IoCreateUnprotectedSymbolicLink, and IoDeassignArcName if the driver called
IoAssignArcName.
ExFreePool if DriverEntry or any other driver routine called ExAllocatePoolWithTag and the
driver has not yet released the allocated memory.
MmUnmapIoSpace if the DriverEntry or Reinitialize routine called MmMapIoSpace.
MmFreeNonCachedMemory if the DriverEntry or Reinitialize routine called
MmAllocateNonCachedMemory.
MmFreeContiguousMemory if the DriverEntry or Reinitialize routine called
MmAllocateContiguousMemory.
FreeCommonBuffer if the DriverEntry or Reinitialize routine called AllocateCommonBuffer.
IoAssignResources or IoReportResourceUsage if the DriverEntry or Reinitialize routine called
one of these support routines or HalAssignSlotResources to claim hardware resources in the
configuration registry for itself and/or for its physical devices individually.
4. Release system objects and resources that the DriverEntry or Reinitialize routine set up in the device
extension of the device objects or in the controller extension of the controller object (if it created one). In
particular, the driver must do the following before it attempts to delete the device object (IoDeleteDevice)
or controller object (IoDeleteController):
Call IoDisconnectInterrupt to free the interrupt object pointer stored in the corresponding device or
controller extension.
Call ObDereferenceObject with the pointer to the next-lower driver's file object if it called
IoGetDeviceObjectPointer and stored this pointer in a device or controller extension.
Call IoDetachDevice with the pointer to the lower driver's device object if it called IoAttachDevice or
IoAttachDeviceToDeviceStack and stored this pointer in a device or controller extension.
5. Free the hardware resources that the DriverEntry or Reinitialize routine claimed for the driver's physical
devices, if any, in the registry under the \Registry\Machine\Hardware\ResourceMap tree.
6. Remove any names for its devices that the DriverEntry or Reinitialize routine stored in the registry under
the \Registry..\DeviceMap tree, as well.
After the driver has released device, system, and hardware resources, it can delete its device and controller objects,
as described in the following section.
Releasing Device and Controller Objects
6/25/2019 • 2 minutes to read • Edit Online

Before a driver deletes a device or controller object, it must release its references to external resources, such as
pointers to other drivers' objects and/or to interrupt objects, that it stored in the corresponding device or controller
extension. It can then call IoDeleteDevice for each device object that the driver created. A non-WDM driver that
previously called IoCreateController must also call IoDeleteController.
Any Kernel-defined object for which the driver provides storage in a device extension is automatically freed when
the Unload routine calls IoDeleteDevice with the corresponding device object. In general, any object that the
DriverEntry or Reinitialize routine set up by calling KeInitializeXxx can be freed by a call to IoDeleteDevice if
the driver provided storage for that object in its device extension. For example, if a driver has a CustomTimerDpc
routine and has provided storage for the necessary DPC and timer objects in its device extension, the call to
IoDeleteDevice releases these system resources.
Similarly, any Kernel-defined object for which the driver provides storage in a controller extension is automatically
freed when the Unload routine calls IoDeleteController with the corresponding controller object.
If the DriverEntry or Reinitialize routine called IoGetConfigurationInformation to increment the count for a
particular type of device, the Unload routine also must call IoGetConfigurationInformation and decrement the
count for its devices in the I/O manager's global configuration information structure as it deletes the
corresponding device objects.
Before it returns control, an Unload routine also is responsible for freeing any other driver-allocated resources that
have not yet been freed by other driver routines.
Introduction to Device Objects
6/25/2019 • 2 minutes to read • Edit Online

The operating system represents devices by device objects. One or more device objects are associated with each
device. Device objects serve as the target of all operations on the device.
Kernel-mode drivers must create at least one device object for each device, with the following exceptions:
Minidrivers that have an associated class or port driver do not have to create their own device objects. The
class or port driver creates the device objects, and dispatches operations to the minidriver.
Drivers that are part of device type-specific subsystems, such as NDIS miniport drivers, have their device
objects created by the subsystem.
See the documentation for your particular device type to determine if your driver creates its own device objects.
Some device objects do not represent physical devices. A software-only driver, which handles I/O requests but
does not pass those requests to hardware, still must create a device object to represent the target of its operations.
For more information about how your driver can create device objects, see Creating a Device Object.
Devices are usually represented by multiple device objects, one for each driver in the driver stack that handles I/O
requests for the device. The device objects for a device are organized into a device stack. Whenever an operation is
performed on a device, the system passes an IRP data structure to the driver for the top device object in the device
stack. Each driver either handles the IRP or passes it to the driver that is associated with the next-lower device
object in the device stack. For more information about device stacks, see WDM Device Objects and Device Stacks.
For more information about IRPs, see Handling IRPs.
Device objects are represented by DEVICE_OBJECT structures, which are managed by the object manager. The
object manager provides the same capabilities for device objects that it does for other system objects. In particular,
a device object can be named, and a named device object can have handles opened on it. For more information
about named device objects, see Named Device Objects.
The system provides dedicated storage for each device object, called the device extension, which the driver can use
for device-specific storage. The device extension is created and freed by the system along with the device object.
For more information, see Device Extensions.
The following figure illustrates the relationship between device objects and the I/O manager.
The figure shows the members of the DEVICE_OBJECT structure that are of interest to a driver writer. For more
information about these members, see Creating a Device Object, Initializing a Device Object, and Properties of
Device Objects.
Types of WDM Device Objects
6/25/2019 • 2 minutes to read • Edit Online

There are three kinds of WDM device objects:


1. Physical Device Object (PDO ) – represents a device on a bus to a bus driver.
2. Functional Device Object (FDO ) – represents a device to a function driver.
3. Filter Device Object (filter DO ) – represents a device to a filter driver.
The three kinds of device objects are all of the type DEVICE_OBJECT, but are used differently and can have
different device extensions.
A driver adds itself to the stack of drivers that handle I/O for a device by creating a device object (IoCreateDevice)
and attaching it to the device stack (IoAttachDeviceToDeviceStack). IoAttachDeviceToDeviceStack
determines the current top of the device stack and attaches the new device object to the top of the device stack.
Example WDM Device Objects
6/25/2019 • 2 minutes to read • Edit Online

The following figure illustrates the device objects that represent the keyboard and mouse devices shown previously
in the figure illustrating Keyboard and Mouse Hardware Configurations. The keyboard and mouse drivers shown in
the figure illustrating Keyboard and Mouse Driver Layers create these device objects by calling an I/O support
routine (IoCreateDevice).

For the keyboard and mouse devices, both their respective port and class drivers create device objects. The port
driver creates a physical device object (PDO ) to represent the physical port. Each class driver creates its own
functional device object (FDO ) to represent the keyboard or mouse device as a target for I/O requests.
Each class driver calls an I/O support routine to get a pointer to the next-lower-level driver's device object, so the
class driver can chain itself above that driver, which is the port driver. Then the class driver can send I/O requests
down to the port driver for the target PDO representing its physical device.
An optional filter driver added to the configuration would create a filter device object (filter DO ). Like the class
driver, an optional filter driver chains itself to the next-lower driver in the device stack and sends I/O requests for
the target PDO down to the next-lower driver.
As shown previously in the Keyboard and Mouse Driver Layers figure, each port driver is a bus (lowest-level)
driver, so every port driver of a device that generates interrupts must set up interrupt object(s) and register an ISR.
A dual-device port driver, like the i8042 driver for the keyboard and auxiliary device controller shown in the
Keyboard and Mouse Hardware Configurations if each device uses a different interrupt vector. When writing such a
driver, you can either implement separate ISRs for each device or implement a single ISR for both devices.
When Are WDM Device Objects Created?
6/25/2019 • 3 minutes to read • Edit Online

This section describes each kind of device object and mentions when each is created.
The following figure shows the possible kinds of device objects that can be attached in a device stack, representing
the drivers handling I/O requests for a device.

Starting at the bottom of this figure:


A bus driver creates a PDO for each device that it enumerates on its bus.
A bus driver creates a PDO for a child device when it enumerates the device. A bus driver enumerates a
device in response to an IRP_MN_QUERY_DEVICE_RELATIONS request for BusRelations from the PnP
manager. The bus driver creates a PDO for a child device if the device has been added to the bus since the
last time the bus driver responded to a query-relations request for BusRelations (or if this is the first query-
relations request since the machine was booted).
A PDO represents the device to the bus driver, as well as to other kernel-mode system components such as
the power manager, the PnP manager, and the I/O manager.
Other drivers for a device attach device objects on top of the PDO, but the PDO is always at the bottom of
the device stack.
Optional bus filter drivers create filter DOs for each device they filter.
When the PnP manager detects a new device in a BusRelations list, it determines whether there are any
bus filter drivers for the device. If so, for each such driver the PnP manager ensures it is loaded (calls
DriverEntry if necessary) and calls the driver's AddDevice routine. If the bus filter driver filters operations
for this device, the filter driver creates a device object and attaches it to the device stack in its AddDevice
routine. If more than one bus filter driver exists and is relevant to this device, each such filter driver creates
and attaches its own device object.
Optional, lower-level filter drivers create filter DOs for each device they filter.
If an optional, lower-level filter driver exists for this device, the PnP manager ensures that such a driver is
loaded after the bus driver and any bus filter drivers. The PnP manager calls the filter driver's AddDevice
routine. In its AddDevice routine, the lower-level filter driver creates a filter DO for the device and attaches it
to the device stack. If more than one lower-level filter driver exists, each such driver would create and attach
its own filter DO.
The function driver creates an FDO for the device.
The PnP manager ensures that the function driver for the device is loaded and calls the function driver's
AddDevice routine. The function driver creates an FDO and attaches it to the device stack.
Optional, upper-level filter drivers create a filter DO for each device they filter.
If any optional, upper-level filter drivers exist for the device, the PnP manager ensures they are loaded after
the function driver and calls their AddDevice routines. Each such filter driver attaches its device object to the
device stack.
In summary, the device stack contains a device object for each driver that is involved in handling I/O to a particular
device. The parent bus driver has a PDO, the function driver has an FDO, and each optional filter driver has a filter
DO.
Note that all devices, bus adapter/controller devices and nonbus devices, have a PDO and an FDO in their device
stack. The PDO for a bus adapter/controller is created by the bus driver for the parent bus. For example, if a SCSI
adapter plugs into a PCI bus, the PCI bus driver creates a PDO for the SCSI adapter.
If a device is being used in raw mode, there are no function or filter drivers (no FDO or filter DOs). There is just a
PDO for the parent bus driver and zero or more bus filter DOs.
See Creating a Device Object for information about which driver routines are responsible for creating and
attaching device objects.
The device stack plus some additional information constitutes the devnode for a device. The PnP manager
maintains information in a device's devnode such as whether the device has been started and which drivers, if any,
have registered for notification of changes on the device. The kernel debugger command !devnode displays
information about a devnode.
Example WDM Device Stack
6/25/2019 • 3 minutes to read • Edit Online

This section describes the device objects created by a possible set of drivers for USB hardware to illustrate WDM
device objects and how they are layered.
The following figure shows the device objects that are created by the sample drivers described in WDM Driver
Layers: An Example.

Starting at the bottom of this figure, the device objects in the sample device stacks include:
1. A PDO and an FDO for the PCI bus.
The root bus driver enumerates the internal system bus (the root bus) and creates a PDO for each device it
finds. One of these PDOs is for the PCI bus. (The PDO and FDO for the root bus are not shown in the
figure.)
The PnP manager identifies the PCI driver as the function driver for the PCI bus, loads the driver (if it is not
already loaded), and passes the PDO to the PCI driver. In its AddDevice routine, the PCI driver creates an
FDO for the PCI bus (IoCreateDevice) and attaches the FDO to the device stack
(IoAttachDeviceToDeviceStack) for the PCI bus. The PCI driver creates and attaches this FDO as part of
its responsibilities as the function driver for the PCI bus.
There are no filter drivers for the PCI bus in this example.
2. A PDO and an FDO for the USB host controller.
The PnP manager directs the PCI driver to start its device ( IRP_MN_START_DEVICE ) and then queries the
PCI driver for its children (IRP_MN_QUERY_DEVICE_RELATIONS with relation type of BusRelations).
In response, the PCI driver enumerates the devices on its bus. In this example, the PCI driver finds a USB
host controller and creates a PDO for that device. The wide arrow in the figure indicates that the USB host
controller is a "child" of the PCI bus. The PCI driver creates PDOs for its child devices as part of its
responsibilities as the bus driver for the PCI bus.
The PnP manager identifies the USB host controller miniclass/class driver pair as the function driver for the
USB host controller and loads the driver pair. The PnP manager calls the driver pair at the appropriate time
to create and attach an FDO for the USB host controller.
There are no filter drivers for the USB host controller in this example.
3. A PDO and an FDO for the USB hub.
The USB host controller enumerates its bus, locates the USB hub in the sole port, and creates a PDO for the
hub. The USB hub driver creates and attaches an FDO for the hub.
There are no filter drivers for the USB hub in this example.
4. A PDO, an FDO, and two filter DOs for the joystick device.
The USB hub driver enumerates its bus, locates a HID device (the joystick), and creates a PDO for the
joystick.
In this example, a lower-level filter driver has been set up in the registry for joystick devices, so the PnP
manager loads the filter driver. The filter driver determines that it is relevant to the device and creates and
attaches a filter DO to the device stack.
The PnP manager determines that the function driver for the joystick device is the HID class/miniclass driver
pair and loads those drivers. The driver pair consists of a miniclass driver linked to a class driver DLL;
together they act as one function driver for the device. The class/miniclass driver pair creates one device
object, the FDO, and attaches it to the device stack.
An upper-level filter driver creates and attaches a filter DO to the device stack, in a manner similar to the
lower-level filter.
Note that the PDO created by the parent bus driver is always at the bottom of the device stack for a particular
device. When drivers handle PnP or power IRPs, they must pass each IRP all the way down the device stack to the
PDO and its associated bus driver.
The following figure shows the same device stacks as the previous figure, but emphasizes which device objects are
created and managed by which drivers.
A bus driver spans more than one device stack. A bus driver creates the FDO for its bus adapter/controller and
creates a PDO for each of its child devices.
Creating a Device Object
6/25/2019 • 3 minutes to read • Edit Online

A monolithic driver must create a device object for each physical, logical, or virtual device for which it handles I/O
requests. A driver that does not create a device object for a device does not receive any IRPs for the device.
In some technology areas, a minidriver that is associated with a class driver or port driver does not have to create
its own device objects. Instead, the class or port driver creates the device object, and receives all IRPs for the
device. The class or port driver then uses a driver-specific method to pass the I/O request to the minidriver. See
the documentation for your particular technology area to determine if Microsoft supplies a class or port driver
that creates device objects on behalf of your driver.
Drivers call either IoCreateDevice or IoCreateDeviceSecure to create their device objects. For more
information about which routine to use, see the following sections.
Creating Device Objects for WDM Function and Filter Drivers
Creating Device Objects for WDM Bus Drivers
Creating Device Objects for Non-WDM Drivers
When the driver creates a device object, it supplies the following information to IoCreateDevice or
IoCreateDeviceSecure:
The size of the device's device extension. The device extension is a system-allocated storage area that the
driver can use for device-specific storage. For more information, see Device Extensions.
A system-defined constant, indicating the DeviceType represented by the device object. For more
information, see Specifying Device Types.
One or more ORed, system-defined constants that indicate the device characteristics for the device. For
more information, see Specifying Device Characteristics.
A Boolean value, named Exclusive, that specifies whether a bit in the device object's Flags should be set
with DO_EXCLUSIVE, indicating the driver services an exclusive device, such as a video, serial, parallel, or
sound device. WDM drivers must set Exclusive to FALSE. For more information, see Specifying Exclusive
Access to Device Objects.
A pointer to the driver object for the driver. A WDM function or filter driver receives a pointer to its driver
object as a parameter to its AddDevice routine. All drivers receive a pointer to their driver object in their
DriverEntry routine. The system uses this pointer to associate the driver with its device object.
An optional pointer to a null-terminated Unicode string (DeviceName) naming the device. WDM drivers,
other than bus drivers, do not supply a device name; doing so bypasses the PnP manager's security
features. For more information, see Named Device Objects.
If the call to IoCreateDevice or IoCreateDeviceSecure succeeds, the I/O manager provides storage for the
device object itself and for all other data structures associated with the device object, including the device
extension, which it initializes with zeros.
Creating Device Objects for WDM Function and Filter Drivers
WDM drivers, other than bus drivers, call IoCreateDevice to create their device objects. Most WDM drivers
create their device objects from within their AddDevice routines. Some drivers, such as disk drivers that must
respond to drive layout IOCTLs, call IoCreateDevice from a dispatch routine.
Unless device type-specific sections of the Windows Driver Kit (WDK) documentation state otherwise, your driver
should create its device objects in its AddDevice routine. For more information, see Writing an AddDevice
Routine.
Creating Device Objects for WDM Bus Drivers
A WDM bus driver creates a PDO when it is enumerating a new device in response to an
IRP_MN_QUERY_DEVICE_RELATIONS request, if the relation type is BusRelations.
The following rules determine if a bus driver calls IoCreateDevice or IoCreateDeviceSecure to create a device
object:
If a device can be used in raw mode, then it must call IoCreateDeviceSecure.
If the device is not raw -mode capable, then the bus driver can use either IoCreateDevice or
IoCreateDeviceSecure. IoCreateDevice can be used when the default system security for devices on the
bus is adequate; IoCreateDeviceSecure can be used to specify a more stringent security descriptor. For
more information, see Controlling Device Access.
Creating Device Objects for Non-WDM Drivers
A non-WDM driver uses IoCreateDevice to create unnamed device objects, and IoCreateDeviceSecure to
create named device objects. Note the unnamed device objects of a non-WDM driver are not accessible from user
mode, so the driver usually must create at least one named object.
Initializing a Device Object
6/25/2019 • 3 minutes to read • Edit Online

After IoCreateDevice returns, giving the caller a pointer to a DeviceObject that contains a pointer to the device
extension, drivers must set up certain fields in the device objects for their respective physical, logical, and/or
virtual devices.
IoCreateDevice sets the StackSize field of a newly created device object to one. A lowest-level driver can ignore
this field. When a higher-level driver calls IoAttachDeviceToDeviceStack to attach itself to the next-lower driver,
that routine automatically sets the StackSize field in the device object to that of the next-lower driver's device
object plus one. For some device types, however, the higher-level driver might need to set the StackSize field to a
greater value, as noted in the device-specific documentation. Setting the stack size ensures that IRPs sent to the
higher-level driver will contain a driver-specific I/O stack location, plus the correct number of I/O stack locations
for all lower-level drivers in the chain.
IoCreateDevice sets the AlignmentRequirement field of a newly created device object to the processor's data
cache line size minus one, to ensure that buffers used in direct I/O are aligned correctly. After IoCreateDevice
returns, lowest-level physical device drivers must do the following:
1. Subtract one from the alignment requirement of the device.
2. Compare the result of step 1 with the current value of the device object's AlignmentRequirement.
3. If the device's alignment requirement is greater, setAlignmentRequirement to the result of step 1.
Otherwise, leave the AlignmentRequirement value as set by IoCreateDevice.
After any higher-level driver chains itself over another driver by calling IoGetDeviceObjectPointer, the higher-
level driver must set the AlignmentRequirement field of its newly created device object to that of the next-
lower-level driver's device object. As a general rule, a higher-level driver should not change this value. If a higher-
level driver calls IoAttachDevice or IoAttachDeviceToDeviceStack, those routines automatically set the
AlignmentRequirement field in the device object to that of the lower-level driver's device object.
IoGetDeviceObjectPointer returns pointers both to the lower-level driver's device object and to the associated
file object. Only an FSD (or, possibly, another highest-level driver) can use the returned file object pointer. An
intermediate driver that calls IoGetDeviceObjectPointer should save this file object pointer so it can be
dereferenced by calling ObDereferenceObject when the driver is unloaded.
After an FSD mounts the volume containing the file object that represents a lower driver's device object, an
intermediate driver cannot chain itself between the file system and the lower driver by calling IoAttachDevice or
IoAttachDeviceToDeviceStack. Additionally, an FSD can set the SectorSize member of the device object based
on the geometry of the underlying volume hardware when a mount occurs. For more information, see
DEVICE_OBJECT.
An intermediate or lowest-level driver also sets a bit in the device object's Flags by ORing it either with
DO_DIRECT_IO or with DO_BUFFERED_IO in every device object it creates. Highest-level drivers of logical or
virtual devices can avoid setting Flags for either buffered or direct I/O if the driver writer decides the additional
work involved will pay off in better driver performance. An intermediate driver must set up the Flags field of its
device object to match that of the next-lower driver's device object.
Setting up a device object Flags field with DO_DIRECT_IO or DO_BUFFERED_IO determines how the I/O
manager passes access to user buffers in all data transfer requests subsequently sent to the driver.
The driver can then set any other device-dependent values in the device object. For example, non-WDM drivers
for removable-media devices must OR the device object's Flags member with DO_VERIFY_VOLUME if they
detect (or suspect) a change in media during I/O operations. (See Supporting Removable Media for more
information.) Drivers of devices that require inrush power must OR the Flags member with
DO_POWER_INRUSH, and drivers of devices that are not on the system paging path must OR the Flags member
with DO_POWER_PAGABLE. Function and filter drivers must clear the DO_DEVICE_INITIALIZING flag.
After initializing the device object, a driver can also initialize any Kernel-defined objects and other system-defined
data structures for which it has provided storage in the device extension. Precisely when a driver performs these
tasks depends on its device, the type of the object, and/or the nature of the data. In general, any objects or data
structures that can persist through PnP start and stop requests can be initialized in the AddDevice routine. Those
that require resource information provided with a PnP IRP_MN_START_DEVICE request, or that might require
changes when the device is stopped and/or restarted, should be initialized when the driver handles the
IRP_MN_START_DEVICE request. For more information about AddDevice routines, see Writing an AddDevice
Routine.
Named Device Objects
6/25/2019 • 2 minutes to read • Edit Online

A device object, like all object manager objects, can be named or unnamed. When a user-mode application makes
an I/O request, it specifies the target of the operation by name. The object manager resolves the name to
determine the destination of the I/O request.

IMPORTANT
To help increase driver security name device objects only when necessary. Named device objects are generally only necessary
for legacy reasons, for example if you have an application that expects to open the device using a particular name or if you’re
using a non-PNP device/control device. Note that WDF drivers do not need to name their PnP device in order to create a
symbolic link using WdfDeviceCreateSymbolicLink.

A driver can specify a name for a device object when it calls IoCreateDevice or IoCreateDeviceSecure to create
the device object. For more information about when and how to name a device object, see NT Device Names.
A named device object can also have an MS -DOS device name, which is a symbolic link created by
IoCreateSymbolicLink or IoCreateUnprotectedSymbolicLink. WDM drivers do not in general require an
MS -DOS device name. For more information, see MS -DOS Device Names.

IMPORTANT
If you use a named device object you can use IoCreateDeviceSecure and specify a SDDL to help secure it. When you
implement IoCreateDeviceSecure always specify a custom class GUID for DeviceClassGuid. You should not specify an existing
class GUID here. Doing so has the potential to break security settings or compatibility for other devices belonging to that
class. For more information, see WdmlibIoCreateDeviceSecure.
In order to allow applications or other WDF drivers to access your PnP device, you should use device interfaces. For more
information, see Using Device Interfaces. A device interface serves as a symbolic link to your device stack’s PDO. Once way to
control access to the PDO is by specifying an SDDL string in your INF. If the SDDL string is not in the INF file, Windows will
apply a default security descriptor. For more information, see Securing Device Objects and SDDL for Device Objects.

This section contains the following subsections:


NT Device Names
MS -DOS Device Names
NT Device Names
6/25/2019 • 2 minutes to read • Edit Online

A named device object has a name of the form \Device\DeviceName. This is known as the NT device name of the
device object.
Device Names for WDM Drivers
WDM drivers do not name their device objects directly. Instead, the system imposes a uniform naming scheme
that ensures that device names do not conflict between drivers. The naming scheme for WDM drivers is as follows.
The PDO for a device is named. The bus driver requests named PDOs for the devices it enumerates. The
bus driver specifies the FILE_AUTOGENERATED_DEVICE_NAME device characteristic when it creates the
device object. For more information, see Specifying Device Characteristics. The system then automatically
generates the device name.
FDOs and filter DOs are not named. Function and filter drivers do not request a name when creating the
device object.
Any I/O request to a named device object automatically goes to the top object in that device object's stack. Thus,
only the PDO is required to be named. User-mode applications do not refer to WDM device objects by name;
instead, applications access the device object through its device interface. For more information, see Device
Interface Classes.
Driver writers must not name more than one object in a device stack. The operating system checks security
settings based on the named object. If two different objects are named and have different security descriptors, the
I/O requests that are sent to the object with the weaker security descriptor can reach the device object with the
stronger security descriptor.
Device Names for non-WDM Drivers
A non-WDM driver must explicitly specify a name for any named device objects. The driver must create at least
one named device object in the \Device object directory to receive I/O requests. The driver specifies the device
name as the DeviceName parameter to IoCreateDeviceSecure when creating the device object.
Introduction to MS-DOS Device Names
6/25/2019 • 2 minutes to read • Edit Online

A named device object that is created by a non-WDM driver typically has an MS -DOS device name. An MS -DOS
device name is a symbolic link in the object manager with a name of the form \DosDevices\DosDeviceName.
An example of a device with an MS -DOS device name is the serial port, COM1. It has the MS -DOS device name
\DosDevices\COM1. Likewise, the C drive has the name \DosDevices\C:.
WDM drivers do not usually supply MS -DOS device names for their devices. Instead, WDM drivers use the
IoRegisterDeviceInterface routine to register a device interface. The device interface specifies devices by their
capabilities, rather than by a particular naming convention. For more information, see Device Interface Classes.
Drivers are required to supply an MS -DOS device name only if the device is required to have a specific well-known
MS -DOS device name to work with user-mode programs.
A driver supplies an MS -DOS device name for a device object by using the IoCreateSymbolicLink routine to
create a symbolic link to the device. For example, the following code example creates a symbolic link from
\DosDevices\DosDeviceName to \Device\DeviceName.

UNICODE_STRING DeviceName;
UNICODE_STRING DosDeviceName;
NTSTATUS status;

RtlInitUnicodeString(&DeviceName, L"\\Device\\DeviceName");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\DosDeviceName");
status = IoCreateSymbolicLink(&DosDeviceName, &DeviceName);
if (!NT_SUCCESS(status)) {
/* Symbolic link creation failed. Handle error appropriately. */
}

Note that the system supports multiple versions of the \DosDevices directory. Make sure that your driver creates
its symbolic links in the version that you intend. For more information, see Local and Global MS -DOS Device
Names.
To access the DosDevices namespace from user mode, specify \\.\ when you open a file name. You can open a
corresponding device in user mode by calling CreateFile().
For example, the following code example opens the \\DosDevices\\DosDeviceName device in user mode.

file = CreateFileW(L"\\\\.\\DosDeviceName",
GENERIC READ | GENERIC WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

A symbolic link can also be created from a user-mode application by using the user-mode DefineDosDevice
routine. For more information, see the Microsoft Windows SDK.
Local and Global MS-DOS Device Names
12/5/2018 • 3 minutes to read • Edit Online

The Microsoft Windows 2000 and later versions of the Windows NT-based operating system maintain multiple
versions of the DosDevices directory.
On these operating systems, there is one global \DosDevices directory and multiple local \DosDevices
directories. The global \DosDevices directory holds the MS -DOS device names that are visible system-wide. A
local \DosDevices directory holds MS -DOS device names that are visible only in a particular local DosDevices
context.
The local DosDevices contexts are as follows.
On Windows XP and later, each logon session has its own local DosDevices context. System threads, and
any thread that is running as the LocalSystem user, do not run in a local DosDevices context.
On Windows 2000, each terminal server session has its own local DosDevices context. Any thread that is
running as part of the console session does not run in a local DosDevices context.
Each thread has a current DosDevices context, which can change over the lifetime of a thread. A thread that does
not run in a local DosDevices context is said to run in the global DosDevices context. Thus, the system account
runs in the global DosDevices context.
If a thread is currently running in a local DosDevices context, any MS -DOS device names that it creates are
created only in the local DosDevices directory. Thus, threads that are running in a local DosDevices context
cannot affect the MS -DOS device names that are visible to threads that are running in another local DosDevices
context or in the global DosDevices context. For example, if a user on Windows XP or later mounts a network
drive as X:, this does not affect the meaning of X: for any other user, or for the system as a whole.
On Windows XP and later, when the object manager looks up a name in \DosDevices, it first searches the local
\DosDevices directory, and then the global \DosDevices directory. If the name exists in both places, the local
name shadows the global name.
On Windows 2000, whenever a new terminal server session is initiated, the system builds local \DosDevices
directory by copying the global \DosDevices directory. Any subsequent changes to the global directory are not
propagated to the local directory.
A driver that must create its MS -DOS device names in the global \DosDevices directory can do so by creating its
symbolic links in a standard driver routine that is guaranteed to run in a system thread context, such as
DriverEntry. Alternatively, the global \DosDevices directory is available as \DosDevices\Global; drivers can
use a name of the \DosDevices\Global\DosDeviceName to specify a name in the global directory.
Note that \DosDevices\Global does not exist on platforms that do not support local and global versions of
\DosDevices, such as Windows 98/Me. The following code example creates a global symbolic link that works on
Windows 98/Me as well as Windows 2000 and later operating systems:
UNICODE_STRING deviceName; // Already initialized.
UNICODE_STRING symbolicLinkName; // Initializing below.
NTSTATUS status;

if (IoIsWdmVersionAvailable(1, 0x10)) {
// We're on Windows 2000 or later, so we use \DosDevices\Global.

RtlInitUnicodeString(&symbolicLinkName, L"\\DosDevices\\Global\\SymbolicLinkName");

} else {
// Windows 98/Me. We just use DosDevices.

RtlInitUnicodeString(&symbolicLinkName, L"\\DosDevices\\SymbolicLinkName");
}

status = IoCreateSymbolicLink(&symbolicLinkName, &deviceName);


if (!NT_SUCCESS(status)) {
/* Symbolic link creation failed. Handle error appropriately. */
}

A driver can create MS -DOS device names in a local \DosDevices directories by creating the symbolic link in
response to an IOCTL. When a thread in a particular local DosDevices context sends the IOCTL, the driver's
DispatchDeviceControl is called from within the current thread context.
For more information about the context in which a standard driver routine runs, see Dispatch Routines and IRQLs.
The system distinguishes local \DosDevices directories as follows:
On Windows XP and later, local \DosDevices directories are identified by the AuthenticationID for the
logon session's access token. For more information about the AuthenticationID, see the description of the
TOKEN_STATISTICS structure in the Microsoft Windows SDK documentation.
On Windows 2000, local \DosDevices directories are identified by the SessionId for the terminal server
session. For more information about the SessionId, see the description of the WTS_SESSION_INFO
structure in the Windows SDK documentation.
Windows NT 4.0 Terminal Server Edition supports local \DosDevices directories in the exact same manner as
Windows 2000.
Device Extensions
6/25/2019 • 3 minutes to read • Edit Online

For most intermediate and lowest-level drivers, the device extension is the most important data structure
associated with a device object. Its internal structure is driver-defined, and it is typically used to:
Maintain device state information.
Provide storage for any kernel-defined objects or other system resources, such as spin locks, used by the
driver.
Hold any data the driver must have resident and in system space to carry out its I/O operations.
Because most bus, function, and filter drivers (lowest-level and intermediate drivers) execute in an arbitrary
thread context (that of whatever thread happens to be current), a device extension is each driver's primary place
to maintain device state and all other device-specific data the driver needs. For example, any driver that
implements a CustomTimerDpc or CustomDpc routine usually provides storage for the required kernel-defined
timer and/or DPC objects in a device extension.
Every driver that has an ISR must provide storage for a pointer to a set of kernel-defined interrupt objects, and
most device drivers store this pointer in a device extension. Each driver determines the size of the device
extension when it creates a device object, and each driver defines the contents and structure of its own device
extensions.
The I/O manager's IoCreateDevice and IoCreateDeviceSecure routines allocate memory for the device
object and extension from the nonpaged memory pool.
Every standard driver routine that receives an IRP also receives a pointer to a device object representing the
target device for the requested I/O operation. These driver routines can access the corresponding device
extension through this pointer. Usually, a DeviceObject pointer is also an input parameter to a lowest-level
driver's ISR.
The following figure shows a representative set of driver-defined data for the device extension of a lowest-level
driver's device object. A higher-level driver would not provide storage for an interrupt object pointer returned by
IoConnectInterrupt and passed to KeSynchronizeExecution and IoDisconnectInterrupt. However, a
higher-level driver would provide storage for the timer and DPC objects shown in the following figure if the
driver has a CustomTimerDpc routine. A higher-level driver also might provide storage for an executive spin lock
and interlocked work queue.
In addition to providing storage for an interrupt object pointer, a lowest-level device driver must supply storage
for an interrupt spin lock if its ISR handles interrupts for two or more devices on different vectors or if it has
more than one ISR. For more information about registering an ISR, see Registering an ISR.
Typically, drivers store pointers to their device objects in their device extensions, as shown in the figure. A driver
might also keep a copy of the resource list for the device in the extension.
A higher-level driver typically stores a pointer to the next-lower driver's device object in its device extension. A
higher-level driver must pass a pointer to the next-lower driver's device object to IoCallDriver, after it has set
up the next-lower driver's I/O stack location in an IRP, as explained in Handling IRPs.
Note also that any higher-level driver that allocates IRPs for lower-level drivers must specify how many stack
locations the new IRPs should have. In particular, if a higher-level driver calls IoMakeAssociatedIrp,
IoAllocateIrp, or IoInitializeIrp, it must access the target device object of the next-lower-level driver to read its
StackSize value, in order to supply the correct StackSize as an argument to these support routines.
While a higher-level driver can read data from the next-lower-level driver's device object through the pointer
returned by IoAttachDeviceToDeviceStack, such a driver must follow these implementation guidelines:
Never attempt to write data to the lower driver's device object.
The only exceptions to this guideline are file systems, which set and clear DO_VERIFY_VOLUME in the
Flags of lower-level removable-media drivers' device objects.
Never attempt to access the lower driver's device extension for the following reasons:
There is no safe way to synchronize access to a single device extension between two drivers.
A pair of drivers that implement such a backdoor communication scheme cannot be upgraded
individually, cannot have an intermediate driver inserted between them without changing existing
driver source, and cannot be recompiled and moved readily from one Windows platform to the next.
To preserve their interoperability with lower-level drivers from one Windows platform or version to the next,
higher-level drivers either must reuse the IRPs given them or must create new IRPs, and they must use
IoCallDriver to communicate requests to lower-level drivers.
Properties of Device Objects
12/5/2018 • 2 minutes to read • Edit Online

Each device object has certain properties that describe the device and how the device object interacts with the
system. The device object properties include:
Device type. Specifies the device's type of hardware. For more information about device types, see
Specifying Device Types.
Device characteristics. Specifies flags that provide additional information about the device. For more
information, see Specifying Device Characteristics.
Exclusive access. Specifies whether the device object represents an exclusive device. If the device is exclusive,
only one handle can be open for the device object at a time. (If the underlying device supports overlapped
I/O, multiple threads of the same process can send requests through a single handle.) For more information,
see Specifying Exclusive Access to Device Objects.
Security descriptor. Device objects have a security descriptor that controls access to the device. For more
information, see Securing Device Objects.
For each of these properties, a default value can be set when the device object is created. For more information
about creating device objects, see Creating a Device Object.
Values for device object properties can also be set in the registry. See Setting Device Object Properties in the
Registry for more information.
Specifying Device Types
6/25/2019 • 2 minutes to read • Edit Online

Each device object has a device type, which is stored in the DeviceType member of its DEVICE_OBJECT
structure. The device type represents the type of underlying hardware for the driver.
Every kernel-mode driver that creates a device object must specify an appropriate device type value when calling
IoCreateDevice. The IoCreateDevice routine uses the supplied device type to initialize the DeviceType
member of the DEVICE_OBJECT structure.
The system defines the following device type values, listed in alphabetical order:
#define FILE_DEVICE_8042_PORT 0x00000027
#define FILE_DEVICE_ACPI 0x00000032
#define FILE_DEVICE_BATTERY 0x00000029
#define FILE_DEVICE_BEEP 0x00000001
#define FILE_DEVICE_BUS_EXTENDER 0x0000002a
#define FILE_DEVICE_CD_ROM 0x00000002
#define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003
#define FILE_DEVICE_CHANGER 0x00000030
#define FILE_DEVICE_CONTROLLER 0x00000004
#define FILE_DEVICE_DATALINK 0x00000005
#define FILE_DEVICE_DFS 0x00000006
#define FILE_DEVICE_DFS_FILE_SYSTEM 0x00000035
#define FILE_DEVICE_DFS_VOLUME 0x00000036
#define FILE_DEVICE_DISK 0x00000007
#define FILE_DEVICE_DISK_FILE_SYSTEM 0x00000008
#define FILE_DEVICE_DVD 0x00000033
#define FILE_DEVICE_FILE_SYSTEM 0x00000009
#define FILE_DEVICE_FIPS 0x0000003a
#define FILE_DEVICE_FULLSCREEN_VIDEO 0x00000034
#define FILE_DEVICE_INPORT_PORT 0x0000000a
#define FILE_DEVICE_KEYBOARD 0x0000000b
#define FILE_DEVICE_KS 0x0000002f
#define FILE_DEVICE_KSEC 0x00000039
#define FILE_DEVICE_MAILSLOT 0x0000000c
#define FILE_DEVICE_MASS_STORAGE 0x0000002d
#define FILE_DEVICE_MIDI_IN 0x0000000d
#define FILE_DEVICE_MIDI_OUT 0x0000000e
#define FILE_DEVICE_MODEM 0x0000002b
#define FILE_DEVICE_MOUSE 0x0000000f
#define FILE_DEVICE_MULTI_UNC_PROVIDER 0x00000010
#define FILE_DEVICE_NAMED_PIPE 0x00000011
#define FILE_DEVICE_NETWORK 0x00000012
#define FILE_DEVICE_NETWORK_BROWSER 0x00000013
#define FILE_DEVICE_NETWORK_FILE_SYSTEM 0x00000014
#define FILE_DEVICE_NETWORK_REDIRECTOR 0x00000028
#define FILE_DEVICE_NULL 0x00000015
#define FILE_DEVICE_PARALLEL_PORT 0x00000016
#define FILE_DEVICE_PHYSICAL_NETCARD 0x00000017
#define FILE_DEVICE_PRINTER 0x00000018
#define FILE_DEVICE_SCANNER 0x00000019
#define FILE_DEVICE_SCREEN 0x0000001c
#define FILE_DEVICE_SERENUM 0x00000037
#define FILE_DEVICE_SERIAL_MOUSE_PORT 0x0000001a
#define FILE_DEVICE_SERIAL_PORT 0x0000001b
#define FILE_DEVICE_SMARTCARD 0x00000031
#define FILE_DEVICE_SMB 0x0000002e
#define FILE_DEVICE_SOUND 0x0000001d
#define FILE_DEVICE_STREAMS 0x0000001e
#define FILE_DEVICE_TAPE 0x0000001f
#define FILE_DEVICE_TAPE_FILE_SYSTEM 0x00000020
#define FILE_DEVICE_TERMSRV 0x00000038
#define FILE_DEVICE_TRANSPORT 0x00000021
#define FILE_DEVICE_UNKNOWN 0x00000022
#define FILE_DEVICE_VDM 0x0000002c
#define FILE_DEVICE_VIDEO 0x00000023
#define FILE_DEVICE_VIRTUAL_DISK 0x00000024
#define FILE_DEVICE_WAVE_IN 0x00000025
#define FILE_DEVICE_WAVE_OUT 0x00000026

These constants are defined in Ntddk.h and Wdm.h. Check these files to see whether additional device types have
been defined.
The FILE_DEVICE_DISK specification covers disk partitions and any object that appears as a disk.
Intermediate drivers usually specify device types that represent the underlying device. For example, the system-
supplied fault-tolerant disk driver, ftdisk, creates device objects of type FILE_DEVICE_DISK; it does not define new
device types for the mirror sets, stripe sets, and volume sets it manages.
FILE_DEVICE_XXX values in the range of 0 through 32767 are reserved for Microsoft. All driver writers must use
these system-defined constants for devices belonging to the system-defined device types.
If a type of hardware does not match any of the defined types, specify a value of either
FILE_DEVICE_UNKNOWN, or a value within the range of 32768 through 65535.
Specifying Device Characteristics
6/25/2019 • 2 minutes to read • Edit Online

Each device object can have one or more device characteristics. Device characteristics are stored as flags in the
Characteristics member of the device object's DEVICE_OBJECT structure.
Most drivers specify only the FILE_DEVICE_SECURE_OPEN characteristic. This ensures that the same security
settings are applied to any open request into the device's namespace. For more information, see Controlling
Device Namespace Access.
The FILE_AUTOGENERATED_DEVICE_NAME is only used for PDOs. The FILE_FLOPPY_DISKETTE,
FILE_REMOVABLE_MEDIA, and FILE_WRITE_ONCE_MEDIA characteristics are specific to storage devices. For a
description of the possible device characteristic flags, see the description of the Characteristics member of
DEVICE_OBJECT.
Certain device characteristics, such as FILE_AUTOGENERATED_DEVICE_NAME, only apply to individual device
objects. Drivers can specify a setting for the device characteristics for individual device objects when they create
the device object by calling IoCreateDevice or IoCreateDeviceSecure.
The following characteristics apply to the entire device stack:
FILE_DEVICE_SECURE_OPEN
FILE_FLOPPY_DISKETTE
FILE_READ_ONLY_DEVICE
FILE_REMOVABLE_MEDIA
FILE_WRITE_ONCE_MEDIA
Drivers can set device characteristics that apply to the entire device stack by calling IoCreateDevice or
IoCreateDeviceSecure. Alternatively, device characteristics that apply to the entire device stack can be set in the
registry, for either the device or for the device's setup class. (For more information, see Setting Device Object
Properties in the Registry.)
The PnP manager determines the registry setting for device characteristics as follows.
If a value is specified for the individual device, the PnP manager uses that value;
Otherwise, if a value is specified for the device setup class, the PnP manager uses that value;
Otherwise, the PnP manager uses a value of zero as the registry setting.
If a device characteristic that applies to the entire device stack is set in the registry, or if it is set for any FDO or
filter DO in the stack, then the PnP manager sets it for every device object in the stack. (If the device is raw mode
capable, and thus does not have an FDO, then the PnP manager uses the PDO instead.)
Specifying Exclusive Access to Device Objects
6/25/2019 • 2 minutes to read • Edit Online

If exclusive access to a device is enabled, only one handle to the device can be open at a time. For the I/O manager
to enforce exclusive access to the device, the exclusive property must be set for the named device object in the
device stack.
For a WDM device stack that has a both a PDO and an FDO, the exclusive property can be set only by the INF file,
by using an INF AddReg directive. The PDO is the named object in the stack, but the bus driver (not the function
driver itself) creates the PDO, on behalf of the function driver. The only way to direct the bus driver to set the
exclusive flag for the PDO is by the class or device INF files. (The call to the IoCreateDevice routine creates the
FDO; setting the exclusive flag for the FDO has no effect.)
Drivers whose device objects are not stacked, such as non-WDM drivers and devices that operate in raw mode,
can use the IoCreateDeviceSecure routine to set the exclusive property for their named device object.
The I/O manager enforces exclusivity on a per name basis on named device objects, regardless of the trailing
name. For example, suppose the device object has the name "\Device\DeviceName". Then, the I/O manager
enforces exclusivity for a request to open "\Device\DeviceName\Filename1" followed by
"\Device\DeviceName\Filename2". If two objects in the device stack are named (which is not recommended), the
I/O manager allows a single handle to be opened for each object. In such a situation, drivers must enforce
exclusivity themselves within their DRIVER_DISPATCH callback functions. The I/O manager also does not enforce
exclusivity for opens relative to another file handle. For more information about file open requests in the device's
namespace, see Controlling Device Namespace Access.
Setting Device Object Properties in the Registry
6/25/2019 • 2 minutes to read • Edit Online

Properties of device objects can be set in the registry as follows:


For WDM drivers, properties can be set for each model of a device, or for a whole device setup class. (For
more information about device setup classes, see Device Setup Classes.)
For non-WDM drivers, properties can be set for a named device object's device setup class. The driver
specifies the device setup class when it creates the device object with IoCreateDeviceSecure. For more
information about how to specify a device setup class, see IoCreateDeviceSecure.
Any settings in the registry override the properties supplied when the driver created the device object.
Registry settings are specified by an INF file that is used during device installation, or they can be specified after
installation by an application that calls the device installation functions.
This section contains the following subsections:
Setting Device Object Registry Properties During Installation
Setting Device Object Registry Properties After Installation
Setting Device Object Registry Properties During
Installation
6/25/2019 • 2 minutes to read • Edit Online

To set device object properties during installation, you must provide an INF file that specifies the properties. You
can specify device object properties for either a device, or a device setup class.
These are specified as follows.
For an individual device, properties are set in the add -registry-section for the device. The INF AddReg
directive within the device's DDInstall.HW section specifies the add -registry-section for the device.
For a device setup class, properties are set in the add -registry-section for the device setup class. The INF
AddReg directive within the ClassInstall32 section for the class specifies the add -registry-section for the
class.
Within an add -registry-section, the following keywords can be used to specify the individual device object property
to set.

KEYWORD DEVICE OBJECT PROPERTY

DeviceType Device type

DeviceCharacteristics Device characteristics

Exclusive Exclusive

Security Security descriptor

For more information about using these keywords, see INF AddReg Directive.
The settings can be set by a user-mode component by using the device installation functions. For more
information, see Setting Device Object Registry Properties After Installation.
Setting Device Object Registry Properties After
Installation
6/25/2019 • 2 minutes to read • Edit Online

A user-mode program can use the device installation functions to get or set the registry settings for the properties
of a driver's device object. Normally these functions are used by installation software, but they can be used by any
user-mode program. (The program must be executed by a user that has Administrator access.)
The SetupDiGetDeviceRegistryProperty and SetupDiSetDeviceRegistryProperty functions get and set the
registry key for each specified property. The Property parameter specifies the property to get or set. The
PropertyBuffer points to the destination buffer (when getting the property) or source buffer (when setting the
property) for the property.
The correspondence between values for the Property parameter and actual properties is as follows.

VALUE FOR PROPERTY PARAMETER DEVICE OBJECT PROPERTY

SPDRP_CHARACTERISTICS Device characteristics

SPDRP_DEVTYPE Device type

SPDRP_EXCLUSIVE Exclusive

SPDRP_SECURITY Security descriptor as a SECURITY_DESCRIPTOR


structure

SPDRP_SECURITY_SDS Security descriptor as an SDDL string

Note that two different ways are provided to get or set the security descriptor. You can specify the
SPDRP_SECURITY value to treat the security descriptor as a SECURITY_DESCRIPTOR structure, or
SPDRP_SECURITY_SDS to treat the security descriptor as an SDDL string. For more information about SDDL
strings, see SDDL for Device Objects.
For Windows XP and later operating systems, programs can also get and set the property values for a device
setup class. Use the SetupDiGetClassRegistryProperty and SetupDiSetClassRegistryProperty functions to
get and set the property values for a device setup class.
For more information about using the SetupDiXxx functions, see Using Device Installation Functions.
Points to Consider About Device Objects
6/25/2019 • 2 minutes to read • Edit Online

Keep the following points in mind when designing a kernel-mode driver:


Except for certain file system drivers, all I/O operations are always sent to the top device object of a device
stack.
Device stacks are identified using the name of the named device object in the stack, or by using an alias for
that name, such as a symbolic link or a device interface. For WDM function drivers, the named device object
is created by the bus driver for the device. Non-WDM drivers must create their own named device objects.
A lowest-level driver, such as a PnP hardware bus driver, creates a physical device object (PDO ) for each
device it controls. An intermediate driver, such as a PnP function driver, creates a functional device object
(FDO ).
A WDM driver creates device objects in its AddDevice routine, which is called by the PnP manager after
device enumeration.
For most lowest-level and intermediate drivers, the device extension of each device object is each driver's
primary (and frequently only) global data storage area. Many drivers maintain device state and all other
device-specific data and resources a driver requires in the driver-defined device extension of each driver-
created device object.
(Additionally, the driver-specific I/O stack location associated with an IRP can be considered an operation-
specific local storage area for some kinds of data.)
For more information about the device objects a specific driver must create, see the device-type-specific
documentation in the Windows Driver Kit (WDK).
Managing Kernel Objects
12/5/2018 • 2 minutes to read • Edit Online

The Windows Object Manager controls objects that are part of the kernel-mode operating system. An object is a
collection of data that the operating system manages.
Typical kernel-mode objects include the following objects:
Device objects (See Device Objects and Device Stacks.)
File objects.
Symbolic links.
Registry keys.
Threads and processes.
Kernel dispatcher objects, such as event objects and mutex objects. (See Kernel Dispatcher Objects.)
Callback objects. (See Callback Objects.)
Section objects. (See Section Objects and Views.)
Kernel-mode objects enable you to manipulate objects in partnership with the object manager without damaging
the portions of the objects that the operating system needs. This principle is called encapsulation and is one of the
core concepts of object-oriented programming. (Because kernel-mode objects do not provide other aspects of
object-orientation, kernel-mode programming is typically referred to as object-based.) Kernel-mode objects do not
follow the same rules as objects in C++ or Microsoft COM.
Kernel-mode objects can be referenced by pointers. An object may have an object name. For more information
about object names, see Object Names.
User-mode programmers can reference objects only through indirection, using a handle. If an object has a name,
you can use it to obtain the handle in user mode. For more information about handles, see Object Handles.
Kernel-mode objects have a very specific life-cycle. For more information about object life-cycles, see Life Cycle of
an Object.
Object security is a prime concern for kernel-mode programming. For more information on object security, see
Object Security.
The kernel-mode environment stores objects in a virtual directory system, also known as the object namespace.
This allows objects to be accessed in a hierarchical way with parent and child objects. This namespace is similar to
a file system set of directories but does not exactly correspond to a particular file system on your computer. For
more information about object directories, see Object Directories.
Object Names
7/18/2019 • 2 minutes to read • Edit Online

Kernel-mode objects are either named or unnamed. The object name is a Unicode string that both user-mode and
kernel-mode components can use to refer to the object. For example, \KernelObjects\LowMemoryCondition is
the name of the standard event object that signals when the amount of free memory in the system is low.
Both user-mode and kernel-mode components use the object name to open a handle to an object. All subsequent
operations are performed by using the handle.
If an object is unnamed, a user-mode component cannot open a handle to it. Kernel-mode components can refer to
an unnamed object by either a pointer or a handle.
Named objects are organized into a hierarchy. Each object is named relative to a parent object. Each component of
the object's name begins with a backslash character. For example, \KernelObjects is the parent object for
\KernelObjects\LowMemoryCondition.
Only some types of objects can have child objects. The following are some examples:
Object directories have child objects. The object manager uses object directories to organize objects. For
example \KernelObjects is an object directory that holds standard event objects. Object directories do not
correspond to actual directories on a disk. For more information, see Object Directories.
Device objects for disk drives have child objects that correspond to files on the disk.
File objects that represent directories have child objects corresponding to files within the directory.
Device objects for WDM drivers have their own namespace that can be used in a driver-defined fashion. For
more information, see Controlling Device Namespace Access.
Files have object names that are relative to \DosDevices. For example, the file C:\Directory\File can be specified
as \DosDevices\C:\Directory\File.
For example, the components of the object name can be described as follows.

OBJECT NAME DESCRIPTION

\DosDevices Object directory.

\DosDevices\C: Device object representing the C: drive.

\DosDevices\C:\Directory File object representing the directory named C:\Directory.

\DosDevices\C:\Directory\File File object representing the file named C:\Directory\File.

Drivers that create named objects do so in specific object directories. For more information, see Object Directories.
Object Directories
6/25/2019 • 2 minutes to read • Edit Online

An object directory is a named object that is used solely to contain other named objects. For example, the \Device
object directory contains the named device objects created by drivers.
Do not confuse object directories with file system directories. Object directories exist only within the object
manager, and do not correspond to any directory on disk. (File system directories are, in fact, represented as file
objects.)
The following is a list of the top-level object directories that contain objects drivers might create or use:
\Callbacks
The system creates standard callback objects in this directory. For more information, see Using a System-
Defined Callback Object.
\Device
Drivers create named device objects in this directory. For more information, see Named Device Objects.
\KernelObjects
The system creates standard event objects in this directory. For more information, see Standard Event
Objects.
\DosDevices
This directory stores the MS -DOS device name of a device as a symbolic link to the corresponding device
object. For more information, see MS -DOS Device Names.
The system creates other top-level directories, but they are reserved for system use.
Drivers can create new object directories by calling the ZwCreateDirectoryObject routine.
Life Cycle of an Object
6/25/2019 • 2 minutes to read • Edit Online

This topic describes the "life cycle" of an object, that is, how objects are referenced and tracked by the object
manager. This topic also describes how to make objects temporary or permanent.
Object Reference Count
The object manager maintains a count of the number of references to an object. When an object is created, the
object manager sets the object's reference count to one. Once that counter falls to zero, the object is freed.
Drivers must ensure that the object manager has an accurate reference count for any objects they manipulate. An
object that is released prematurely can cause the system to crash. An object whose reference count is mistakenly
high will never be freed.
Objects can be referenced either by handle, or by pointer. In addition to the reference count, the object manager
maintains a count of the number of open handles to an object. Each routine that opens a handle increases both the
object reference count and the object handle count by one. Each call to such a routine must be matched with a
corresponding call to ZwClose. For more information, see Object Handles.
Within kernel mode, objects can be referenced by a pointer to the object. Routines that return pointers to objects,
such as IoGetAttachedDeviceReference, increase the reference count by one. Once the driver is done using the
pointer, it must call ObDereferenceObject to decrease the reference count by one.
The following routines all increase the reference count of an object by one:
ExCreateCallback
IoGetAttachedDeviceReference
IoGetDeviceObjectPointer
IoWMIOpenBlock
ObReferenceObject
ObReferenceObjectByHandle
ObReferenceObjectByPointer
Each call that is made to any of the preceding routines must be matched with a corresponding call to
ObDereferenceObject.
The ObReferenceObject and ObReferenceObjectByPointer routines are provided so that drivers can increase
the reference count of a known object pointer by one. ObReferenceObject simply increases the reference count.
ObReferenceObjectByPointer does an access check before increasing the reference count.
The ObReferenceObjectByHandle routine receives an object handle and supplies a pointer to the underlying
object. It too increases the reference count by one.
Temporary and Permanent Objects
Most objects are temporary; they exist as long as they are in use, and then they are freed by the object manager.
Objects can be created that are permanent. If an object is permanent, the object manager itself holds a reference to
the object. Thus, its reference count remains greater than zero, and the object is not freed when it is no longer in
use.
A temporary object can be accessed by name only as long as its handle count is nonzero. Once the handle count
decrements to zero, the object's name is removed from the object manager's namespace. Such objects can still be
accessed by pointer as long as their reference count remains greater than zero. Permanent objects can be accessed
by name as long as they exist.
An object can be made permanent at the time of its creation by specifying the OBJ_PERMANENT attribute in the
OBJECT_ATTRIBUTES structure for the object. For more information, see InitializeObjectAttributes.
To make a permanent object temporary, use the ZwMakeTemporaryObject routine. This routine causes an
object to be automatically deleted once it is no longer in use. (If the object has no open handles, the object's name
is immediately removed from the object manager's namespace. The object itself remains until the reference count
falls to zero.)
Object Handles
6/25/2019 • 2 minutes to read • Edit Online

Drivers and user-mode components access most system-defined objects through handles. Handles are
represented by the HANDLE opaque data type. (Note that handles are not used to access device objects or driver
objects.)
For most object types, the kernel-mode routine that creates or opens the object provides a handle to the caller. The
caller then uses that handle in subsequent operations on the object.
Here is a list of object types that drivers typically use, and the routines that provide handles to objects of that type.

OBJECT TYPE CORRESPONDING CREATE/OPEN ROUTINE

File IoCreateFile, ZwCreateFile, ZwOpenFile

Registry keys IoOpenDeviceInterfaceRegistryKey,


IoOpenDeviceRegistryKey, ZwCreateKey,
ZwOpenKey

Threads PsCreateSystemThread

Events IoCreateSynchronizationEvent,
IoCreateNotificationEvent

Symbolic links ZwOpenSymbolicLinkObject

Directory objects ZwCreateDirectoryObject

Section objects ZwOpenSection

When the driver no longer requires access to the object, it calls the ZwClose routine to close the handle. This
works for all of the object types listed in the table above.
Most of the routines that provide handles take an OBJECT_ATTRIBUTES structure as a parameter. This structure
can be used to specify attributes for the handle.
Drivers can specify the following handle attributes:
OBJ_KERNEL_HANDLE
The handle can only be accessed from kernel mode.
OBJ_INHERIT
Any children of the current process receive a copy of the handle when they are created.
OBJ_FORCE_ACCESS_CHECK
This attribute specifies that the system performs all access checks on the handle. By default, the system
bypasses all access checks on handles created in kernel mode.
Use the InitializeObjectAttributes routine to set these attributes in an OBJECT_ATTRIBUTES structure.
For information about validating object handles, see Failure to Validate Object Handles.
Private Object Handles
Whenever a driver creates an object handle for its private use, the driver must specify the OBJ_KERNEL_HANDLE
attribute. This ensures that the handle is inaccessible to user-mode applications.
Shared Object Handles
A driver that shares object handles between kernel mode and user mode must be carefully written to avoid
accidentally creating security holes. Here are some guidelines:
1. Create handles in kernel mode and pass them to user mode, instead of the other way around. Handles
created by a user-mode component and passed to the driver should not be trusted.
2. If the driver must manipulate handles on behalf of user-mode applications, use the
OBJ_FORCE_ACCESS_CHECK attribute to verify that the application has the necessary access.
3. Use ObReferenceObjectByPointer to keep a kernel-mode reference on a shared handle. Otherwise, if a
user-mode component closes the handle, the reference count goes to zero, and if the driver then tries to use
or close the handle the system will crash.
If a user-mode application creates an event object, a driver can safely wait for that event to be signaled, but only if
the application passes a handle to the event object to the driver through an IOCTL. The driver must handle the
IOCTL in the context of the process that created the event and must validate that the handle is an event handle by
calling ObReferenceObjectByHandle.
Memory Management for Windows Drivers
10/17/2019 • 2 minutes to read • Edit Online

Kernel-mode drivers allocate memory for purposes such as storing internal data, buffering data during I/O
operations, and sharing memory with other kernel-mode and user-mode components. Driver developers should
understand memory management in Windows so that they use allocated memory correctly and efficiently.
Windows manages virtual and physical memory, and divides memory into separate user and system address
spaces. A driver can specify whether allocated memory supports capabilities such as demand paging, data caching,
and instruction execution.
The memory manager is the kernel component that performs the memory management operations in Windows.
For more information, see Windows Kernel-Mode Memory Manager.
The memory manager implements a number of kernel-mode support routines that drivers call to allocate and
manage memory. For more information, see Memory Allocation and Buffer Management.
The memory-management capabilities of kernel-mode drivers are different from those of user-mode applications.
For more information about memory management for applications, see Memory Management.

In this section
Overview of Windows Memory Space
Allocating System-Space Memory
Map Registers
Mapping Bus-Relative Addresses to Virtual Addresses
Using the Kernel Stack
Using Lookaside Lists
Making Drivers Pageable
Accessing Read-Only System Memory
Accessing User-Space Memory
No-Execute (NX) Nonpaged Pool
Section Objects and Views
Using MDLs
Overview of Windows Memory Space
12/5/2018 • 2 minutes to read • Edit Online

The following figure illustrates the NT-based operating system's virtual memory spaces and their relationship to
system physical memory.

As this figure shows, virtual memory is backed by paged physical memory, and a virtual address range can be
backed by discontiguous physical memory pages. User-space virtual memory and system-space memory
allocated from paged pool are always pageable. Any user-space code or data can be paged out to secondary
storage at any time, even while the process is executing.
Note that any noncurrent process's virtual addresses are not visible, so its memory space is inaccessible.
For an extensive discussion of memory management, see the Inside Microsoft Windows Internals book from
Microsoft Press.
Allocating System-Space Memory
6/25/2019 • 2 minutes to read • Edit Online

Drivers can use system-allocated space within their device extensions as global storage areas for device-specific
information. Drivers can use only the kernel stack to pass small amounts of data to their internal routines. Some
drivers have to allocate additional, larger amounts of system-space memory, typically for I/O buffers.
To allocate I/O buffer space, the best memory allocation routines to use are MmAllocateNonCachedMemory,
MmAllocateContiguousMemorySpecifyCache, AllocateCommonBuffer (if the driver's device uses bus-
master DMA or a system DMA controller's auto-initialize mode), or ExAllocatePoolWithTag.
Nonpaged pool typically becomes fragmented as the system runs, so a driver's DriverEntry routine should call
these routines to set up any long-term I/O buffers the driver needs. Each of these routines, except
ExAllocatePoolWithTag, allocates memory that is aligned on a processor-specific boundary (determined by the
processor's data-cache-line size) to provide best performance.
Drivers should allocate I/O buffers as economically as possible, because nonpaged pool memory is a limited
system resource. Typically, a driver should avoid calling these support routines repeatedly to request allocations of
less than PAGE_SIZE because each allocation that is less than PAGE_SIZE also comes with a pool header that is
used to internally manage the allocation.
Tips for Allocating Driver Buffer Space Economically
To allocate I/O buffer memory economically, be aware of the following:
Each call to MmAllocateNonCachedMemory or MmAllocateContiguousMemorySpecifyCache
always returns a full multiple of the system's page size, of nonpaged system-space memory, whatever the
size of the requested allocation. Therefore, requests for less than a page are rounded up to a full page and
any remainder bytes on the page are wasted; they are inaccessible by the driver that called the function and
are unusable by other kernel-mode code.
Each call to AllocateCommonBuffer uses at least one adapter object map register, which maps at least
one byte and at most one page. For more information about map registers and using common buffers, see
Adapter Objects and DMA.
Allocating Memory with ExAllocatePoolWithTag
Drivers can also call ExAllocatePoolWithTag, specifying one of the following system-defined POOL_TYPE
values for the PoolType parameter:
PoolType = NonPagedPool for any objects or resources not stored in a device extension or controller
extension that the driver might access while it is running at IRQL > APC_LEVEL.
For this PoolType value, ExAllocatePoolWithTag allocates the amount of memory that is requested if the
specified NumberOfBytes is less than or equal to PAGE_SIZE. Otherwise, any remainder bytes on the last-
allocated page are wasted: inaccessible to the caller and unusable by other kernel-mode code.
For example, on an x86, an allocation request of 5 kilobytes (KB ) returns two 4-KB pages. The last 3 KB of
the second page is unavailable to the caller or another caller. To avoid wasting nonpaged pool, the driver
should allocate multiple pages efficiently. In this case, for example, the driver could make two allocations,
one for PAGE_SIZE and the other for 1 KB, to allocate a total of 5 KB.
Note Starting with Windows Vista, the system automatically adds the additional memory so two
allocations are unnecessary.
PoolType = PagedPool for memory that is always accessed at IRQL <= APC_LEVEL and is not in the file
system's write path.
ExAllocatePoolWithTag returns a NULL pointer if it cannot allocate the requested number of bytes. Drivers
should always check the returned pointer. If its value is NULL, the DriverEntry routine (or any other driver
routine that returns NTSTATUS values) should return STATUS_INSUFFICIENT_RESOURCES or handle the error
condition if possible.
Map Registers
5/6/2019 • 4 minutes to read • Edit Online

Drivers that perform DMA use three different address spaces, as shown in the following figure.

On any Windows platform, a driver has access to the full virtual address space supported by the processor. On a
32-bit processor, the virtual address space represents four gigabytes. The CPU translates addresses in the virtual
address space to addresses in the system's physical address space by using a page table. Each page table entry
(PTE ) maps one page of virtual memory to a page of physical memory, resulting in a paging operation when
necessary. An MDL (memory descriptor list) provides a similar mapping for a buffer associated with driver DMA
operations.
Devices vary in their ability to access the system's full virtual address space. A device uses addresses in logical
(device) address space. Each HAL uses map registers to translate a device or logical address to a physical address
(a location in physical RAM ). For the device hardware, map registers perform the same function that the MDL
(and page table) performs for the software (drivers): they translate addresses to physical memory.
Because these address spaces are separately addressed, a driver cannot use a pointer in virtual address space to
address a location in physical memory, and vice versa. The driver must first translate the virtual address to a
physical address. Similarly, a device cannot use a logical address to directly access physical memory. The device
must first translate the address.
A HAL must set up adapter objects that support DMA for a wide variety of DMA devices and I/O buses on
different computers. For example, most ISA DMA controllers, subordinate devices, and bus-master devices have
insufficient address lines to access the full four-gigabyte system physical address space of a 32-bit processor (or
the 64-gigabyte system physical address of an x86 processor running in 36-bit PAE mode). By contrast, PCI DMA
devices generally have more than enough address lines to access the full system physical address space in 32-bit
processors. Therefore, every HAL provides mappings between the logical address ranges that DMA devices can
access and physical address ranges of each computer.
Each adapter object is associated with one or more map registers, depending on the amount of data to be
transferred and the amount of available memory. During DMA transfers, the HAL uses each map register to alias
a device-accessible logical page to a page of physical memory in the CPU. In effect, map registers provide
scatter/gather support for drivers that use DMA, regardless of whether their devices have scatter/gather
capabilities.
The following figure illustrates such a physical-to-logical address mapping for the driver of an ISA DMA device
that does not have scatter/gather capabilities.
The previous figure shows the following types of mappings:
1. Each map register maps a range of physical addresses (pointed to by solid lines) to low -order logical
addresses (dotted lines) for an ISA DMA device.
Here, three map registers are used to alias three paged ranges of data in system physical memory to three
page-sized ranges of low -order logical addresses for an ISA DMA device.
2. The ISA device uses the mapped logical addresses to access system memory during DMA operations.
For a comparable PCI DMA device, three map registers would also be used for three page-sized ranges of
data. However, the mapped logical address ranges would not necessarily be identical to the corresponding
physical address ranges, so a PCI device would also use logical addresses to access system memory.
3. Each entry in the MDL maps a location in virtual address space to a physical address.
Note the correspondence between a map register and a virtual-to-physical entry in the MDL:
Each map register and each virtual entry in an MDL maps at most a full physical page of data for a DMA
transfer operation.
Each map register and each virtual entry in an MDL might map less than a full page of data. For example,
the initial virtual entry in an MDL can map to an offset from the physical page boundary, as shown earlier
in the Physical, Logical, and Virtual Address Mappings figure.
Each map register and each virtual entry in an MDL maps, at a minimum, one byte.
In an IRP requesting a read or write operation, each virtual entry in the opaque-to-drivers MDL at Irp-
>MdlAddress represents a page boundary in the system physical memory for a user buffer. Similarly, each
additional map register needed for a single DMA transfer represents a page boundary in the device-accessible
logical address range aliased to system physical memory.
On every Windows platform, each adapter object has an associated set of one or more map registers located at a
platform-specific (and opaque-to-drivers) base address. From a driver's point of view, the map register base
shown in the figure illustrating address mapping for a sample ISA DMA device is a handle for a set of map
registers that could be hardware registers in a chip, in a system DMA controller, or in a bus-master adapter, or
could even be HAL -created virtual registers in system memory.
The number of map registers available with an adapter object can vary for different devices and Windows
platforms. For example, the HAL can make more map registers available to drivers that use system DMA on some
platforms than on other platforms because the DMA controllers on different Windows platforms have different
capabilities.
Mapping Bus-Relative Addresses to Virtual Addresses
7/1/2019 • 2 minutes to read • Edit Online

Some processors implement separate memory and I/O address spaces, while other processors do not. Because of
these differences in hardware platforms, the mechanism drivers use to access I/O - or memory-resident device
resources differs from platform to platform.
A driver requests device I/O and memory resources in response to the PnP manager's
IRP_MN_QUERY_RESOURCE_REQUIREMENTS IRP. Depending on the hardware architecture, the HAL can
assign I/O resources in I/O space or in memory space, and can assign memory resources in I/O space or in
memory space.
If the HAL uses bus-relative memory space to access device resources (such as device registers), a driver must
map I/O space into virtual memory so that it can access these resources. The driver can determine whether the
resources are I/O - or memory-resident by inspecting the translated resources passed to the driver by the PnP
manager at device startup. If the HAL uses I/O space, no mapping is required.
Specifically, when a driver receives an IRP_MN_START_DEVICE request, it should examine the structures at
IrpSp->Parameters.StartDevice.AllocatedResources and IrpSp-
>Parameters.StartDevice.AllocatedResourcesTranslated, which describe the raw (bus-relative) and translated
resources, respectively, that the PnP manager has assigned to the device. Drivers should save a copy of each
resource list in the device extension as an aid to debugging.
The resource lists are paired CM_RESOURCE_LIST structures, in which each element of the raw list corresponds
to the same element of the translated list. For example, if AllocatedResources.List[0] describes a raw I/O port
range, AllocatedResourcesTranslated.List[0] describes the same range after translation. Each translated
resource includes a physical address and the type of the resource.
If a driver is assigned a translated memory resource ( CmResourceTypeMemory), it must call MmMapIoSpace
to map the physical address into a virtual address through which it can access device registers. For a driver to
operate in a platform-independent manner, it should check every returned, translated resource and map it, if
necessary.
A kernel-mode driver should take the following steps, in response to an IRP_MN_START_DEVICE
request, to ensure access to all device resources
1. Copy IrpSp->Parameters.StartDevice.AllocatedResources to the device extension.
2. Copy IrpSp->Parameters.StartDevice.AllocatedResourcesTranslated to the device extension.
3. In a loop, inspect each descriptor element in AllocatedResourcesTranslated. If the descriptor resource
type is CmResourceTypeMemory, call MmMapIoSpace, passing the physical address and length of the
translated resource.
When the driver receives an IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE request from the PnP
manager, it must release the mappings by calling MmUnmapIoSpace in a similar loop. The driver should also call
MmUnmapIoSpace if it must fail the IRP_MN_START_DEVICE request.
The raw resource type indicates which HAL access routine a driver should call (READ_REGISTER_XXX,
WRITE_REGISTER_XXX, READ_PORT_XXX, WRITE_PORT_XXX). Most drivers do not have to check the raw
resource list to determine which of these routines to use, because the driver itself requested the resource or the
driver writer knows the required type given the nature of the device hardware.
For a resource in I/O space (CmResourceTypePort, CmResourceTypeInterrupt, CmResourceTypeDma), the
driver should use the low -order 32 bits of the returned physical address to access the device resource, for example,
through the HAL's read and write READ_REGISTER_XXX, WRITE_REGISTER_XXX, READ_PORT_XXX,
WRITE_PORT_XXX routines.
Using the Kernel Stack
6/25/2019 • 2 minutes to read • Edit Online

The size of the kernel-mode stack is limited to approximately three pages. Therefore, when passing data to internal
routines, drivers cannot pass large amounts of data on the kernel stack.
To avoid running out of kernel-mode stack space, use the following design guidelines:
Avoid making deeply nested calls from one internal driver routine to another, if each routine passes data on
the kernel stack.
Make sure that you limit the number of recursive calls that can occur, if you design a driver that has a
recursive routine.
In other words, the call-tree structure of a driver should be relatively flat. You can call the IoGetStackLimits and
IoGetRemainingStackSize routines to determine the kernel stack space that is available, or
KeExpandKernelStackAndCallout to expand it. Note that the size of the kernel-mode stack can vary among
different hardware platforms and different versions of the operating system.
Running out of kernel stack space causes a fatal system error. Therefore, it is better for a driver to allocate system-
space memory than to run out of kernel stack space. However, nonpaged pool is also a limited system resource.
Generally, the kernel-mode stack resides in memory, however it can occasionally be paged out if the thread enters
a wait state that specifies user mode. See KeSetKernelStackSwapEnable for information on how to temporarily
disable kernel stack paging for the current thread. For performance reasons, it is not recommended to disable
kernel stack paging globally, but if you want to do so during a debugging session, see Disable paging of kernel
stacks
Because the kernel stack might be paged out, please be cautious about passing stack-based buffers (i.e. local
variables) to DMA or any routine that runs at DISPATCH_LEVEL or above.
Using Lookaside Lists
6/25/2019 • 5 minutes to read • Edit Online

Drivers that must allocate fixed-size buffers dynamically to perform on-demand I/O operations can use the
ExXxxLookasideListEx or ExXxxLookasideList support routines. After such a driver initializes its lookaside list,
the operating system will hold some number of dynamically allocated buffers of the given size in the driver's
lookaside list, effectively reserving a set of reusable, fixed-size buffers for the driver. The format and contents of a
driver's fixed-size buffers (also known as entries) in its lookaside list are driver-determined.
For example, storage class drivers that must set up SCSI request blocks (SRBs) for the underlying SCSI
port/miniport drivers use lookaside lists. Such a class driver allocates buffers for SRBs on an as-needed basis
from its lookaside list and releases each SRB buffer back to the lookaside list for the lookaside list to reuse
whenever an SRB is returned to the class driver in a completed IRP. Because a storage class driver cannot
predetermine how many SRBs it has to use at any time because I/O demand on the driver increases and falls, a
lookaside list is a convenient and economical way to manage the allocation and deallocation of buffers for fixed-
size SRBs in such a driver.
The operating system maintains state about all paged and nonpaged lookaside lists that are currently being used,
dynamically tracking the demand for allocations and deallocations of entries in all lists, and available system pool
for new entries. When demand for allocations is high, the operating system increases the number of entries it
holds in each lookaside list. When demand falls again, it frees surplus lookaside entries back to system pool.
Lookaside lists are thread-safe. A lookaside list has built-in synchronization to enable multiple, concurrently
running threads in a driver to share a lookaside list. These threads can safely allocate buffers from the shared
lookaside list and free these buffers to the list without requiring the driver to explicitly synchronize these
operations. However, to avoid possible leaks and data corruption, a set of threads that share a lookaside list must
explictly synchronize the initialization and deletion of the list.

Lookaside list interfaces


Starting with Windows Vista, the LOOKASIDE_LIST_EX structure describes a lookaside list that can contain
either paged or nonpaged buffers. If a driver provides custom Allocate and Free routines for this lookaside list,
these routines receive a private context as an input parameter. A driver can use this context to collect private data
for the lookaside list. For example, the context might be used to count the number of list entries that are
dynamically allocated and freed by the list. For a code example that shows how to use a context in this way, see
ExInitializeLookasideListEx.
The following system-supplied routines support lookaside lists that are described by a LOOKASIDE_LIST_EX
structure:
ExAllocateFromLookasideListEx
ExDeleteLookasideListEx
ExFlushLookasideListEx
ExFreeToLookasideListEx
ExInitializeLookasideListEx
Starting with Windows 2000, the PAGED_LOOKASIDE_LIST structure describes a lookaside list that contains
paged buffers. If a driver provides custom Allocate and Free routines for this lookaside list, these routines do not
receive a private context as an input parameter. For this reason, if your driver is intended to run only on
Windows Vista and later versions of Windows, consider using the LOOKASIDE_LIST_EX structure instead of the
PAGED_LOOKASIDE_LIST structure for your lookaside lists. The following system-supplied routines support
lookaside lists that are described by a PAGED_LOOKASIDE_LIST structure:
ExAllocateFromPagedLookasideList
ExDeletePagedLookasideList
ExFreeToPagedLookasideList
ExInitializePagedLookasideList
Starting with Windows 2000, the NPAGED_LOOKASIDE_LIST structure describes a lookaside list that contains
nonpaged buffers. If a driver provides custom Allocate and Free routines for this lookaside list, these routines do
not receive a private context as an input parameter. Again, if your driver is intended to run only on Windows Vista
and later versions of Windows, consider using the LOOKASIDE_LIST_EX structure instead of the
NPAGED_LOOKASIDE_LIST structure for your lookaside lists. The following system-supplied routines support
lookaside lists that are described by an NPAGED_LOOKASIDE_LIST structure:
ExAllocateFromNPagedLookasideList
ExDeleteNPagedLookasideList
ExFreeToNPagedLookasideList
ExInitializeNPagedLookasideList

Implementation guidelines
To implement a lookaside list that uses a LOOKASIDE_LIST_EX structure, follow these design guidelines:
Call ExInitializeLookasideListEx to set up a lookaside list. In this call, specify whether the entries in the
lookaside list are to be paged or nonpaged buffers. Use nonpaged buffers if the driver itself or any
underlying driver to which it passes its lookaside list entries might access these entries at IRQL >=
DISPATCH_LEVEL. Use paged buffers only if accesses to the driver's lookaside list entries always occur at
IRQL <= APC_LEVEL.
The LOOKASIDE_LIST_EX structure for the lookaside list must always reside in nonpaged system
memory regardless of whether the entries in the list are paged or nonpaged.
For better performance, pass NULL pointers for the Allocate and Free parameters to
ExInitializeLookasideListEx unless the allocation and deallocation routines must do more than merely
allocate and free memory for lookaside list entries. For example, these routines might record information
about the driver's usage of dynamically allocated buffers.
A driver-supplied Allocate routine can pass the input parameters (PoolType, Tag, and Size) that it receives
directly to the ExAllocatePoolWithTag or ExAllocatePoolWithQuotaTag routine to allocate a new
buffer.
For every call to ExAllocateFromLookasideListEx, make the reciprocal call to
ExFreeToLookasideListEx as soon as possible whenever a previously allocated entry is no longer being
used.
Supplying Allocate and Free routines that do nothing more than call ExAllocatePoolWithTag and ExFreePool,
respectively, wastes processor cycles. ExAllocateFromLookasideListEx makes the necessary calls to
ExAllocatePoolWithTag and ExFreePool automatically when a driver passes NULL Allocate and Free pointers
to ExInitializeLookasideListEx.
Any driver-supplied Allocate routine must not allocate memory for an entry from paged pool to be held in a
nonpaged lookaside list or vice versa. It must also allocate fixed-size entries, because any subsequent driver call to
ExAllocateFromLookasideListEx returns the first entry currently held in the lookaside list unless the list is
empty. That is, a call to ExAllocateFromLookasideListEx causes a call to the driver-supplied Allocate routine
only if the given lookaside list is currently empty. Therefore, at each call to ExAllocateFromLookasideListEx, the
returned entry will be exactly the size that the driver needs only if all entries in the lookaside list are of a fixed size.
A driver-supplied Allocate routine should also not change the Tag value that the driver originally passed to
ExInitializeLookasideListEx, because changes in the pool-tag value would make debugging and tracking the
driver's memory usage more difficult.
Calls to ExFreeToLookasideListEx store previously allocated entries in the lookaside list unless the list is already
full (that is, the list contains the system-determined maximum number of entries). For better performance, a driver
should make a reciprocal call to ExFreeToLookasideListEx as quickly as it can for every call that it makes to
ExAllocateFromLookasideListEx. When a driver frees entries back to its lookaside list quickly, that driver's next
call to ExAllocateFromLookasideListEx is far less likely to incur the performance penalty of dynamically
allocating memory for a new entry.
Similar guidelines apply to a lookaside list that uses a PAGED_LOOKASIDE_LIST or
NPAGED_LOOKASIDE_LIST structure.
Making Drivers Pageable
6/25/2019 • 2 minutes to read • Edit Online

By default, the linker assigns names such as ".text" and ".data" to the code and data sections of a driver image file.
When the driver is loaded, the I/O manager makes these sections nonpaged. A nonpaged section is always
memory-resident.
A driver developer has the option to make designated parts of a driver pageable so that Windows can move these
parts to the paging file when they are not in use. To make a code or data section pageable, the driver developer
assigns a name that begins with "PAGE" to the section. The I/O manager checks the names of the sections when it
loads a driver. If a section name begins with "PAGE", the I/O manager makes the section pageable.
Code that runs at IRQL >= DISPATCH_LEVEL must be memory-resident. That is, this code must be either in a
nonpageable segment, or in a pageable segment that is locked in memory. If code that is running at IRQL >=
DISPATCH_LEVEL causes a page fault, a bug check occurs. Drivers can use the PAGED_CODE macro to verify
that pageable functions are called only at appropriate IRQLs.
If a code or data section is pageable, the driver can lock the section in memory by calling the
MmLockPagableCodeSection or MmLockPagableDataSection routine. The section remains locked until the
driver calls the MmUnlockPagableImageSection routine to unlock it. While the pageable section is locked, it
behaves the same as a nonpaged section.
For information about how to assign names to code and data sections, see MmLockPagableCodeSection and
MmLockPagableDataSection.
This section includes the following topics:
When Should Code and Data Be Pageable?
Making Driver Code or Data Pageable
When Should Code and Data Be Pageable?
6/25/2019 • 2 minutes to read • Edit Online

You can make all or part of your driver pageable. Paging driver code can reduce the size of the driver's load image,
thus freeing system space for other uses. It is most practical for drivers of sporadically used devices, such as
modems and CD -ROMs, or for parts of drivers that are rarely called.
Driver code that does any of the following must be memory-resident. That is, this code must be either in a
nonpaged section, or in a paged section that is locked in memory when the code runs.
Runs at or above IRQL = DISPATCH_LEVEL.
Acquires spin locks.
Calls any of the kernel's object support routines, such as KeReleaseMutex or KeReleaseSemaphore, in
which the Wait parameter is set to TRUE. If the kernel is called with Wait set to TRUE, the call returns with
IRQL at DISPATCH_LEVEL.
Driver code must be running at IRQL < DISPATCH_LEVEL when the code does anything that might cause a page
fault. Code can cause a page fault if it does any of the following:
Accesses paged pool that is not locked in memory.
Calls a pageable routine.
Accesses unlocked user buffers in the context of the user thread.
Typically, you should make a section paged if the total amount of all the pageable code (or data) is at least 4
kilobytes (KB ). Whenever possible, you should isolate purely pageable code (or data) into a separate section from
code (or data) that can sometimes be pageable but must sometimes be locked. For example, combining purely
pageable code and locked-on-demand code causes more system space to be locked down for the combined
section than is necessary. However, if a driver has less than 4 KB of possibly pageable code (or data), you might
combine that code (or data) with locked-on-demand code (or data) into one section, saving system space.
Detecting Code That Can Be Pageable
6/25/2019 • 2 minutes to read • Edit Online

To detect code that runs at IRQL >= DISPATCH_LEVEL, use the PAGED_CODE macro. In debug mode, this macro
generates a message if the code runs at IRQL >= DISPATCH_LEVEL. Add the macro as the first statement in a
routine to mark the whole routine as paged code, as the following example shows:

NTSTATUS
MyDriverXxx(
IN OUT PVOID ParseContext OPTIONAL,
OUT PHANDLE Handle
)
{
NTSTATUS Status;

PAGED_CODE();
.
.
.
}

To make sure that you are doing this correctly, run the Driver Verifier against your finished driver with the Force
IRQL Checking option enabled. This option causes the system to automatically page out all pageable code every
time that the driver raises IRQL to DISPATCH_LEVEL or above. Using the Driver Verifier, you can quickly find any
driver bugs in this area. Otherwise, these bugs will typically be found only by customers and they can frequently be
very hard for you to reproduce.
Isolating Pageable Code
6/25/2019 • 2 minutes to read • Edit Online

A routine that uses a spin lock cannot be paged. However, in some cases you can isolate the operations that require
a spin lock in a separate routine that will not be included in the pageable section.
For example, consider the following fragment:

// PAGED_CODE();

KeInitializeEvent( &event, NotificationEvent, FALSE );


irp = IoBuildDeviceIoControlRequest( IRP_MJ_DEVICE_CONTROL,
DeviceObject,
(PVOID) NULL,
0,
(PVOID) NULL,
0,
FALSE,
&event,
&ioStatus );
if (irp) {
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_LOAD_FILE_SYSTEM;
status = IoCallDriver( DeviceObject, irp );
if (status == STATUS_PENDING) {
(VOID) KeWaitForSingleObject( &event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER) NULL );
}
}

SPINLOCKUSE !
KeAcquireSpinLock( &IopDatabaseLock, &irql );
// Code inside spin lock ...

DeviceObject->ReferenceCount--;

if (!DeviceObject->ReferenceCount && !DeviceObject->AttachedDevice) {


//Unload the driver
.
.
.
} else {
KeReleaseSpinLock( &IopDatabaseLock, irql );
}

The preceding routine could be made pageable (saving about 160 bytes) by moving the few lines of code that
reference a spin lock into a separate routine.
In addition, remember that driver code must not be marked as pageable if it calls any KeXxx support routines,
such as KeReleaseMutex or KeReleaseSemaphore, in which the Wait parameter is set to TRUE. Such a call
returns with IRQL at DISPATCH_LEVEL.
Locking Pageable Code or Data
7/9/2019 • 7 minutes to read • Edit Online

Certain kernel-mode drivers, such as the serial and parallel drivers, do not have to be memory-resident unless the
devices they manage are open. However, as long as there is an active connection or port, some part of the driver
code that manages that port must be resident to service the device. When the port or connection is not being used,
the driver code is not required. In contrast, a driver for a disk that contains system code, application code, or the
system paging file must always be memory-resident because the driver constantly transfers data between its
device and the system.
A driver for a sporadically used device (such as a modem) can free system space when the device it manages is not
active. If you place in a single section the code that must be resident to service an active device, and if your driver
locks the code in memory while the device is being used, this section can be designated as pageable. When the
driver's device is opened, the operating system brings the pageable section into memory and the driver locks it
there until no longer needed.
The system CD audio driver code uses this technique. Code for the driver is grouped into pageable sections
according to the manufacturer of CD device. Certain brands might never be present on a given system. Also, even
if a CD -ROM exists on a system, it might be accessed infrequently, so grouping code into pageable sections by CD
type makes sure that code for devices that do not exist on a particular computer will never be loaded. However,
when the device is accessed, the system loads the code for the appropriate CD device. Then the driver calls the
MmLockPagableCodeSection routine, as described below, to lock its code into memory while its device is being
used.
To isolate the pageable code into a named section, mark it with the following compiler directive:
#pragma alloc_text(PAGE*Xxx, *RoutineName)
The name of a pageable code section must start with the four letters "PAGE" and can be followed by up to four
characters (represented here as Xxx) to uniquely identify the section. The first four letters of the section name (that
is, "PAGE") must be capitalized. The RoutineName identifies an entry point to be included in the pageable section.
The shortest valid name for a pageable code section in a driver file is simply PAGE. For example, the pragma
directive in the following code example identifies RdrCreateConnection as an entry point in a pageable code section
named PAGE.

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RdrCreateConnection)
#endif

To make pageable driver code resident and locked in memory, a driver calls MmLockPagableCodeSection,
passing an address (typically the entry point of a driver routine) that is in the pageable code section.
MmLockPagableCodeSection locks in the whole contents of the section that contains the routine referenced in
the call. In other words, this call makes every routine associated with the same PAGEXxx identifier resident and
locked in memory.
MmLockPagableCodeSection returns a handle to be used when unlocking the section (by calling the
MmUnlockPagableImageSection routine) or when the driver must lock the section from additional locations in
its code.
A driver can also treat seldom-used data as pageable so that it, too, can be paged out until the device it supports is
active. For example, the system mixer driver uses pageable data. The mixer device has no asynchronous I/O
associated with it, so this driver can make its data pageable.
The name of a pageable data section must start with the four letters "PAGE" and can be followed by up to four
characters to uniquely identify the section. The first four letters of the section name (that is, "PAGE") must be
capitalized.
Avoid assigning identical names to code and data sections. To make source code more readable, driver developers
typically assign the name PAGE to the pageable code section because this name is short and it might appear in
numerous alloc_text pragma directives. Longer names are then assigned to any pageable data sections (for
example, PAGEDATA for data_seg, PAGEBSS for bss_seg, and so on) that the driver might require.
For example, the first two pragma directives in the following code example define two pageable data sections,
PAGEDATA and PAGEBSS. PAGEDATA is declared using the data_seg pragma directive and contains initialized data.
PAGEBSS is declared using the bss_seg pragma directive and contains uninitialized data.

#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")

INT Variable1 = 1;
INT Variable2;

CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];

#pragma data_seg()
#pragma bss_seg()

In this code example, Variable1 and Array1 are explicitly initialized and are therefore placed in the PAGEDATA
section. Variable2 and Array2 are implicitly zero-initialized and are placed in the PAGEBSS section.
Implicitly initializing global variables to zero reduces the size of the on-disk executable file and is preferred over
explicit initialization to zero. Explicit zero-initialization should be avoided except in cases where it is required in
order to place a variable in a specific data section.
To make a data section memory-resident and lock it in memory, a driver calls MmLockPagableDataSection,
passing a data item that appears in the pageable data section. MmLockPagableDataSection returns a handle to
be used in subsequent locking or unlocking requests.
To restore a locked section's pageable status, call MmUnlockPagableImageSection, passing the handle value
returned by MmLockPagableCodeSection or MmLockPagableDataSection, as appropriate. A driver's Unload
routine must call MmUnlockPagableImageSection to release each handle it has obtained for lockable code and
data sections.
Locking a section is an expensive operation because the memory manager must search its loaded module list
before locking the pages into memory. If a driver locks a section from many locations in its code, it should use the
more efficient MmLockPagableSectionByHandle after its initial call to MmLockPagableXxxSection.
The handle passed to MmLockPagableSectionByHandle is the handle returned by the earlier call to
MmLockPagableCodeSection or MmLockPagableDataSection.
The memory manager maintains a count for each section handle and increments this count every time that a driver
calls MmLockPagableXxx for that section. A call to MmUnlockPagableImageSection decrements the count.
While the counter for any section handle is nonzero, that section remains locked in memory.
The handle to a section is valid as long as its driver is loaded. Therefore, a driver should call
MmLockPagableXxxSection only one time. If the driver requires additional locking calls, it should use
MmLockPagableSectionByHandle.
If a driver calls any MmLockPagableXxx routine for a section that is already locked, the memory manager
increments the reference count for the section. If the section is paged out when the lock routine is called, the
memory manager pages in the section and sets its reference count to one.
Using this technique minimizes the driver's effect on system resources. When the driver runs, it can lock into
memory the code and data that must be resident. When there are no outstanding I/O requests for its device, (that
is, when the device is closed or if the device was never opened), the driver can unlock the same code or data,
making it available to be paged out.
However, after a driver has connected interrupts, any driver code that can be called during interrupt processing
always must be memory resident. While some device drivers can be made pageable or locked into memory on
demand, some core set of such a driver's code and data must be permanently resident in system space.
Consider the following implementation guidelines for locking a code or data section.
The primary use of the Mm (Un)LockXxx routines is to enable normally nonpaged code or data to be made
pageable and brought in as nonpaged code or data. Drivers such as the serial driver and the parallel driver
are good examples: if there are no open handles to a device such a driver manages, parts of code are not
needed and can remain paged out. The redirector and server are also good examples of drivers that can use
this technique. When there are no active connections, both of these components can be paged out.
The whole pageable section is locked into memory.
One section for code and one for data per driver is efficient. Many named, pageable sections are generally
inefficient.
Keep purely pageable sections and paged but locked-on-demand sections separate.
Remember that MmLockPagableCodeSection and MmLockPagableDataSection should not be
frequently called. These routines can cause heavy I/O activity when the memory manager loads the section.
If a driver must lock a section from several locations in its code, it should use
MmLockPagableSectionByHandle.
Paging an Entire Driver
6/25/2019 • 2 minutes to read • Edit Online

A driver that uses the MmLockPagableXxx support routines and specifies paged and discardable sections
consists of nonpaged sections, paged sections, and an INIT section that is discarded after driver initialization.
After a device driver connects interrupts for the devices it manages, the driver's interrupt handling path must be
resident in system space. The interrupt-handling code must be part of the driver section that cannot be paged out,
in case an interrupt occurs.
Two additional memory manager routines, MmPageEntireDriver and MmResetDriverPaging, can be used to
override the pageable or nonpageable attributes of all sections that make up a driver image. These routines enable
a driver to be paged out in its entirety when the device it manages is not being used and cannot generate
interrupts.
Examples of system drivers that are completely pageable are the win32k.sys driver, the serial driver, the mailslot
driver, the beep driver and the null driver.
A serial driver is typically used intermittently. Until a port it manages is opened, a serial driver can be paged out
completely. As soon as a port is opened, the parts of the serial driver that must be memory-resident must be
brought into nonpaged system space. Other parts of the driver can remain pageable.
A driver that can be completely paged out should call MmPageEntireDriver during driver initialization before
interrupts are connected.
When a device managed by a paged-out driver receives an open request, the driver is paged in. Then, the driver
must call MmResetDriverPaging before it connects to interrupts. Calling MmResetDriverPaging causes the
memory manager to treat the driver's sections according to the attributes acquired during compilation and linkage.
Any section that is nonpaged, such as a text section, will be paged into nonpaged system memory; pageable
sections will be paged in as they are referenced.
Such a driver must keep a reference count of open handles to its devices. The driver increments the count at each
open request for any device and decrements the count at each close request. When the count reaches zero, the
driver should disconnect interrupts and then call MmPageEntireDriver. If a driver manages more than one
device, the count must be zero for all such devices before the driver can call MmPageEntireDriver.
It is the driver's responsibility to do whatever synchronization is necessary when changing the reference count, and
to prevent the reference count from changing while the pageable state of the driver is changing. That is, in SMP
computers, the driver must make sure that MmPageEntireDriver cannot be in progress on one processor, while
on another processor, an open call is causing interrupts to be connected and the reference count to be incremented.
Accessing Read-Only System Memory
6/25/2019 • 2 minutes to read • Edit Online

The Windows memory manager enforces read-only access of pages that are not marked as writable.
Read-only memory has always been protected in user mode. However, in Windows NT 4.0 and earlier versions,
read-only memory was not protected in kernel mode.
If a Windows kernel-mode driver or application tries to write to a read-only memory segment, the system issues a
bug check. For more information, see Bug Check 0xBE: ATTEMPTED_WRITE_TO_READONLY_MEMORY.
Intercepting System Calls
Some drivers intercept system calls by overwriting the driver's own code and inserting jump instructions or other
changes. Because the driver's own code is read-only, this technique will cause a bug check to be issued.
Global Strings
If a global string is to be modified, it must not be declared as a pointer to a constant value:

CHAR *myString = "This string cannot be modified.";

In this case, the linker might put the string in a read-only memory segment. Then an attempt to modify the string
will result in a bug check.
Instead, the string should be explicitly declared as an array of L -value characters:

CHAR myString[] = "This string can be modified.";

This declaration makes sure that the string is put in writable memory.
Accessing User-Space Memory
6/25/2019 • 2 minutes to read • Edit Online

A driver cannot directly access memory through user-mode virtual addresses unless it is running in the context of
the user-mode thread that caused the driver's current I/O operation and it is using that thread's virtual addresses.
Only highest-level drivers, such as FSDs, can be sure their dispatch routines will be called in the context of such a
user-mode thread. A highest-level driver can call MmProbeAndLockPages to lock down a user buffer before
setting up an IRP for lower drivers.
Lowest-level and intermediate drivers that set up their device objects for buffered I/O or direct I/O can rely on the
I/O manager or a highest-level driver to pass valid access to locked-down user buffers or to system-space buffers
in IRPs.
No-Execute (NX) Nonpaged Pool
12/5/2018 • 2 minutes to read • Edit Online

As a best practice, drivers for Windows 8 and later versions of Windows should allocate most or all of their
nonpaged memory from the no-execute (NX) nonpaged pool. By allocating memory from NX nonpaged pool, a
kernel-mode driver improves security by preventing malicious software from executing instructions in this
memory.
Starting with Windows 8, kernel-mode drivers can allocate memory from a pool of NX nonpaged memory. This
pool is managed by a general-purpose, kernel-mode memory allocator that operates similarly to the user-mode
Win32 heap allocator. The memory in this pool is NX and nonpaged. The x86, x64, and ARM processor
architectures enable memory pages to be designated as NX to prevent the execution of instructions in these pages.
Typically, a kernel-mode driver uses memory allocated from nonpaged pool to store data, and does not require the
ability to execute instructions in this memory.

Support for Legacy Drivers


In Windows 7 and earlier versions of Windows, all memory allocated from the nonpaged pool is executable. To
encourage porting of these drivers to use NX nonpaged pool in Windows 8 and later versions of Windows,
Microsoft provides several opt-in mechanisms to enable developers to update their drivers with minimal effort.
For more information, see NX Pool Opt-In Mechanisms.
For backward compatibility, driver binaries that run on Windows 7 and earlier versions of Windows, and that
allocate memory from the executable nonpaged pool, will run on Windows 8 and later versions of Windows
without modification. However, these drivers do not take advantage of the improved security of the NX nonpaged
pool.
NX and Execute Pool Types
6/25/2019 • 2 minutes to read • Edit Online

To indicate whether memory allocated from a nonpaged pool should be no-execute (NX), you can use two new
pool types starting with Windows 8. These pool types are designated by the following POOL_TYPE enumeration
values:
NonPagedPoolNx
NX nonpaged pool. Instructions cannot be executed in memory allocated from this pool.
NonPagedPoolExecute
Executable nonpaged pool. Instruction execution is enabled in memory allocated from this pool.
Most drivers use allocated memory only to store data, and do not execute instructions in this memory. If you build
your driver to run on Windows 8 and later versions of Windows, allocate NonPagedPoolNx memory from the
NX nonpaged pool whenever possible. Only drivers that need to execute instructions from nonpaged memory
should allocate NonPagedPoolExecute memory from the executable nonpaged pool.
For existing drivers that are built for Windows 7 and earlier versions of Windows, when you use the
NonPagedPool pool type your driver allocates memory from the executable pool. The NonPagedPool constant
name has the same value as the NonPagedPoolExecute constant name that is defined starting with Windows 8.
Therefore, these constants refer to the same pool type.
If a driver written for Windows 7 or an earlier version of Windows is built for Windows 8 or a later version of
Windows, instances of the NonPagedPool pool type should be replaced by either NonPagedPoolNx or
NonPagedPoolExecute. The driver developer either can explicitly perform this replacement, or can implicitly
perform the replacement by using one of the opt-in mechanisms that is provided to aid developers in porting their
drivers to Windows 8. For more information, see NX Pool Opt-In Mechanisms.
NX Pool Compatibility Issues
6/25/2019 • 3 minutes to read • Edit Online

When you use the NX nonpaged pool in driver binaries for Windows 8, you will find compatibility issues if you run
these binaries on earlier versions of Windows.
Windows 8 is the first version of Windows to support the NX nonpaged pool. However, a large number of existing
kernel-mode driver binaries are available for Windows 7 and earlier versions of Windows that run on the x86, x64,
and IA64 processor architectures. To allocate nonpaged memory, these drivers use the executable nonpaged pool
instead the NX nonpaged pool. For backward compatibility, kernel-mode driver binaries that run on Windows 7,
and on some earlier versions of Windows, and that allocate memory from the nonpaged pool, will run on
Windows 8 without modification. However, these drivers do not take advantage of the availability of NX nonpaged
pool in Windows 8.

Running Existing Driver Binaries on Windows 8


A driver binary that is built for Windows 7 (or possibly for an earlier version of Windows), and that uses the
NonPagedPool pool type, is not prevented from running on Windows 8. To enable backward compatibility, the
NonPagedPoolExecute constant is defined to have the same value as the NonPagedPool constant in the
POOL_TYPE enumeration. Thus, in any version of Windows in which this driver runs, the memory that the driver
allocates from nonpaged pool is always executable.
Windows 8 is the first version of Windows to support the ARM architecture. Thus, there are no driver binaries for
ARM that are built for earlier versions of Windows and that require backward compatibility. Instead, all drivers
written for Windows on ARM are expected to specify NonPagedPoolNx instead of NonPagedPoolExecute in
their nonpaged pool allocations unless they explicitly require executable memory.
If a driver is ported to ARM from x86, x64, or IA64, the POOL_NX_OPTIN_AUTO opt-in mechanism is
automatically applied during the driver build process. This opt-in mechanism uses the preprocessor to replace, by
default, all instances of the NonPagedPool constant name with NonPagedPoolNx. If necessary, you can use the
POOL_NX_OPTOUT opt-out mechanism to overrride this opt-in mechanism on a per-file basis.

Other Compatibility Issues


The NonPagedPoolNx pool type is supported starting with Windows 8. Do not use this pool type in drivers for
earlier versions of Windows. The pool allocators in these earlier versions of Windows do not operate correctly
when the driver requests an allocation with a NonPagedPoolNx pool type.
In versions of Windows before Windows 8, the NonPagedPoolExecute pool type can be freely used as a
substitute for the NonPagedPool pool type. The POOL_TYPE enumeration defines NonPagedPool and
NonPagedPoolExecute to have the same value.

NX Pool Type Porting Guidelines


When you port your driver code to Windows 8 or later from an earlier version of Windows, there are several ways
to add support for the NonPagedPoolNx and NonPagedPoolExecute pool types. From the following list,
choose the approach that best fits your requirements:
If your driver is not intended to run on a version of Windows earlier than Windows 8, replace most or all
instances of NonPagedPool with NonPagedPoolNx. Only rarely should the developer replace an
instance of NonPagedPool with NonPagedPoolExecute.
If your driver source code targets both Windows 8 and earlier versions of Windows, and you ship a different
driver binary for each version, use the POOL_NX_OPTIN_AUTO opt-in mechanism. This approach does not
require replacing the instances of NonPagedPool in the driver source. For more information, see NX Pool
Opt-In Mechanisms.
If your driver source code targets both Windows 8 and earlier versions of Windows, and you ship a single
driver binary to run on all supported versions, use the POOL_NX_OPTIN opt-in mechanism. This approach
does not require replacing the instances of NonPagedPool in the driver source. For more information, see
NX Pool Opt-In Mechanisms.
By using one of these three approaches, most drivers can be ported quickly and with little effort.
Avoid simply replacing all instances of NonPagedPool in your driver code with NonPagedPoolExecute. Use the
NonPagedPoolExecute pool type only for pool allocations that must be executable (for example, to run code
produced by a just-in-time, or JIT, compiler).
NX Pool Opt-In Mechanisms
6/25/2019 • 2 minutes to read • Edit Online

To port kernel-mode driver code to Windows 8 from earlier versions of Windows, you should use the
NonPagedPoolNx type of memory pool as a best practice. You can use one of several porting aids to easily "opt
in" to use the NonPagedPoolNx pool type by default.
These porting aids use one or both of the following techniques to enable the driver to use NX nonpaged pool:
Use a #define preprocessor statement to create a globally defined macro name.
Call an inline function from the DriverEntry routine.
For most kernel-mode driver code, these porting aids enable developers to update their drivers with minimal
effort.

In this section
TOPIC DESCRIPTION

Single Binary Opt-In: POOL_NX_OPTIN To build a single driver binary that runs both in
Windows 8 and in earlier versions of Windows, use the
POOL_NX_OPTIN opt-in mechanism. This is a porting aid
for third-party hardware vendors who supply a single
driver binary to support multiple Windows versions.

Multiple Binary Opt-In: POOL_NX_OPTIN_AUTO If you are a hardware vendor who supplies different driver
binaries for different versions of Windows, you can use
the POOL_NX_OPTIN_AUTO opt-in mechanism. This
porting aid builds a separate driver binary for Windows 8
and for each earlier version of Windows that your driver
supports.

Selective Opt-Out: POOL_NX_OPTOUT You can globally enable one of the no-execute (NX) pool
opt-in mechanisms for a set of driver source files, and
then override this opt-in mechanism for one or more
selected source files with POOL_NX_OPTOUT. This allows
the selected source files to continue to use executable
nonpaged memory. You can use the POOL_NX_OPTOUT
opt-out mechanism with either the POOL_NX_OPTIN or
the POOL_NX_OPTIN_AUTO opt-in mechanism.
Single Binary Opt-In: POOL_NX_OPTIN
6/25/2019 • 2 minutes to read • Edit Online

To build a single driver binary that runs both in Windows 8 and in earlier versions of Windows, use the
POOL_NX_OPTIN opt-in mechanism. This is a porting aid for third-party hardware vendors who supply a single
driver binary to support multiple Windows versions.
To use this opt-in mechanism, do the following:
Define POOL_NX_OPTIN = 1 for all source files that you want to opt-in. To do this, include the following
preprocessor definition in the appropriate property page for your driver project:
C_DEFINES=$(C_DEFINES) -DPOOL_NX_OPTIN=1

In your DriverEntry (or equivalent) routine, include the following function call:
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);

This call must occur before the driver makes any allocations that use the NonPagedPool pool type or
makes any calls to the ExInitializeNPagedLookasideList routine. ExInitializeDriverRuntime is a force
inline function and can be called on Windows 8 or later versions of Windows.
For most drivers, these two tasks are sufficient to enable the opt-in mechanism for the single driver binary.

Implementation details
POOL_NX_OPTIN works by replacing NonPagedPool with a global POOL_TYPE variable,
ExDefaultNonPagedPoolType , which is initialized either to NonPagedPoolNx (for Windows 8 and later versions of
Windows) or to NonPagedPoolExecute (for earlier versions of Windows). This opt-in mechanism enables your
kernel-mode driver to run both on Windows 8, with the enhanced protection of NX pool, and on earlier versions of
Windows, which do not support NX pool. The macro that converts instances of the NonPagedPool constant
name to NonPagedPoolNx also converts instances of NonPagedPoolCacheAligned to
NonPagedPoolNxCacheAligned.

Support for static libraries (.lib projects)


You can use the POOL_NX_OPTIN opt-in mechanism for a .lib project, but projects that link to the .lib generally
must also use POOL_NX_OPTIN. At a minimum, the project that implements the DriverEntry routine must
contain the following function call:
ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
Multiple Binary Opt-In: POOL_NX_OPTIN_AUTO
12/5/2018 • 2 minutes to read • Edit Online

If you are a hardware vendor who supplies different driver binaries for different versions of Windows, you can use
the POOL_NX_OPTIN_AUTO opt-in mechanism. This porting aid builds a separate driver binary for Windows 8
and for each earlier version of Windows that your driver supports.
To use this opt-in mechanism, define POOL_NX_OPTIN_AUTO=1 for all source files that you want to opt-in. To do
this, include the following preprocessor definition in the appropriate property page for your driver project:
C_DEFINES=$(C_DEFINES) -DPOOL_NX_OPTIN_AUTO=1

For most drivers, this definition is sufficient to enable the opt-in mechanism to create a different binary for each
version of Windows that you support.

Implementation details
The POOL_NX_OPTIN_AUTO definition redefines the NonPagedPool constant name to NonPagedPoolNx.
The redefined pool type is still a compile-time constant. The macro that converts instances of the NonPagedPool
constant name to NonPagedPoolNx also converts instances of NonPagedPoolCacheAligned to
NonPagedPoolNxCacheAligned.
Selective Opt-Out: POOL_NX_OPTOUT
12/5/2018 • 2 minutes to read • Edit Online

You can globally enable one of the no-execute (NX) pool opt-in mechanisms for a set of driver source files, and
then override this opt-in mechanism for one or more selected source files with POOL_NX_OPTOUT. This allows
the selected source files to continue to use executable nonpaged memory. You can use the POOL_NX_OPTOUT
opt-out mechanism with either the POOL_NX_OPTIN or the POOL_NX_OPTIN_AUTO opt-in mechanism. For
more information, see NX Pool Opt-In Mechanisms.
To use the POOL_NX_OUTPUT opt-out mechanism to override the opt-in mechanism in a selected source file,
add the following definition to this file:
#define POOL_NX_OPTOUT 1

This definition overrides the global opt-in settings in the selected file, and prevents instances of the
NonPagedPool constant name from being replaced. Insert this definition into the file before the first instance of
NonPagedPool in the file.
An alternative to using the POOL_NX_OPTOUT opt-out mechanism in a source file is to explicitly replace each
instance of NonPagedPool in the file with NonPagedPoolExecute.
Section Objects and Views
12/5/2018 • 2 minutes to read • Edit Online

A section object represents a section of memory that can be shared. A process can use a section object to share
parts of its memory address space (memory sections) with other processes. Section objects also provide the
mechanism by which a process can map a file into its memory address space.
Each memory section has one or more corresponding views. A view of a section is a part of the section that is
actually visible to a process. The act of creating a view for a section is known as mapping a view of the section.
Each process that is manipulating the contents of a section has its own view; a process can also have multiple
views (to the same or different sections).
This section contains the following topics:
File-Backed and Page-File-Backed Sections
Managing Memory Sections
Security Issues for Section Objects and Views
File-Backed and Page-File-Backed Sections
12/5/2018 • 2 minutes to read • Edit Online

All memory sections are supported ("backed") by disk files that can contain, either temporarily or permanently, the
data to be shared. When you create a section, you can identify a specific data file to which the section will be
backed. Such sections are called file-backed sections. If you do not identify a backing file, the section is backed by
the system's paging file and the section is called a page-file-backed section. The data in file-backed sections can be
permanently written to disk. Data in page-file-backed sections is never permanently written to disk.
A file-backed section reflects the contents of an actual file on disk; in other words, it is a memory-mapped file. Any
access to memory locations within a given file-backed section corresponds to accesses to locations in the
associated file. If a process maps the view as read-only, any data that is read from the view is transparently read
from the file. Similarly, if the process maps the view as read/write, any data that is read from the view or written to
the view is transparently read from or written to the file. In either case, the view's virtual memory does not use any
space in the page files. A file-backed section can also be mapped as copy-on-write. In that case, the view's data is
read from the file, but any data written to the view is not written to the file; instead it is discarded after the final
view is unmapped and the last handle to the section is closed.
A page-file-backed section is backed by the page files instead of by any explicit file on the disk. Any changes that
are made to a page-file-backed section are automatically discarded after the section object is destroyed. Page-file-
backed sections can be used as shared memory segments between two processes.
Any section, file-backed or not, can be shared between two processes. The same physical memory address range is
mapped to a virtual memory address range within each process (though not necessarily to the same virtual
address).
Managing Memory Sections
6/25/2019 • 2 minutes to read • Edit Online

A driver can create a section object by calling ZwCreateSection, which returns a handle to the section object. Use
the FileHandle parameter to specify the backing file, or NULL if the section is not file-backed. Additional handles
to the section object can be opened by using ZwOpenSection.
To make the data that belongs to a section object accessible within the current process' address space, a view of the
section must be mapped. Drivers can map a view of a section into the current process' address space by using the
ZwMapViewOfSection routine. The SectionOffset parameter specifies the byte offset where the view begins
within the section, and the ViewSize specifies the number of bytes to be mapped.
The Protect parameter specifies the allowed operations on the view. Specify PAGE_READONLY for a read-only
view, PAGE_READWRITE for a read/write view, and PAGE_WRITECOPY for a copy-on-write view.
No physical memory is allocated for a view until the virtual memory range is accessed. The first access of the
memory range causes a page fault; the system then allocates a page to hold that memory location. If the section is
file-backed, the system reads the contents of the file that corresponds to that page and copies it into memory.
(Note that unused section objects and views do use some paged and nonpaged pool for bookkeeping purposes.)
After a driver is no longer using a view, it unmaps it by making a call to ZwUnmapViewOfSection. After the
driver is no longer using the section object, it closes the section handle with ZwClose. Note that after the view is
mapped and no other views are going to be mapped, it is safe to immediately call ZwClose on the section handle;
the view (and section object) continue to exist until the view is unmapped. This is the recommended practice
because it reduces the risk of the driver failing to close the handle.
Security Issues for Section Objects and Views
6/25/2019 • 2 minutes to read • Edit Online

Drivers that create sections and views that are not to be shared with user mode must use the following protocol
when they are working with sections and views:
The driver must use a kernel handle when it is opening a handle to the section object. Drivers can make sure
that a handle is a kernel handle by either creating it in the system process, or specifying the
OBJ_KERNEL_HANDLE attribute for the handle. For more information, see Object Handles.
The view must be mapped only from a system thread. (Otherwise, the view is accessible from the process
whose context it is created in.) A driver can make sure that the view is mapped from the system process by
using a system worker thread to perform the mapping operation. For more information, see System Worker
Threads and Driver Thread Context.
Drivers that share a view with a user-mode process must use the following protocol when they are working with
sections and views:
The driver, not the user-mode process, must create the section object and map the views.
As mentioned earlier, the driver must use a kernel handle when it is opening a handle to the section object.
Drivers can make sure that a handle is a kernel handle by either creating it in the system process, or
specifying the OBJ_KERNEL_HANDLE attribute for the handle. For more information, see Object Handles.
The view is mapped in the thread context of the process that shares the view. A highest-level driver can
guarantee the view is mapped in the current process context by performing the mapping operation in a
dispatch routine, such DispatchDeviceControl. Dispatch routines of lower-level drivers run in an arbitrary
thread context, and thus cannot safely map a view in a dispatch routine. For more information, see Driver
Thread Context.
All memory accesses to the view within the driver must be protected by try-except blocks. A malicious
user-mode application could unmap the view or change the protection state of the view. Either would cause
a system crash unless protected by a try-except block. For more information, see Handling Exceptions.
The driver must also validate the contents of the view as necessary. The driver writer cannot assume that only a
trusted user-mode component will have access to the view.
A driver that must share a section object with a user-mode application (that must be able to create its own views)
must use the following protocol:
The driver, not the user-mode process, must create the section object. Drivers must never use a handle that
was passed from user mode.
Before passing the handle to user mode, the driver must call ObReferenceObjectByHandle to obtain a
reference to the section object. This prevents a malicious application from deleting the section object by
closing the handle. The object reference should be stored in the driver's device extension.
After the driver is no longer using the section object, it must call ObDereferenceObject to release the
object reference.
On systems that run Microsoft Windows Server 2003 with Service Pack 1 (SP1) and later versions, only kernel-
mode drivers can open \Device\PhysicalMemory. However, drivers can decide to give a handle to a user
application. To prevent security issues, only user applications that the driver trusts should be given access to
\Device\PhysicalMemory.
Using MDLs
10/17/2019 • 5 minutes to read • Edit Online

An I/O buffer that spans a range of contiguous virtual memory addresses can be spread over several physical
pages, and these pages can be discontiguous. The operating system uses a memory descriptor list (MDL ) to
describe the physical page layout for a virtual memory buffer.
An MDL consists of an MDL structure that is followed by an array of data that describes the physical memory in
which the I/O buffer resides. The size of an MDL varies according to the characteristics of the I/O buffer that the
MDL describes. System routines are available to calculate the required size of an MDL and to allocate and free the
MDL.
An MDL structure is semi-opaque. Your driver should directly access only the Next and MdlFlags members of
this structure. For a code example that uses these two members, see the following Example section.
The remaining members of an MDL are opaque. Do not access the opaque members of an MDL directly. Instead,
use the following macros, which the operating system provides to perform basic operations on the structure:
MmGetMdlVirtualAddress returns the virtual memory address of the I/O buffer that is described by the MDL.
MmGetMdlByteCount returns the size, in bytes, of the I/O buffer.
MmGetMdlByteOffset returns the offset within a physical page of the beginning of the I/O buffer.
You can allocate an MDL with the IoAllocateMdl routine. To free the MDL, use the IoFreeMdl routine.
Alternatively, you can allocate a block of nonpaged memory and then format this block of memory as an MDL by
calling the MmInitializeMdl routine.
Neither IoAllocateMdl nor MmInitializeMdl initializes the data array that immediately follows the MDL
structure. For an MDL that resides in a driver-allocated block of nonpaged memory, use
MmBuildMdlForNonPagedPool to initialize this array to describe the physical memory in which the I/O buffer
resides.
For pageable memory, the correspondence between virtual and physical memory is temporary, so the data array
that follows the MDL structure is valid only under certain circumstances. Call MmProbeAndLockPages to lock
the pageable memory into place and to initialize this data array for the current layout. The memory will not be
paged out until the caller uses the MmUnlockPages routine, at which point the contents of the data array are no
longer valid.
The MmGetSystemAddressForMdlSafe routine maps the physical pages that are described by the specified
MDL to a virtual address in system address space, if they are not already mapped to system address space. This
virtual address is useful for drivers that might have to look at the pages to perform I/O, because the original
virtual address might be a user address that can be used only in its original context and can be deleted at any time.
Note that when you build a partial MDL by using the IoBuildPartialMdl routine, the caller should use
MmGetMdlVirtualAddress instead of the MmGetSystemAddressForMdlSafe routine when determining the
virtual address to pass in. IoBuildPartialMdl uses the address that MmGetMdlVirtualAddress returns from
the source MDL to determine the offset for the target MDL. If the addresses are different (for example, when the
first address is a user address), passing the address that MmGetSystemAddressForMdlSafe returns can cause
data corruption or a bug check.
When a driver calls IoAllocateMdl, it can associate an IRP with the newly allocated MDL by specifying a pointer
to the IRP as the Irp parameter of IoAllocateMdl. An IRP can have one or more MDLs associated with it. If the
IRP has a single MDL associated with it, the IRP's MdlAddress member points to that MDL. If the IRP has
multiple MDLs associated with it, MdlAddress points to the first MDL in a linked list of MDLs that are associated
with the IRP, known as an MDL chain. The MDLs are linked by their Next members. The Next member of the last
MDL in the chain is set to NULL.
If, when the driver calls IoAllocateMdl, it specifies FALSE for the SecondaryBuffer parameter, the IRP's
MdlAddress member is set to point to the new MDL. If SecondaryBuffer is TRUE, the routine inserts the new
MDL at the end of the MDL chain.
When the IRP is completed, the system unlocks and frees all the MDLs that are associated with the IRP. The
system unlocks the MDLs before it queues the I/O completion routine and frees them after the I/O completion
routine executes.
Drivers can traverse the MDL chain by using the Next member of each MDL to access the next MDL in the chain.
Drivers can manually insert MDLs into the chain by updating the Next members.
MDL chains are typically used to manage an array of buffers that are associated with a single I/O request. (For
example, a network driver could use one buffer for each IP packet in a network operation.) Each buffer in the array
has its own MDL in the chain. When the driver completes the request, it combines the buffers into a single large
buffer. The system then automatically cleans up all the allocated MDLs for the request.
The I/O manager is a frequent source of I/O requests. When the I/O manager completes an I/O request, the I/O
manager frees the IRP and frees any MDLs that are attached to the IRP. Some of these MDLs might have been
attached to the IRP by drivers that are located beneath the I/O manager in the device stack. Similarly, if your
driver is the source of an I/O request, your driver must clean up the IRP and any MDLs that are attached to the
IRP when the I/O request completes.
Example
The following code example is a driver-implemented function that frees an MDL chain from an IRP:

VOID MyFreeMdl(PMDL Mdl)


{
PMDL currentMdl, nextMdl;

for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl)


{
nextMdl = currentMdl->Next;
if (currentMdl->MdlFlags & MDL_PAGES_LOCKED)
{
MmUnlockPages(currentMdl);
}
IoFreeMdl(currentMdl);
}
}

If the physical pages that are described by an MDL in the chain are locked, the example function calls the
MmUnlockPages routine to unlock the pages before it calls IoFreeMdl to free the MDL. However, the example
function does not need to explicitly unmap the pages before it calls IoFreeMdl. Instead, IoFreeMdl automatically
unmaps the pages when it frees the MDL.
Controlling Device Access
6/25/2019 • 2 minutes to read • Edit Online

Access to a device is controlled by a security descriptor (and the ACL it contains). A security descriptor for a device
object can be specified when the device object is created, or set in the registry.
Controlling Device Access for WDM Drivers
When a WDM driver (other than certain bus drivers) creates a device object, the Plug and Play manager
determines a security descriptor for the device. The order of operations is as follows.
1. The PnP manager calls the driver's AddDevice routine.
2. The driver's AddDevice routine calls IoCreateDevice to create the device object and attach it to the device
object stack.
3. The PnP manager updates the security descriptor for the newly-created device object.
For a WDM driver, the PnP manager determines the security descriptor for the device object as follows.
1. If the device has a security descriptor setting in the registry, it is applied to every object in the device stack.
2. Otherwise, if the device's setup class has a security descriptor setting in the registry, it is applied to every
object in the device stack.
3. Otherwise, the PnP manager leaves the default security descriptor for each object unchanged. In this case,
the default security descriptor for the stack is determined by the device type and device characteristics of the
PDO.
For most device types and characteristics, the default security descriptor gives full access (GENERIC_ALL ) to
administrators, and read, write, and execute access (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE )
access to everyone else.
For more information about how to set a security descriptor for a device or device setup class in the registry, see
Setting Device Object Properties in the Registry.
If a device is operated in raw mode, then the PnP manager cannot determine a security descriptor for the device
object. In that case, the bus driver must provide a security descriptor; see below.
Controlling Device Access for WDM Bus Drivers
A WDM bus driver must provide a security descriptor for the PDO of every device that can be operated in raw
mode. Use IoCreateDeviceSecure to create the device object with a security descriptor.
If the bus driver does not operate a device in raw mode, then it is not required to supply a security descriptor. The
PnP manager determines the security descriptor, as described above. The bus driver can supply a security
descriptor if it must ensure that its PDOs have stricter security settings than the default descriptor. Any descriptor
specified by the bus driver is overridden by settings in the registry.
For more information about creating device objects, see Creating a Device Object.
Controlling Device Access for Non-WDM Drivers
Non-WDM drivers must specify a default security descriptor and class GUID for any named device objects they
create.
Use the IoCreateDeviceSecure routine to create the named device object and to specify the default security
descriptor and class GUID for that device. The security descriptor is specified in a subset of SDDL. For more
information, see SDDL for Device Objects.
The system overrides the default security descriptor with any security settings in the registry for the specified class
GUID. The driver must specify its own unique GUID for the device. Use the GuidGen tool to generate a unique
GUID. (GuidGen is included in the Microsoft Windows SDK.)
Controlling Device Namespace Access
6/25/2019 • 2 minutes to read • Edit Online

In the Windows Driver Model (WDM ), every device object has an associated namespace. Names in the device's
namespace are paths that begin with the device's name. For a device named "\Device\DeviceName", its
namespace consists of any name of the form "\Device\DeviceName\FileName". (For a file system, FileName is an
actual name of a file on the file system.)
A WDM driver receives open requests for all names in the device's namespace. The driver treats an open request
for "\Device\DeviceName" as an open of the device object itself. If the driver implements support for open
requests into the device's namespace, then it treats an open request for "\Device\DeviceName\FileName" as an
open of a "file" within the device object's namespace (where the notion of "file" for the device is driver-
determined).
Most drivers do not implement support for open operations into the device's namespace, but all drivers must
provide security checks to prevent unauthorized access to the device's namespace. By default, security checks for
file open requests within the device's namespace, (for example, "\Device\DeviceName\FileName") are left entirely
up to the driver—the device object ACL is not checked by the operating system.
If a device object's FILE_DEVICE_SECURE_OPEN characteristic is set, the system applies the device object's
security descriptor to all file open requests in the device's namespace. Drivers can set
FILE_DEVICE_SECURE_OPEN when they create the device object with IoCreateDevice or
IoCreateDeviceSecure. For WDM drivers, FILE_DEVICE_SECURE_OPEN can also be set in the registry. It can
also be set in the registry for device objects of non-WDM drivers that are created by IoCreateDeviceSecure. For
more information about setting device object properties, such as the device characteristics, in the registry, see
Setting Device Object Properties in the Registry. For more information about device characteristics, see Specifying
Device Characteristics.
Drivers for devices that do not support namespaces must use one of two methods to ensure that file open
requests within the device's namespace are handled correctly:
The driver's device objects have the FILE_DEVICE_SECURE_OPEN device characteristic set. The driver can
then treat any open request into the device's namespace as an open request for the device object.
The driver can fail any IRP_MJ_CREATE requests that specify an IrpSp->FileObject->FileName
parameter whose length is nonzero. In this case, open requests for the device are subject to the system's
ACL check, while all file open requests within the device's namespace are failed by the driver. (Drivers that
support exclusive opens must use this option.)
Drivers for devices that do support namespaces can also use two methods to secure file open requests into the
device's namespace:
The driver's device objects have the FILE_DEVICE_SECURE_OPEN device characteristic set. This ensures
that the security settings for the device apply uniformly to the device's namespace. (The driver is
responsible for implementing support for the namespace in its DRIVER_DISPATCH callback function.)
The driver checks any ACLs for the file name in its DispatchCreate routine. (Even in this case the driver
should set the FILE_DEVICE_SECURE_OPEN characteristic unless opens into the device's namespace can
have weaker security settings than the device object.)
The FILE_DEVICE_SECURE_OPEN characteristic is checked at the top of the stack, so filter device objects must
copy the Characteristics member of the next-lower device object after attaching.
SDDL for Device Objects
6/25/2019 • 6 minutes to read • Edit Online

The Security Descriptor Definition Language (SDDL ) is used to represent security descriptors. Security for device
objects can be specified by an SDDL string that is placed in an INF file or passed to IoCreateDeviceSecure. The
Security Descriptor Definition Language is fully documented in the Microsoft Windows SDK documentation.
While INF files support the full range of SDDL, only a subset of the language is supported by the
IoCreateDeviceSecure routine. This subset is defined here.
SDDL strings for device objects are of the form "D:P" followed by one or more expressions of the form "
(A;;Access;;;SID)". The SID value specifies a security identifier that determines to whom the Access value applies (for
example, a user or group). The Access value specifies the access rights allowed for the SID. The Access and SID
values are as follows.
Note When using SDDL for device objects, your driver must link against Wdmsec.lib.
Access
Specifies an ACCESS_MASK value that determines the allowed access. This value can be written either as a
hexadecimal value in the form "0xhex", or as a sequence of two-letter symbolic codes that represent access rights.
The following codes can be used to specify generic access rights.

CODE GENERIC ACCESS RIGHT

GA GENERIC_ALL

GR GENERIC_READ

GW GENERIC_WRITE

GX GENERIC_EXECUTE

The following codes can be used to specify specific access rights.

CODE SPECIFIC ACCESS RIGHT

RC READ_CONTROL

SD DELETE

WD WRITE_DAC

WO WRITE_OWNER

Note that GENERIC_ALL grants all the rights listed in the above two tables, including the ability to change the
ACL.
SID
Specifies the SID that is granted the specified access. SIDs represent accounts, aliases, groups, users, or
computers.
The following SIDs represent accounts on the machine.

SID DESCRIPTION

SY System
Represents the operating system itself, including its user-
mode components.

LS Local Service
A predefined account for local services (which also
belongs to Authenticated and World). This SID is available
starting with Windows XP.

NS Network Service
A predefined account for network services (which also
belongs to Authenticated and World). This SID is available
starting with Windows XP.

The following SIDs represent groups on the machine.

SID DESCRIPTION

BA Administrators
The built-in Administrators group on the machine.

BU Built-in User Group


Group covering all local user accounts, and users on the
domain.

BG Built-in Guest Group


Group covering users logging in using the local or domain
guest account.

The following SIDs describe the extent to which a user has been authenticated.

SID DESCRIPTION

AU Authenticated Users
Any user recognized by the local machine or by a domain.
Note that users logged in using the Builtin Guest account
are not authenticated. However, members of the Guests
group with individual accounts on the machine or the
domain are authenticated.
SID DESCRIPTION

AN Anonymous Logged-on User


Any user logged on without an identity, such as an
anonymous network session. Note that users logging in
using the Builtin Guest account are neither authenticated
nor anonymous. This SID is available starting with
Windows XP.

The following SIDs describe how the user logged into the machine.

SID DESCRIPTION

IU Interactive Users
Users who initially logged onto the machine
"interactively", such as local logons and Remote Desktops
logons.

NU Network Logon User


Users accessing the machine remotely, without interactive
desktop access (for example, file sharing or RPC calls).

WD World
Before Windows XP, this SID covered every session,
whether authenticated users, anonymous users, or the
Builtin Guest account.
Starting with Windows XP, this SID does not cover
anonymous logon sessions; it covers only authenticated
users and the Builtin Guest account.
Note that untrusted or "restricted" code is also not
covered by the World SID. For more information, see the
description of the Restricted Code (RC) SID in the
following table.

The following SIDs deserve special mention.

SID DESCRIPTION
SID DESCRIPTION

RC Restricted Code
This SID is used to control access by untrusted code. ACL
validation against tokens with RC consists of two checks,
one against the token's normal list of SIDs (containing WD
for instance), and one against a second list (typically
containing RC and a subset of the original token SIDs).
Access is granted only if a token passes both tests. As
such, RC actually works in combination with other SIDs.
Any ACL that specifies RC must also specify WD. When RC
is paired with WD in an ACL, a superset of Everyone
including untrusted code is described.
Untrusted code might be code launched using the Run As
option in Explorer. By default, World does not cover
untrusted code.

UD User-Mode Drivers
This SID grants access to user-mode drivers. Currently,
this SID covers only drivers that are written for the User-
Mode Driver Framework (UMDF). This SID is available
starting with Windows 8.
In earlier versions of Windows, which do not recognize the
"UD" abbreviation, you must specify the fully qualified
form of this SID (S-1-5-84-0-0-0-0-0) to grant access to
UMDF drivers. For more information, see Controlling
Device Access in the User-Mode Driver Framework
documentation.

SDDL Examples For Device Objects


This section describes the predefined SDDL strings found in Wdmsec.h. You can also use these as templates to
define new SDDL strings for device objects.
SDDL_DEVOBJ_KERNEL_ONLY
"D:P"
SDDL_DEVOBJ_KERNEL_ONLY is the "empty" ACL. User-mode code (including processes running as system)
cannot open the device.
A PnP bus driver could use this descriptor when creating a PDO. The INF file could then specify looser security
settings for the device. By specifying this descriptor, the bus driver would ensure that no attempt to open the
device before the INF was processed would succeed.
Similarly, a non-WDM driver could use this descriptor to make its device objects inaccessible until the appropriate
user-mode program (such as an installer) sets the final security descriptor in the registry.
In all of these cases, the default is tight security, loosened as necessary.
SDDL_DEVOBJ_SYS_ALL
"D:P (A;;GA;;;SY )"
SDDL_DEVOBJ_SYS_ALL is similar to SDDL_DEVOBJ_KERNEL_ONLY, except that in addition to kernel-mode
code, user-mode code running as System is also allowed to open the device for any access.
A legacy driver might use this ACL to start with tight security settings, and let its service open the device up at run
time to individual users by using the SetFileSecurity user-mode function. In this case, the service would have to
be running as System.
SDDL_DEVOBJ_SYS_ALL_ADM_ALL
"D:P (A;;GA;;;SY )(A;;GA;;;BA )"
SDDL_DEVOBJ_SYS_ALL_ADM_ALL allows the kernel, system, and administrator complete control over the
device. No other users may access the device.
SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R
"D:P (A;;GA;;;SY )(A;;GRGWGX;;;BA )(A;;GR;;;WD )"
SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R allows the kernel and system complete control over the
device. By default the administrator can access the entire device, but cannot change the ACL (the administrator
must take control of the device first.)
Everyone (the World SID ) is given read access. Untrusted code cannot access the device (untrusted code might be
code launched using the Run As option in Explorer. By default, World does not cover Restricted code.)
Also note that traversal access is not granted to normal users. As such, this might not be an appropriate descriptor
for a device with a namespace.
SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R_RES_R
"D:P (A;;GA;;;SY )(A;;GRGWGX;;;BA )(A;;GR;;;WD )(A;;GR;;;RC )"
SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R_RES_R allows the kernel and system complete control over
the device. By default the administrator can access the entire device, but cannot change the ACL (the administrator
must take control of the device first.)
Everyone (the World SID ) is given read access. In addition, untrusted code is also allowed to access code.
Untrusted code might be code launched using the Run As option in Explorer. By default, World does not cover
Restricted code.
Also note that traversal access is not granted to normal users. As such, this might not be an appropriate descriptor
for a device with a namespace.
Note that the above SDDL strings do not include any inheritance modifiers. As such, they are only appropriate for
device objects and should not be used for files or registry keys. For more information about specifying inheritance
using SDDL, see the Microsoft Windows SDK documentation.
Access Rights
6/25/2019 • 2 minutes to read • Edit Online

An access right is the right to perform a particular operation on the object. For example, the FILE_READ_DATA
access right specifies the right to read from a file.
When you open a handle to an object, you specify a set of access rights corresponding to the operations that may
be performed on the object. The system checks the specified access rights against the object's security descriptor to
see if each operation is permitted for the current user. (For more information, see Security Descriptors.)
Access rights come in two types:
A specific access right is a right to perform a single operation. Specific access rights can depend on the type of
object.
A generic access right is a right to perform one of a set of similar operations. Generic access rights are independent
of the type of object.
Standard access rights are specific access rights that apply to all types of objects. For example, the DELETE access
right is the right to delete an object, regardless of type. For more information about the available standard access
rights, see ACCESS_MASK.
Objects also have specific access rights that depend on the type of the object. For example, the FILE_READ_DATA
represents the right to read from a file, while the KEY_QUERY_VALUE represents the right to read the value entries
for a registry key.
An object type can have zero, one, or more access rights that correspond to the general notion of reading from or
writing to an object. For example, in addition to FILE_READ_DATA, file objects have the FILE_READ_ATTRIBUTES
access right, which represents to read a file's metadata (such as file creation time). Key objects have both
KEY_QUERY_VALUE and KEY_ENUMERATE_SUBKEYS, which represents the right to read the subkeys of a key.
To simplify specifying all access rights that correspond to a general notion such as reading or writing, the system
provides generic access rights. The system maps a generic access right to the appropriate set of specific access
rights for the object.
The system provides the following generic access rights:
GENERIC_READ
GENERIC_WRITE
GENERIC_EXECUTE
GENERIC_ALL
Thus, the system maps GENERIC_READ to a set of rights that includes FILE_READ_DATA and
FILE_READ_ATTRIBUTES for a file, and KEY_QUERY_VALUE and KEY_ENUMERATE_SUBKEYS for a key. For
more information about each generic access right, see ACCESS_MASK.
Security Descriptors
6/25/2019 • 2 minutes to read • Edit Online

Every object has a security descriptor, which contains the security settings for an object. In kernel-mode, the opaque
SECURITY_DESCRIPTOR data type represents a security descriptor.
Information in a security descriptor is stored in access control lists (ACLs). An access control list is made up of a
series of access control entries (ACEs).
A security descriptor has two separate ACLs:
A system ACL (SACL ), which determines which operations on an object are logged.
A discretionary ACL (DACL ), which determines which users can perform particular operations on the object.
Typically, a driver developer is only concerned with discretionary ACLs. For more information about system ACLs,
see the Microsoft Windows SDK.
For a discretionary ACL, each ACE contains three pieces of information:
A security identifier (SID ). The security identifier determines who the ACE applies to. A SID can represent a
single user, or a group of users. For example, the World SID represents the set of all users.
A set of access rights. For a description of access rights, see Access Rights.
Whether the set of access rights is granted, or denied.
For a driver, the most important security descriptors are those for the driver's device objects. For more information,
see Securing Device Objects.
For more information about security descriptors in general, see the Windows SDK.
Privileges
6/25/2019 • 2 minutes to read • Edit Online

A privilege is a right that is associated with a process, rather than an object. A typical example of a privilege is
SeBackupPrivilege, which confers on a process the right to back up files on a disk.
A few routines check the privilege of the current process before completing an operation. If a driver routine is
executed by the system process, then the operation always succeeds, but if the driver routine is executed by a user
process that does not have the required privilege, then the operation can fail.
The following table lists some examples of privileges and routines that can require them to succeed.

PRIVILEGE ROUTINE THAT CAN REQUIRE PRIVILEGE

SeManageVolumePrivilege ZwSetInformationFile with FileInformationClass =


FileValidDataLengthInformation

SeTakeOwnershipPrivilege SeAccessCheck

SeSecurityPrivilege SeAccessCheck

Most system routines do not perform any privilege checks.


Handling IRPs
12/5/2018 • 2 minutes to read • Edit Online

This section describes how kernel-mode drivers handle I/O request packets (IRPs). It contains the following
sections:
Overview of the Windows I/O Model
End-User I/O Requests and File Objects
The Life of an I/O Request
I/O Stack Locations
I/O Status Blocks
Passing IRPs down the Driver Stack
Creating IRPs for Lower-Level Drivers
Queuing and Dequeuing IRPs
Completing IRPs
Canceling IRPs
Reusing IRPs
Device Type-Specific I/O Requests
Using I/O Control Codes
Using IRP Priority Hints
IRP Major Function Codes
IRP Processing Examples
Overview of the Windows I/O Model
12/5/2018 • 2 minutes to read • Edit Online

Every operating system has an implicit or explicit I/O model for handling the flow of data to and from peripheral
devices. One feature of the Microsoft Windows I/O model is its support for asynchronous I/O. In addition, the I/O
model has the following general features:
The I/O manager presents a consistent interface to all kernel-mode drivers, including lowest-level,
intermediate, and file system drivers. All I/O requests to drivers are sent as I/O request packets (IRPs).
I/O operations are layered. The I/O manager exports I/O system services, which user-mode protected
subsystems call to carry out I/O operations on behalf of their applications and/or end users. The I/O
manager intercepts these calls, sets up one or more IRPs, and routes them through possibly layered drivers
to physical devices.
The I/O manager defines a set of standard routines, some required and others optional, that drivers can
support. All drivers follow a relatively consistent implementation model, given the differences among
peripheral devices and the differing functionality required of bus, function, filter, and file system drivers.
Like the operating system itself, drivers are object-based. Drivers, their devices, and system hardware are
represented as objects. The I/O manager and other operating system components export kernel-mode
support routines that drivers can call to get work done by manipulating the appropriate objects.
In addition to using IRPs to convey traditional I/O requests, the I/O manager works with the PnP and power
managers to send IRPs containing PnP and power requests.
End-User I/O Requests and File Objects
12/5/2018 • 2 minutes to read • Edit Online

Kernel-mode drivers are hidden from end users by a protected subsystem that implements an already familiar
programming interface, such as Windows or POSIX. Devices are visible to user-mode code, which includes
protected subsystems, only as named file objects controlled by the I/O manager.
The following figure illustrates this relationship between an end user, a subsystem, and the I/O manager.

A protected subsystem, such as the Win32 subsystem, passes I/O requests to the appropriate kernel-mode driver
through the I/O system services. The subsystem shown in the previous figure depends on support from the
display, video adapter, keyboard, and mouse device drivers.
A protected subsystem insulates its end users and applications from having to know anything about kernel-mode
components, including drivers. In turn, the I/O manager insulates protected subsystems from having to know
anything about machine-specific device configurations or about drivers' implementations.
The I/O manager's layered approach also insulates most drivers from having to know anything about the
following:
Whether an I/O request originated in any particular protected subsystem, such as Win32 or POSIX
Whether a given protected subsystem has particular kinds of user-mode drivers
What any protected subsystem's I/O model and interface to drivers is
The I/O manager supplies drivers with a single I/O model, a set of kernel-mode support routines that drivers can
use to carry out I/O operations, and a consistent interface between the originator of an I/O request and the drivers
that must respond to it.
As shown in the previous figure, a subsystem and its native applications can access a driver's device or a file on a
mass-storage device only through file object handles supplied by the I/O manager. To open such a file object or to
obtain a handle for I/O to a device or a data file, a subsystem calls the I/O system services with a request to open a
named file. The named file can have a subsystem-specific alias (symbolic link) to the kernel-mode name for the file
object.
The I/O manager, which exports these system services, is then responsible for locating or creating the file object
that represents the device or data file and for locating the appropriate driver(s).
Example I/O Request - An Overview
12/5/2018 • 2 minutes to read • Edit Online

The following figure shows an overview of what happens when a subsystem opens a file object representing a data
file on behalf of an application.

1. The subsystem calls an I/O system service to open a named file.


2. The I/O manager calls the object manager to look up the named file and to help it resolve any symbolic links
for the file object. It also calls the security reference monitor to check that the subsystem has the correct
access rights to open that file object.
3. If the volume is not yet mounted, the I/O manager suspends the open request temporarily and calls one or
more file systems until one of them recognizes the file object as something it has stored on one of the mass-
storage devices the file system uses. When the file system has mounted the volume, the I/O manager
resumes the request.
4. The I/O manager allocates memory for and initializes an IRP for the open request. To drivers, an open is
equivalent to a "create" request.
5. The I/O manager calls the file system driver, passing it the IRP. The file system driver accesses its I/O stack
location in the IRP to determine what operation it must carry out, checks parameters, determines if the
requested file is in cache, and, if not, sets up the next-lower driver's I/O stack location in the IRP.
6. Both drivers process the IRP and complete the requested I/O operation, calling kernel-mode support
routines supplied by the I/O manager and by other system components (not shown in the previous figure).
7. The drivers return the IRP to the I/O manager with the I/O status block set in the IRP to indicate whether
the requested operation succeeded or why it failed.
8. The I/O manager gets the I/O status from the IRP, so it can return status information through the protected
subsystem to the original caller.
9. The I/O manager frees the completed IRP.
10. The I/O manager returns a handle for the file object to the subsystem if the open operation was successful.
If there was an error, it returns appropriate status to the subsystem.
After a subsystem successfully opens a file object that represents a data file, a device, or a volume, the subsystem
uses the returned handle to identify the file object in subsequent requests for device I/O operations (usually read,
write, or device I/O control requests). To make such a request, the subsystem calls I/O system services. The I/O
manager routes these requests as IRPs sent to appropriate drivers.
Example I/O Request - The Details
6/25/2019 • 6 minutes to read • Edit Online

The figure illustrating opening a file object shows an IRP with two I/O stack locations, but an IRP can have any
number of I/O stack locations, depending on how many layered drivers will handle a given request.
The following figure illustrates in more detail how the drivers in the Opening a File Object figure use I/O support
routines (IoXxx routines) to process the IRP for a read or write request.

1. The I/O manager calls the file system driver (FSD ) with the IRP it has allocated for the subsystem's
read/write request. The FSD accesses its I/O stack location in the IRP to determine what operation it should
carry out.
2. The FSD can break the original request into smaller requests (possibly for more than one device driver) by
calling an I/O support routine (IoAllocateIrp) one or more times to allocate additional IRPs. The additional
IRPs are returned to the FSD with zero-filled I/O stack location(s) for lower-level driver(s). At its discretion,
the FSD can reuse the original IRP, rather than allocating additional IRPs as shown in the previous figure, by
setting up the next-lower driver's I/O stack location in the original IRP and passing it on to lower drivers.
3. For each driver-allocated IRP, the FSD in the previous figure calls an I/O support routine to register an
FSD -supplied completion routine; in the completion routine, the FSD can determine whether lower drivers
satisfied the request and can free each driver-allocated IRP when lower drivers have completed it. The I/O
manager will call the FSD -supplied completion routine whether each driver-allocated IRP was completed
successfully, completed with an error status, or canceled. A higher-level driver is responsible for freeing any
IRPs it allocates and sets up on its own behalf for lower-level drivers. The I/O manager frees the IRPs that it
allocates after all drivers have completed them.
Next, the FSD calls an I/O support routine ( IoGetNextIrpStackLocation) to access the next-lower-level
driver's I/O stack location in order to set up the request for the next-lower driver. (In the previous figure, the
next lower driver happens to be the lowest-level driver.) The FSD then calls an I/O support routine
(IoCallDriver) to pass that IRP on to the next-lower driver.
4. When it is called with the IRP, the lowest-level driver checks its I/O stack location to determine what
operation (indicated by the IRP_MJ_XXX function code) it should carry out on the target device. The target
device is represented by the device object in its designated I/O stack location and is passed with the IRP to
the driver. The lowest-level driver can assume that the I/O manager has routed the IRP to an entry point
that the driver defined for the IRP_MJ_XXX operation (here IRP_MJ_READ or IRP_MJ_WRITE ) and that
the higher-level driver has checked the validity of other parameters for the request.
If there were no higher-level driver, the lowest-level driver would check whether the input parameters for an
IRP_MJ_XXX operation are valid. If they are, the driver usually calls I/O support routines to tell the I/O
manager that a device operation is pending on the IRP and to either queue the IRP or pass it on to another
driver-supplied routine that accesses the target device (here, a physical or logical device: the disk or a
partition on the disk).
5. The I/O manager determines whether the driver is already busy processing another IRP for the target
device, queues the IRP if it is, and returns. Otherwise, the I/O manager routes the IRP to a driver-supplied
routine that starts the I/O operation on its device. (At this stage, both drivers in the previous figure and the
I/O manager return control.)
6. When the device interrupts, the driver's interrupt service routine (ISR ) does only as much work as it must to
stop the device from interrupting and to save necessary context about the operation. The ISR then calls an
I/O support routine (IoRequestDpc) with the IRP to queue a driver-supplied DPC (Deferred Procedure
Call) routine to complete the requested operation at a lower hardware priority than the ISR.
7. When the driver's DPC gets control, it uses the context (passed in the ISR's call to IoRequestDpc) to
complete the I/O operation. The DPC calls a support routine to dequeue the next IRP (if any) and to pass
that IRP on to the driver-supplied routine that starts I/O operations on the device (see Step 5). The DPC
then sets status about the just-completed operation in the IRP's I/O status block and returns it to the I/O
manager with IoCompleteRequest.
8. The I/O manager zeros the lowest-level driver's I/O stack location in the IRP and calls the file system's
registered completion routine (see Step 3) with the FSD -allocated IRP. This completion routine checks the
I/O status block to determine whether to retry the request or to update any internal state maintained about
the original request and to free its driver-allocated IRP. The file system can collect status information for all
driver-allocated IRPs it sends to lower-level drivers so that it can set I/O status and complete the original
IRP. When the file system has completed the original IRP, the I/O manager returns and NTSTATUS value to
the original requester (the subsystem's native function) of the I/O operation.
Like the file system driver shown in the Processing IRPs in Layered Drivers figure, any new driver that is added to
a chain of existing drivers can do all of the following:
Set its own completion routine into an IRP. The IoCompletion routine checks the I/O status block to
determine whether lower drivers completed the IRP successfully, canceled the IRP, and/or completed it with
an error. The completion routine can also update any IRP -specific state the driver might have saved, release
any operation-specific resources the driver might have allocated, and so forth, before completing the IRP. In
addition, the completion routine can postpone IRP completion (by informing the I/O manager that more
processing is required on the IRP ), and can send another request to the next-lower-level driver before
allowing the IRP to complete.
Set up the next-lower-level driver's I/O stack location in the IRPs it allocates and send requests to the next-
lower-level driver.
Pass any incoming requests on to lower drivers by setting up the next-lower driver's I/O stack location in
each IRP and calling IoCallDriver. (Note that for IRPs with major function code IRP_MJ_POWER, drivers
must use PoCallDriver.)
Each driver-created device object represents a physical, logical, or virtual device for which a particular driver carries
out I/O requests. For detailed information about creating and setting up a device object, see Device Objects and
Device Stacks.
As the Processing IRPs in Layered Drivers figure also shows, most drivers process each IRP in stages through a
driver-supplied set of system-defined standard routines, but drivers at different levels in a chain necessarily have
different standard routines. For example, only lowest-level drivers handle interrupts from a physical device, so only
a lowest-level driver would have an ISR and a DPC that completes interrupt-driven I/O operations. On the other
hand, because such a driver knows that I/O is complete when it receives an interrupt from its device, it has no need
for a completion routine. Only a higher-level driver would have one or more completion routines like the FSD in
this figure.
Driver Thread Context
12/5/2018 • 2 minutes to read • Edit Online

As shown in the Processing IRPs in Layered Drivers figure, a file system is a two-part driver:
1. A file system driver (FSD ), which executes in the context of a user-mode thread that calls an I/O system
service
The I/O manager sends the corresponding IRP to the FSD. If the FSD sets up a completion routine for an
IRP, its completion routine is not necessarily called in the original user-mode thread's context.
2. A set of file system threads, and possibly an FSP (file system process)
An FSD can create a set of driver-dedicated system threads, but most FSDs use system worker threads in
order to get work done without tying up user-mode threads that make I/O requests. Any FSD might set up
its own process address space in which its driver-dedicated threads execute, but the system-supplied FSDs
avoid this practice to conserve system memory.
File systems generally use system worker threads to set up and manage internal work queues of IRPs that they
send to one or more lower-level drivers, possibly for different devices.
While the lowest-level driver shown in the Processing IRPs in Layered Drivers figure processes each IRP in stages
through a set of discrete, driver-supplied routines, it does not use system threads as the file system does. A lowest-
level driver does not need its own thread context unless setting up its device for I/O is such a protracted process
that it has a noticeable effect on system performance. Few lowest-level or intermediate drivers need to set up their
own driver-dedicated or device-dedicated system threads, and those that do pay a performance penalty caused by
context switches to their threads.
Most kernel-mode drivers, like the physical device driver in the Processing IRPs in Layered Drivers figure, execute
in an arbitrary thread context: that of whatever thread happens to be current when they are called to process an
IRP. Consequently, drivers usually maintain state about their I/O operations and the devices they service in a
driver-defined part of their device objects, called a device extension.
Points to Consider about User I/O Requests
6/25/2019 • 3 minutes to read • Edit Online

Keep the following points in mind when designing a kernel-mode driver:


Drivers can be layered, and more than one driver can process a single I/O request (IRP ).
A driver cannot make any assumptions about which other drivers will be in the device stack. Therefore, each
driver should be prepared to receive requests from any other driver and should handle all potential error
cases.
Drivers communicate the success or failure of a requested I/O operation in the I/O status block of the IRP.
The I/O manager communicates the success or failure of a requested I/O operation to a user-mode
requester.
Drivers need not and should not be designed to provide application-specific support. A protected subsystem
or its subsystem-specific, user-mode drivers supply this kind of support. There is one exception to this rule:
an MS -DOS application that relies on an application-dedicated device can require a kernel-mode driver to
control the device and a closely coupled Win32 user-mode virtual device driver (VDD ). For more
information about VDDs, see the Virtual Device Drivers documentation in the Windows Driver
Development Kit (DDK). (The DDK preceded the Windows Driver Kit [WDK].)
A new driver must handle the same set of IRP_MJ_XXX as any system-supplied driver it replaces. The I/O
manager returns STATUS_INVALID_DEVICE_REQUEST for a given I/O request to a target device if its
driver does not define an entry point for that IRP_MJ_*XXX. A device driver also must handle the same I/O
control codes for IRP_MJ_DEVICE_CONTROL* requests as any system-supplied driver it replaces. In other
words, a new device driver must not "break applications" by implementing less functionality than an existing
driver for the same type of device.
A new intermediate driver inserted into a chain of existing drivers should recognize the same set of
IRP_MJ_XXX as the driver it displaces. The new driver can simply pass on IRPs for those requests that it
does not process to the next-lower-level driver. However, a new intermediate driver must not "break the
chain" for drivers above and below it by neglecting to define an entry point for an IRP_MJ_XXX request
that the newly displaced, next-lower-level driver does handle.
A lowest-level driver can access only its own I/O stack location in any IRP that it is sent. A higher-level
driver can access only its own and the next-lower-level driver's I/O stack locations in any IRP that it is sent.
Every driver communicates information to higher-level drivers (and ultimately, to user-mode applications
via the I/O manager) only in the I/O status blocks of IRPs because the I/O manager zeros the corresponding
I/O stack location as each driver in a chain completes an IRP. Any new driver that attempts to implement
back-door communication with a particular higher (or lower) driver compromises its portability and its
interoperability with other drivers from one Windows platform or version to the next.
A pair of drivers can define a set of device-specific (also called private) I/O control codes for
IRP_MJ_INTERNAL_DEVICE_CONTROL requests that the higher of the pair can send down to the lower
of the pair. However, such a pair of drivers must follow all of the preceding guidelines if they are to remain
portable and interoperable with other drivers from one Windows platform or version to another. If you
design a pair of drivers with a private interface, consider the set of I/O control codes to be defined carefully.
Make them as generally useful as possible and design your paired drivers to follow the preceding guidelines,
so that you (or someone else) can reuse, replace, or displace either or both of your new drivers easily as they
migrate from one Windows platform or version to another.
IRP Major Function Codes
7/9/2019 • 2 minutes to read • Edit Online

Each driver-specific I/O stack location (IO_STACK_LOCATION for the major function codes that it must support.
The specific operations a driver carries out for a given IRP_MJ_XXX code depend somewhat on the underlying
device, particularly for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL requests.
For example, the requests sent to a keyboard driver are necessarily somewhat different from those sent to a disk
driver. However, the I/O manager defines the parameters and I/O stack location contents for each system-defined
major function code.
Every higher-level driver must set up the appropriate I/O stack location in IRPs for the next-lower-level driver and
call IoCallDriver, either with each input IRP, or with a driver-created IRP (if the higher-level driver holds on to the
input IRP ). Consequently, every intermediate driver must supply a dispatch routine for each major function code
that the underlying device driver handles. Otherwise, a new intermediate driver will "break the chain" whenever an
application or still higher-level driver attempts to send an I/O request down to the underlying device driver.
File system drivers also handle a required subset of system-defined IRP_MJ_XXX function codes, some with
subordinate IRP_MN_XXX function codes.
Drivers handle IRPs set with some or all of the following major function codes:
IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
IRP_MJ_QUERY_INFORMATION
IRP_MJ_READ
IRP_MJ_SET_INFORMATION
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE
The input and output parameters described in this section are the function-specific parameters in the IRP.
For more information about IRPs, see Handling IRPs.
IRP_MJ_CLEANUP
6/25/2019 • 2 minutes to read • Edit Online

Drivers that maintain process-specific context information must handle cleanup requests in DispatchCleanup
routines.

When Sent
Receipt of this request indicates that the last handle for a file object that is associated with the target device object
has been closed (but, due to outstanding I/O requests, might not have been released).

Input Parameters
None

Output Parameters
None

Operation
This IRP is sent in the context of the process that closed the file object handle. Therefore, the driver should release
process-specific resources, such as user memory, that the driver previously locked or mapped.
If the driver's device objects were set up as exclusive, so that only a single thread can use the device at a time, the
driver must complete every IRP that is currently queued to the target device object and set STATUS_CANCELLED
in each IRP's I/O status block.
Otherwise, the driver must cancel and complete only the currently queued IRPs that are associated with the file
object handle that is being released. (A pointer to the file object is located in the FileObject member of the driver's
IO_STACK_LOCATION of the IRP.) After canceling these queued IRPs, the driver completes the cleanup IRP and
sets STATUS_SUCCESS in its I/O status block.
For more information about handling this request, see DispatchCleanup Routines.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchCleanup
IO_STACK_LOCATION
IRP_MJ_CLOSE
IRP_MJ_CLOSE
12/5/2018 • 2 minutes to read • Edit Online

Every driver must handle close requests in a DispatchClose routine, with the possible exception of a driver whose
device cannot be disabled or removed from the machine without bringing down the system. A disk driver whose
device holds the system page file is an example of such a driver. Note that the driver of such a device also cannot
be unloaded dynamically.

When Sent
Receipt of this request indicates that the last handle of the file object that is associated with the target device object
has been closed and released. All outstanding I/O requests have been completed or canceled.

Input Parameters
None

Output Parameters
None

Operation
Many device and intermediate drivers merely set STATUS_SUCCESS in the I/O status block of the IRP and
complete the close request. However, what a given driver does on receipt of a close request depends on the
driver's design. In general, a driver should undo whatever actions it takes on receipt of the IRP_MJ_CREATE
request. Device drivers whose device objects are exclusive, such as a serial driver, also can reset the hardware on
receipt of a close request.
The IRP_MJ_CLOSE request is not necessarily sent in the context of the process that closed the file object handle.
If the driver must release process-specific resources, such as user memory, that the driver previously locked or
mapped, it must do so in response to an IRP_MJ_CLEANUP request.
The IRP_MJ_CLOSE request will always be sent at PASSIVE_LEVEL.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchClose
IRP_MJ_CLEANUP
IRP_MJ_CREATE
IRP_MJ_CREATE
6/25/2019 • 2 minutes to read • Edit Online

Every kernel-mode driver must handle IRP_MJ_CREATE requests in a DRIVER_DISPATCH callback function.

When Sent
The operating system sends an IRP_MJ_CREATE request to open a handle to a file object or device object. For
example, when a driver calls ZwCreateFile, the operating system sends an IRP_MJ_CREATE request to perform
the actual open operation.

Input Parameters
The Parameters.Create.SecurityContext member points to an IO_SECURITY_CONTEXT structure that
describes the security context for the request. The Parameters.Create.SecurityContext->DesiredAccess
member is an access mask that specifies the access rights that are being requested by the caller.
The Parameters.Create.Options member is a ULONG value that describes the options that are used when
opening the handle. The high 8 bits correspond to the value of the CreateDisposition parameter of ZwCreateFile,
and the low 24 bits correspond to the value of the CreateOptions parameter of ZwCreateFile.
The Parameters.Create.ShareAccess member is a USHORT value that describes the type of share access. This
value corresponds to the value of the ShareAccess parameter of ZwCreateFile.
The Parameters.Create.FileAttributes and Parameters.Create.EaLength members are reserved for use by file
systems and file system filter drivers. For more information, see the IRP_MJ_CREATE topic in the Installable File
System (IFS ) documentation.

Output Parameters
None

Operation
Most device and intermediate drivers set STATUS_SUCCESS in the I/O status block of the IRP and complete the
create request, but drivers can optionally use their DRIVER_DISPATCH callback function to reserve resources for
any subsequent I/O requests for that handle. For example, the system serial driver maps its paged-out code and
allocates any resources that are necessary to handle subsequent I/O requests for the user-mode thread that is
attempting to open the device for input and output.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DRIVER_DISPATCH
DispatchCreateClose
IO_SECURITY_CONTEXT
ZwCreateFile
IRP_MJ_DEVICE_CONTROL
6/25/2019 • 2 minutes to read • Edit Online

Every driver whose device objects belong to a particular device type (see Specifying Device Types) is required to
support this request in a DispatchDeviceControl routine, if a set of system-defined I/O control codes (IOCTLs)
exists for the type. For more info about IOCTLs, see Introduction to I/O Control Codes.
Higher-level drivers usually pass these requests on to an underlying device driver. Each device driver in a driver
stack is assumed to support this request, along with a set of device type-specific, public or private IOCTLs. For
more information about IOCTLs for specific device types, see device type-specific documentation in the Microsoft
Windows Driver Kit (WDK).

When Sent
Any time following the successful completion of a create request.

Input Parameters
The I/O control code is contained at Parameters.DeviceIoControl.IoControlCode in the driver's I/O stack
location of the IRP.
Other input parameters depend on the I/O control code's value. For more information, see Buffer Descriptions for
I/O Control Codes.

Output Parameters
Output parameters depend on the I/O control code's value. For more information, see Buffer Descriptions for I/O
Control Codes.

Operation
A driver receives this I/O control code because user-mode thread has called the Microsoft Win32
DeviceIoControl function, or a higher-level kernel-mode driver has set up the request. Possibly, a user-mode
driver has called DeviceIoControl, passing in a driver-defined (also called private) I/O control code, to request
device- or driver-specific support from a closely coupled, kernel-mode device driver.
On receipt of a device I/O control request, a higher-level driver usually passes the IRP on to the next-lower driver.
However, there are some exceptions to this practice. For example, a class driver that has stored configuration
information obtained from the underlying port driver might complete certain IOCTL_XXX requests without
passing the IRP down to the corresponding port driver.
On receipt of a device I/O control request, a device driver examines the I/O control code to determine how to
satisfy the request. For most public I/O control codes, device drivers transfer a small amount of data to or from
the buffer at Irp->AssociatedIrp.SystemBuffer.
For general information about I/O control codes for IRP_MJ_DEVICE_CONTROL or
IRP_MJ_INTERNAL_DEVICE_CONTROL requests, see Using I/O Control Codes. See also Device Type-Specific
I/O Requests.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchDeviceControl
IRP_MJ_FILE_SYSTEM_CONTROL
6/25/2019 • 2 minutes to read • Edit Online

Only file system drivers process IRP_MJ_FILE_SYSTEM_CONTROL requests. For more information about the
use of this IRP major function code by file system drivers, see IRP_MJ_FILE_SYSTEM_CONTROL. For more
information about file system drivers, see File System Drivers.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)
IRP_MJ_FLUSH_BUFFERS
12/7/2018 • 2 minutes to read • Edit Online

Drivers of devices with internal caches for data and drivers that maintain internal buffers for data must handle this
request in a DispatchFlushBuffers routine.

When Sent
Receipt of a flush request indicates that the driver should flush the device's cache or its internal buffer, or, possibly,
should discard the data in its internal buffer.

Input Parameters
None

Output Parameters
None

Operation
The driver transfers any data currently cached in the device or held in the driver's internal buffers before
completing the flush request. The driver of an input-only device that buffers data internally might simply discard
the currently buffered device data before completing the flush IRP, depending on the nature of its device.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchFlushBuffers
IRP_MJ_INTERNAL_DEVICE_CONTROL
6/25/2019 • 2 minutes to read • Edit Online

In general, any replacement for an existing driver that supports internal device control requests should handle this
request in a DispatchInternalDeviceControl routine. Such a driver must support at least the same set of internal
I/O control codes as the driver it replaces. Otherwise, existing higher-level drivers might not work with the new
driver.
Drivers that replace certain lower-level system drivers are required to handle this request. For example, a
replacement for the system parallel port driver must continue to support existing parallel class drivers. Note that
certain system drivers that handle this request cannot be replaced, in particular, the system-supplied SCSI and
video port drivers.

When Sent
Any time after the successful completion of a create request.

Input Parameters
The I/O control code is contained at Parameters.DeviceIoControl.IoControlCode in the I/O stack location of
the IRP.
Other input parameters depend on the I/O control code's value. For more information, see Buffer Descriptions for
I/O Control Codes.

Output Parameters
Output parameters depend on the I/O control code's value. For more information, see Buffer Descriptions for I/O
Control Codes.

Operation
Drivers receive IRP_MJ_INTERNAL_DEVICE_CONTROL requests when another driver calls either
IoBuildDeviceIoControlRequest or IoAllocateIrp to create a request.
This I/O control code has been defined for communication between paired and layered kernel-mode drivers, such
as one or more class drivers layered over a port driver. The higher-level driver sets up IRPs with device- or driver-
specific I/O control codes, requesting support from the next-lower driver.
The requested operation is device- or driver-specific.
For general information about I/O control codes for IRP_MJ_DEVICE_CONTROL or
IRP_MJ_INTERNAL_DEVICE_CONTROL requests, see Using I/O Control Codes. See also Device Type-Specific
I/O Requests.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchInternalDeviceControl
IoAllocateIrp
IoBuildDeviceIoControlRequest
IRP_MJ_PNP
12/7/2018 • 2 minutes to read • Edit Online

All drivers must be prepared to service IRP_MJ_PNP requests in a DispatchPnP routine.

When Sent
The PnP manager sends IRP_MJ_PNP requests during enumeration, resource rebalancing, and any other
time Plug and Play activity occurs on the system. Drivers can also send certain IRP_MJ_PNP requests,
depending on the minor function code.

Input Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP. Every IRP_MJ_PNP
request specifies a minor function code that identifies the requested PnP action.

Output Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP.

Operation
See Plug and Play Minor IRPs for detailed information about IRP_MJ_PNP requests.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchPnP
IRP_MJ_POWER
12/7/2018 • 2 minutes to read • Edit Online

All drivers must be prepared to service IRP_MJ_POWER requests in a DispatchPower routine.

When Sent
The power manager or a driver can send IRP_MJ_POWER requests at any time the operating system is running.

Input Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP. Every IRP_MJ_POWER
request specifies a minor function code that identifies the requested power action.

Output Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP.

Operation
In addition to the usual rules that govern the processing of IRPs, IRP_MJ_POWER IRPs have the following
special requirement: A driver that receives a power IRP must not change the major and minor function codes in
any I/O stack locations in the IRP that have been set by the power manager or by higher-level drivers. The power
manager relies on these function codes remaining unchanged until the IRP is completed. Violations of this rule
can cause problems that are difficult to debug. For example, the operating system might stop responding, or
"hang."
See Power Management Minor IRPs for detailed information about IRP_MJ_POWER requests.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchPower
IRP_MJ_QUERY_INFORMATION
6/25/2019 • 2 minutes to read • Edit Online

Drivers can optionally handle an IRP_MJ_QUERY_INFORMATION request.

When Sent
The operating system sends an IRP_MJ_QUERY_INFORMATION request to obtain metadata about a file or file
handle. For example, when a driver calls ZwQueryInformationFile, the operating system sends an
IRP_MJ_QUERY_INFORMATION request.

Input Parameters
The Parameters.QueryFile.FileInformationClass member is a FILE_INFORMATION_CLASS constant that
specifies the type of metadata to provide. For more information about the types of metadata, see the
FileInformationClass parameter of the ZwQueryInformationFile routine.
The Parameters.QueryFile.Length member specifies the length of the buffer that the
AssociatedIrp.SystemBuffer member points to.

Output Parameters
The AssociatedIrp.SystemBuffer member points to the buffer where the driver supplies the requested
information. The value of Parameters.QueryFile.FileInformationClass determines the format of the metadata
(a FILE_XXX_INFORMATION structure) to return. For more information about the formats of metadata, see the
FILE_INFORMATION_CLASS enumeration.

Operation
Drivers are not required to handle this request, and drivers that do are not required to handle every possible value
of Parameters.QueryFile.FileInformationClass. The driver's dispatch routine should return an error code such
as STATUS_INVALID_DEVICE_REQUEST for any values that it does not handle.
Not all of the possible values of FILE_INFORMATION_CLASS can occur.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
ZwQueryInformationFile
IRP_MJ_READ
6/25/2019 • 2 minutes to read • Edit Online

Every device driver that transfers data from its device to the system must handle read requests in a DispatchRead
or DispatchReadWrite routine, as must any higher-level driver layered over such a device driver.

When Sent
Any time following the successful completion of a create request.
Possibly, a user-mode application or Win32 component with a handle for the file object representing the target
device object has requested a data transfer from the device. Possibly, a higher-level driver has created and set up
the read IRP.

Input Parameters
The driver's I/O stack location in the IRP indicates how many bytes to transfer at Parameters.Read.Length.
Some drivers use the value at Parameters.Read.Key to sort incoming read requests into a driver-determined
order in the device queue or in a driver-managed internal queue of IRPs.
Certain types of drivers also use the value at Parameters.Read.ByteOffset, which indicates the starting offset for
the transfer operation. For example, see the IRP_MJ_READ topic in the Installable File System (IFS )
documentation.

Output Parameters
Depending on whether the underlying device driver sets up the target device object's Flags with
DO_BUFFERED_IO or with DO_DIRECT_IO, data is transferred into one of the following:
The buffer at Irp->AssociatedIrp.SystemBuffer if the driver uses buffered I/O.
The buffer described by the MDL at Irp->MdlAddress if the underlying device driver uses direct I/O (DMA
or PIO ).

Operation
On receipt of a read request, a higher-level driver sets up the I/O stack location in the IRP for the next-lower driver,
or it creates and sets up additional IRPs for one or more lower drivers. It can set up its IoCompletion routine,
which is optional for the input IRP but required for driver-created IRPs, by calling IoSetCompletionRoutine.
Then, the driver passes the request on to the next-lower driver with IoCallDriver.
On receipt of a read request, a device driver transfers data from its device to system memory. The device driver
sets the Information field of the I/O status block to the number of bytes transferred when it completes the IRP.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchRead
DispatchReadWrite
IoCallDriver
IoSetCompletionRoutine
IRP_MJ_SET_INFORMATION
6/25/2019 • 2 minutes to read • Edit Online

Device drivers can optionally handle an IRP_MJ_SET_INFORMATION request.

When Sent
The operating system sends an IRP_MJ_SET_INFORMATION request to set metadata about a file or file handle.
For example, when a driver calls ZwSetInformationFile, the operating system sends an
IRP_MJ_SET_INFORMATION request.

Input Parameters
The Parameters.SetFile.FileInformationClass member is a FILE_INFORMATION_CLASS constant that
specifies the type of metadata to set. For more information about the types of metadata, see the
FileInformationClass parameter of ZwSetInformationFile.
The Parameters.SetFile.Length member specifies the length of the buffer that the AssociatedIrp.SystemBuffer
member points to.
AssociatedIrp.SystemBuffer points to the buffer that contains the new information setting. The value of
Parameters.SetFile.FileInformationClass determines the format of the data (a FILE_XXX_INFORMATION
structure) to return. For more information about the formats of metadata, see the FILE_INFORMATION_CLASS
enumeration.

Output Parameters
None

Operation
Drivers are not required to handle this request, and drivers that do are not required to handle every possible value
of Parameters.SetFile.FileInformationClass. The driver's dispatch routine should return an error code such as
STATUS_INVALID_DEVICE_REQUEST for any values that it does not handle.
Not all of the possible values of FILE_INFORMATION_CLASS can occur.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
ZwSetInformationFile
IRP_MJ_SHUTDOWN
10/15/2019 • 2 minutes to read • Edit Online

Drivers of mass-storage devices that have internal caches for data must handle this request in a DispatchShutdown
routine. Drivers of mass-storage devices and intermediate drivers layered over them also must handle this request
if an underlying driver maintains internal buffers for data.

When Sent
Receipt of a shutdown request indicates that a file system driver is sending notice that the system is being shut
down.
One or more file system drivers can send such a lower-level driver more than one shutdown request when a user
logs off or when the system is being shut down for some other reason.
The PnP manager sends this IRP at IRQL<=APC_LEVEL in an arbitrary thread context.

Input Parameters
None

Output Parameters
None

Operation
The driver must complete the transfer of any data currently cached in the device or held in the driver's internal
buffers before completing the shutdown request.
A driver does not receive an IRP_MJ_SHUTDOWN request for a device object unless it registers to do so with
either IoRegisterShutdownNotification or IoRegisterLastChanceShutdownNotification.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchShutdown
IoRegisterLastChanceShutdownNotification
IoRegisterShutdownNotification
IRP_MJ_SYSTEM_CONTROL
6/25/2019 • 2 minutes to read • Edit Online

All drivers must provide a DispatchSystemControl routine that handles IRP_MJ_SYSTEM_CONTROL requests,
which are sent by the kernel-mode component of Windows Management Instrumentation (WMI).

When Sent
The WMI kernel-mode component can send an IRP_MJ_SYSTEM_CONTROL request any time following a
driver's successful registration as a supplier of WMI data. WMI IRPs typically are sent when a user-mode data
consumer has requested WMI data.

Input Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP. Every
IRP_MJ_SYSTEM_CONTROL request specifies a minor function code that identifies the requested WMI action.

Output Parameters
Depends on the value at MinorFunction in the current I/O stack location of the IRP.

Operation
All drivers must support IRP_MJ_SYSTEM_CONTROL requests by supplying a DispatchSystemControl routine.
Drivers that support Windows Management Instrumentation (WMI) must handle
IRP_MJ_SYSTEM_CONTROL requests by processing the minor function codes associated with this major
function code. For information about the WMI minor function codes, see WMI Minor IRPs.
Drivers that do not support WMI by registering as a WMI data provider must pass
IRP_MJ_SYSTEM_CONTROL requests to the next lower driver.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)

See also
DispatchSystemControl
IRP_MJ_WRITE
6/25/2019 • 2 minutes to read • Edit Online

Every device driver that transfers data from the system to its device must handle write requests in a DispatchWrite
or DispatchReadWrite routine, as must any higher-level driver layered over such a device driver.

When Sent
Any time following the successful completion of a create request.
Possibly, a user-mode application or Win32 component with a handle for the file object representing the target
device object has requested a data transfer to the device. Possibly, a higher-level driver has created and set up the
write IRP.

Input Parameters
The driver's I/O stack location in the IRP indicates how many bytes to transfer at Parameters.Write.Length.
Some drivers use the value at Parameters.Write.Key to sort incoming write requests into a driver-determined
order in the device queue or in a driver-managed internal queue of IRPs.
Certain types of drivers also use the value at Parameters.Write.ByteOffset, which indicates the starting offset for
the transfer operation. For example, see the IRP_MJ_WRITE topic in the Installable File System (IFS )
documentation.
Depending on whether the underlying device driver sets up the target device object's Flags with
DO_BUFFERED_IO or with DO_DIRECT_IO, data is transferred from one of the following:
The buffer at Irp->AssociatedIrp.SystemBuffer, if the driver uses buffered I/O
The buffer described by the MDL at Irp->MdlAddress, if the underlying device driver uses direct I/O
(DMA or PIO )

Output Parameters
None

Operation
On receipt of a write request, a higher-level driver sets up the I/O stack location in the IRP for the next-lower
driver, or it creates and sets up additional IRPs for one or more lower drivers. It can set up its IoCompletion
routine, which is optional for the input IRP but required for driver-created IRPs, by calling
IoSetCompletionRoutine. Then, the driver passes the request on to the next-lower driver with IoCallDriver.
On receipt of a write request, a device driver transfers data from system memory to its device. The device driver
sets the Information field of the I/O status block to the number of bytes transferred when it completes the IRP.

Requirements
Header Wdm.h (include Wdm.h, Ntddk.h, or Ntifs.h)
See also
DispatchReadWrite
DispatchWrite
IoCallDriver
IoCompletion
IoSetCompletionRoutine
I/O Stack Locations
6/25/2019 • 4 minutes to read • Edit Online

The I/O manager gives each driver in a chain of layered drivers an I/O stack location for every IRP that it sets up.
Each I/O stack location consists of an IO_STACK_LOCATION structure.
The I/O manager creates an array of I/O stack locations for each IRP, with an array element corresponding to
each driver in a chain of layered drivers. Each driver owns one of the stack locations in the packet and calls
IoGetCurrentIrpStackLocation to obtain driver-specific information about the I/O operation.
Each driver in such a chain is responsible for calling IoGetNextIrpStackLocation, then setting up the next-lower
driver's I/O stack location. Any higher-level driver's I/O stack location can also be used to store context about an
operation so that the driver's IoCompletion routine can perform its cleanup operations.
The Processing IRPs in Layered Drivers figure shows two I/O stack locations in the original IRP because it shows
two drivers, a file system driver and a mass-storage device driver. The driver-allocated IRPs in the Processing
IRPs in Layered Drivers figure do not have a stack location for the FSD that created them. Any higher-level driver
that allocates IRPs for lower-level drivers also determines how many I/O stack locations the new IRPs should
have, according to the StackSize value of the next-lower driver's device object.
The following figure shows the contents of the IRP in more detail.

As shown in the figure, each driver-specific I/O stack location in an IRP contains the following general
information:
The major function code (IRP_MJ_XXX), indicating the basic operation the driver should carry out
For some major function codes handled by FSDs, higher-level SCSI drivers, and all PnP drivers, a minor
function code (IRP_MN_XXX), indicating which subcase of the basic operation the driver should carry out
A set of operation-specific arguments, such as the length and starting location of a buffer into which or
from which the driver transfers data
A pointer to the driver-created device object, representing the target (physical, logical, or virtual) device for
the requested operation
A pointer to the file object, representing an open file, device, directory, or volume
A file system driver accesses the file object through its I/O stack location in IRPs. Other drivers usually
ignore the file object.
The set of IRP major and minor function codes that a particular driver handles can be device-type-specific.
However, lowest-level drivers and intermediate drivers (including PnP function and filter drivers) usually handle
the following set of basic requests:
IRP_MJ_CREATE — open the target device object, indicating that it is present and available for I/O
operations
IRP_MJ_READ — transfer data from the device
IRP_MJ_WRITE — transfer data to the device
IRP_MJ_DEVICE_CONTROL — set up (or reset) the device, according to a system-defined, device-type-
specific I/O control code (IOCTL )
IRP_MJ_CLOSE — close the target device object
IRP_MJ_PNP — perform a Plug and Play operation on the device. An IRP_MJ_PNP request is sent by
the PnP manager through the I/O manager.
IRP_MJ_POWER — perform a power operation on the device. An IRP_MJ_POWER request is sent by
the power manager through the I/O manager.
For more information about the major IRP function codes that drivers are required to handle, see IRP Major
Function Codes.
In general, the I/O manager sends IRPs with at least two I/O stack locations to mass-storage device drivers
because a file system is layered over other drivers for mass-storage devices. The I/O manager sends IRPs with a
single stack location to any driver that has no other driver layered above it.
However, the I/O manager provides support for adding a new driver to any chain of existing drivers in the system.
For example, an intermediate mirror driver that backs up data on a given disk partition might be inserted between
a pair of drivers, such as the file system driver and lowest-level driver shown in the Processing IRPs in Layered
Drivers figure. When this new driver attaches itself to the device stack, the I/O manager adjusts the number of I/O
stack locations in all IRPs it sends to the file system, mirror, and lowest-level drivers. Every IRP that the file system
in the Processing IRPs in Layered Drivers figure allocated would also contain another I/O stack location for such a
new mirror driver.
Note that this support for adding new drivers to an existing chain implies certain restrictions on any particular
driver's access to the I/O stack locations in IRPs:
A higher-level driver in a chain of layered drivers can safely access only its own and the next-lower-level
driver's I/O stack locations in any IRP. Such a driver must set up the I/O stack location for the next-lower-
level driver in IRPs. However, when designing such a higher-level driver, you cannot predict when (or
whether) a new driver will be added to the existing chain just below your driver.
Therefore, you should assume that any subsequently added driver will handle the same IRP major function
codes (IRP_MJ_XXX) as the displaced next-lower-level driver did.
The lowest-level driver in a chain of layered drivers can safely access only its own I/O stack location in any
IRP. When designing such a driver, you cannot predict when (or whether) a new driver will be added to the
existing chain above your device driver.
In designing a lowest-level driver, assume that the driver can continue to process IRPs using the
information passed in its own I/O stack location, whatever the originating source of a given IRP and
however many drivers are layered above it.
I/O Status Blocks
6/25/2019 • 2 minutes to read • Edit Online

An I/O status block, which consists of an IO_STATUS_BLOCK structure, is a part of each IRP. An I/O status block
serves two purposes:
It provides a higher-level driver's IoCompletion routine a way of determining whether the service worked
when the IRP is completed.
It provides more information about why the service either worked or did not work.
Upon completion of a IRP, the Status field indicates whether the drivers that processed the IRP actually satisfied
the request or failed the IRP with an error status. The Information field supplies the caller with more information
about what actually occurred. For example, it contains the number of bytes actually transferred after a read or
write operation.
For more information, see Setting the I/O Status Block in an IRP.
Passing IRPs down the Driver Stack
6/25/2019 • 2 minutes to read • Edit Online

When a driver's dispatch routine receives an IRP, it must call IoGetCurrentIrpStackLocation so that it can
check its own I/O stack location and determine that any parameters are valid. If the driver cannot satisfy and
complete the request itself, it can do one of the following:
Pass the IRP on for further processing by lower-level drivers.
Create one or more new IRPs and pass them down to lower-level drivers.
A higher-level driver should pass an I/O request on to a next-lower driver as follows:
1. If the driver will pass the input IRP on to the next lower-level driver, the dispatch routine should call
IoSkipCurrentIrpStackLocation or IoCopyCurrentIrpStackLocationToNext to set up the I/O stack
location of the next-lower driver.
If the driver calls IoAllocateIrp to allocate one or more additional IRPs for lower drivers, the dispatch
routine must initialize the next-lower driver's I/O stack location by following the steps that are described in
Processing IRPs in an Intermediate-Level Driver.
The dispatch routine can modify some of the parameters in the next-lower driver's I/O stack location for
certain requests. For example, a higher-level driver might modify the parameters for a large transfer
request when the underlying device has a known limit in transfer capacity, and reuse the IRP to send
partial-transfer requests to the underlying device driver.
2. Call IoSetCompletionRoutine.
If the dispatch routine is passing a received IRP to the next-lower driver, setting an IoCompletion routine is
optional but useful, because the routine can perform such tasks as determining how lower drivers
completed the request, reusing the IRP for partial transfers, updating whatever state the driver maintains if
it tracks IRPs, and retrying a request that was returned with an error.
If the dispatch routine has allocated new IRPs, setting an IoCompletion routine is required because the
routine must release each IRP after lower drivers have completed it.
For more information about IoCompletion routines, see Completing IRPs.
3. Call IoCallDriver with each IRP to be processed by lower drivers.
4. Return an appropriate NTSTATUS value, such as:
STATUS_PENDING
The driver usually returns STATUS_PENDING if the input IRP is an asynchronous request, such as
IRP_MJ_READ or IRP_MJ_WRITE.
The result of the call to IoCallDriver
The driver frequently returns the result of the call to IoCallDriver if the input IRP is a synchronous
request, such as IRP_MJ_CREATE.
A lowest-level device driver passes any IRP that it cannot complete in its dispatch routine on to other driver
routines as follows:
1. Call IoMarkIrpPending with the input IRP.
2. Call IoStartPacket to pass on or queue the IRP to the driver's StartIo routine, unless the driver manages
its own internal IRP queuing, as described in Driver-Managed IRP Queues.
If the driver does not have a StartIo routine but handles cancelable IRPs, it must either register a Cancel
routine or implement a cancel-safe IRP queue. For more information about Cancel routines, see Canceling
IRPs.
3. Return STATUS_PENDING.
Creating IRPs for Lower-Level Drivers
6/25/2019 • 4 minutes to read • Edit Online

To allocate an IRP for an asynchronous request, which will be processed in an arbitrary thread context by lower
drivers, a DispatchReadWrite routine can call one of the following support routines:
IoAllocateIrp, which allocates an IRP and a number of zero-initialized I/O stack locations
The dispatch routine must set up the next-lower driver's I/O stack location for the newly allocated IRP,
usually by copying (possibly modified) information from its own stack location in the original IRP. If a
higher-level driver allocates an I/O stack location of its own for a newly-allocated IRP, the dispatch routine
can set up per-request context information there for the IoCompletion routine to use.
IoBuildAsynchronousFsdRequest, which sets up the next-lower driver's I/O stack location for the caller,
according to caller-specified parameters
Higher-level drivers can call this routine to allocate IRPs for IRP_MJ_READ, IRP_MJ_WRITE,
IRP_MJ_FLUSH_BUFFERS, and IRP_MJ_SHUTDOWN requests.
When an IoCompletion routine is called for such an IRP, it can check the I/O status block, and if necessary
(or possible) set up the next-lower driver's I/O stack location in the IRP again and retry the request or reuse
it. However, the IoCompletion routine has no local context storage for itself in the IRP, so the driver must
maintain context about the original request elsewhere in resident memory.
IoMakeAssociatedIrp, which allocates an IRP and a number of zero-initialized I/O stack locations, and
associates the IRP with a master IRP.
Intermediate drivers cannot call IoMakeAssociatedIrp to create IRPs for lower drivers.
Any highest-level driver that calls IoMakeAssociatedIrp to create IRPs for lower drivers can return
control to the I/O manager after sending its associated IRPs on and calling IoMarkIrpPending for the
original, master IRP. A highest-level driver can rely on the I/O manager to complete the master IRP when
all associated IRPs have been completed by lower drivers.
Drivers seldom set an IoCompletion routine for an associated IRP. If a highest-level driver calls
IoSetCompletionRoutine for an associated IRP it creates, the I/O manager does not complete the master
IRP if the driver returns STATUS_MORE_PROCESSING_REQUIRED from its IoCompletion routine. In
these circumstances, the driver's IoCompletion routine must explicitly complete the master IRP with
IoCompleteRequest.
If a driver allocates an I/O stack location of its own in a new IRP, the dispatch routine must call
IoSetNextIrpStackLocation before it calls IoGetCurrentIrpStackLocation to set up context in its own I/O
stack location for the IoCompletion routine. For more information, see Processing IRPs in an Intermediate-Level
Driver.
The dispatch routine must call IoMarkIrpPending with the original IRP, but not with any driver-allocated IRPs
because the IoCompletion routine will free them.
If the dispatch routine is allocating IRPs for partial transfers and the underlying device driver might control a
removable-media device, the dispatch routine must set up the thread context in its newly allocated IRPs from the
value at Tail.Overlay.Thread in the original IRP.
An underlying driver for a removable-media device might call IoSetHardErrorOrVerifyDevice, which references
the pointer at Irp->Tail.Overlay.Thread, for a driver-allocated IRP. If the driver calls this support routine, the file
system driver can send a dialog box to the appropriate user thread that prompts the user to cancel, retry, or fail an
operation that the driver could not satisfy. See Supporting Removable Media for more information.
Dispatch routines must return STATUS_PENDING after sending all driver-allocated IRPs on to lower drivers.
A driver's IoCompletion routine should free all driver-allocated IRPs with IoFreeIrp before it calls
IoCompleteRequest for the original IRP. When it completes the original IRP, the IoCompletion routine must free
all driver-allocated IRPs before it returns control.
Each higher-level driver sets up any driver-allocated (and reused) IRPs for lower drivers in such a way that it is
immaterial to the underlying device driver whether a given request comes from an intermediate driver or
originates from any other source, such as a file system or user-mode application.
Highest-level drivers can call IoMakeAssociatedIrp to allocate IRPs and set them up for a chain of lower drivers.
The I/O manager automatically completes the original IRP when all its associated IRPs have been completed, as
long as the driver does not call IoSetCompletionRoutine with the original IRP or with any of the associated
IRPs it allocates. Highest-level drivers must not, however, allocate associated IRPs for any IRP that requests a
buffered I/O operation.
An intermediate-level driver cannot allocate IRPs for lower-level drivers by calling IoMakeAssociatedIrp. Any
IRP an intermediate driver receives might already be an associated IRP, and a driver cannot associate another IRP
with such an IRP.
Instead, if an intermediate driver creates IRPs for lower drivers, it should call IoAllocateIrp,
IoBuildDeviceIoControlRequest, IoBuildSynchronousFsdRequest, or IoBuildAsynchronousFsdRequest.
However, IoBuildSynchronousFsdRequest can be called only in the following circumstances:
By a driver-created thread to build IRPs for read or write requests, because such a thread can wait in a
nonarbitrary thread context (its own) on a dispatcher object, such as a driver-initialized Event passed to
IoBuildSynchronousFsdRequest
In the system thread context during initialization or while unloading
To build IRPs for inherently synchronous operations, such as create, flush, shutdown, close, and device
control requests
However, a driver is more likely to call IoBuildDeviceIoControlRequest to allocate device control IRPs than
IoBuildSynchronousFsdRequest.
Queuing and Dequeuing IRPs
6/25/2019 • 3 minutes to read • Edit Online

Because the I/O manager supports asynchronous I/O within a multitasking and multithreaded system, I/O
requests to a device can come in faster than its driver can process them to completion, particularly in
multiprocessor machines. Consequently, IRPs bound to any particular device must be queued in the driver when
its device is already busy processing another IRP.
Therefore, a lowest-level driver requires one of the following:
A StartIo routine, which the I/O manager calls to start I/O operations for IRPs the driver has queued to a
system-supplied IRP queue (see IoStartPacket).
An internal IRP queuing and dequeuing mechanism, which the driver uses to manage IRPs that come in
faster than it can satisfy them. Drivers can use device queues, interlocked queues, or cancel-safe queues.
For more information, see Driver-Managed IRP Queues.
Only a lowest-level device driver that can satisfy and complete every possible IRP in its dispatch routines needs
no StartIo routine and no driver-managed queues for IRPs.
Higher-level drivers almost never have StartIo routines. Most intermediate drivers have neither StartIo routines
nor internal queues; an intermediate driver can usually pass IRPs with valid parameters on from its dispatch
routines and do whatever postprocessing is required for any IRP in its IoCompletion routine.
The following describes, in general, some of the design considerations for determining whether to implement a
StartIo routine with or without internal, driver-managed queues for IRPs.
StartIo Routines in Drivers
For computer peripheral devices capable of handling only one device I/O operation at a time, device drivers can
implement StartIo routines. For these drivers, the I/O manager provides IoStartPacket and IoStartNextPacket
routines to queue and dequeue IRPs to and from a system-supplied IRP queue.
For more information about StartIo routines, see Writing a StartIo Routine.
Internal Queues for IRPs in Drivers
If a device can support more than one concurrent I/O operation, its lowest-level device driver must set up internal
request queues and manage its own queuing of IRPs. For example, the system serial driver maintains separate
queues for read, write, purge, and wait operations on its devices because it supports full-duplex serial devices.
A higher-level driver that sends requests to some number of underlying device drivers also might maintain
internal queues of IRPs. For example, file system drivers almost always have internal queues for IRPs.
For more information, see Driver-Managed IRP Queues.
Internal Queue Synchronization
Drivers with device-dedicated threads and highest-level drivers that use executive worker threads (including most
file system drivers) usually set up their own queue for IRPs. The queue is shared by the driver thread or driver-
supplied worker-thread callback and by other driver routines that process IRPs.
A driver that implements its own queue structure must ensure that access to the queue is synchronized, and that
canceled IRPs are removed from the queue. To make this task simpler for driver writers, cancel-safe IRP queues
provide a standard framework you can use when implementing an IRP queue. See Cancel-Safe IRP Queues for
more information. This is the preferred method for implementing an IRP queue.
Drivers can also implement all IRP queue synchronization and cancel logic explicitly. For example, a driver could
use an interlocked queue. The driver's dispatch routines insert IRPs into the interlocked queue and a driver-
created thread or the driver's worker-thread callback removes them by calling the ExInterlockedXxxList support
routines.
For example, the system floppy controller driver uses an interlocked queue. Its device-dedicated thread handles
the same processing of IRPs that is done by other device drivers' StartIo routines and some of the same
processing of IRPs that is done by other device drivers' DpcForIsr routines.
Internal Queues with StartIo Routines in Drivers
A driver that manages its own internal queues can also have a StartIo routine, but need not. Most lowest-level
device drivers either have a StartIo routine or manage their own queuing of IRPs, but not both.
An exception to this is the SCSI port driver, which has a StartIo routine and manages internal queues of IRPs. The
I/O manager queues IRPs to the port driver's StartIo routine in the device queue associated with the driver-
created device object that represents a SCSI HBA. The SCSI port driver also sets up and manages device queues
for IRPs to each target device (corresponding to a SCSI logical unit) on any HBA-driven SCSI bus in the machine.
The SCSI port driver uses its supplemental device queues to hold IRPs sent down from the SCSI class drivers in
LU -specific queues whenever any device on a SCSI bus is particularly busy. In effect, this driver's supplemental,
LU -specific device queues allow the SCSI port driver to serialize operations for heterogeneous SCSI devices
through an HBA while keeping each device on that HBA's SCSI buses as busy as possible.
Writing a StartIo Routine
6/25/2019 • 2 minutes to read • Edit Online

As its name suggests, a StartIo routine is responsible for starting an I/O operation on the physical device.
Most lowest-level drivers provide a StartIo routine and rely on the I/O manager to queue IRPs to a system-
supplied device queue. Some lowest-level drivers are designed to set up and manage their own supplemental IRP
queues, but even these usually also provide a StartIo routine. (For more information about supplemental queues,
see Setting up and Using Device Queues and Managing Device Queues.)
Higher-level drivers, including FSDs and PnP function and filter drivers, seldom have a StartIo routine because it
can hamper performance. Instead, most file system drivers set up and maintain internal queues of IRPs. Other
higher-level drivers either have internal queues for IRPs or simply pass IRPs on to lower drivers from their
dispatch routines. See Driver-Managed IRP Queues for more information.
You can use the IoSetStartIoAttributes routine to set attributes that modify StartIo handling for your driver.
This section contains the following topics:
StartIo Routines in Lowest-Level Drivers
StartIo Routines in Higher-Level Drivers
Points to Consider for StartIo Routines
StartIo Routines in Lowest-Level Drivers
6/25/2019 • 10 minutes to read • Edit Online

The I/O manager's call to a driver's dispatch routine is the first stage in satisfying a device I/O request. The StartIo
routine is the second stage. Every device driver with a StartIo routine is likely to call IoStartPacket from its
DispatchRead and DispatchWrite routines, and usually for a subset of the I/O control codes it supports in its
DispatchDeviceControl routine. The IoStartPacket routine adds the IRP to the device's system-supplied device
queue or, if the queue is empty, immediately calls the driver's StartIo routine to process the IRP.
You can assume that when a driver's StartIo routine is called, the target device is not busy. This is because the I/O
manager calls StartIo under two circumstances; either one of the driver's dispatch routines has just called
IoStartPacket and the device queue was empty, or the driver's DpcForIsr routine is completing another request
and has just called IoStartNextPacket to dequeue the next IRP.
Before the StartIo routine in a highest-level device driver is called, that driver's dispatch routine should have
probed and locked down the user buffer, if necessary, to set up valid mapped buffer addresses in the IRP queued to
its StartIo routine. If a highest-level device driver sets up its device objects for direct I/O (or for neither buffered
nor direct I/O ), the driver cannot defer locking down a user buffer to its StartIo routine; every StartIo routine is
called in an arbitrary thread context at IRQL = DISPATCH_LEVEL.
Note Any buffer memory to be accessed by a driver's StartIo routine must be locked down or allocated from
resident, system-space memory and must be accessible in an arbitrary thread context.
In general, any lower-level device driver's StartIo routine is responsible for calling
IoGetCurrentIrpStackLocation with the input IRP and then doing whatever request-specific processing is
necessary to start the I/O operation on its device. Request-specific processing can include the following:
Setting up or updating any state information about the current request that the driver maintains. The state
information might be stored in the device extension of the target device object or elsewhere in nonpaged
pool allocated by the driver.
For example, if a device driver maintains an InterruptExpected Boolean for the current transfer operation, its
StartIo routine might set this variable to TRUE. If the driver maintains a time-out counter for the current
operation, its StartIo routine might set up this value, or the StartIo routine might queue the driver's
CustomTimerDpc routine.
If the StartIo routine shares access to state information or hardware resources with other driver routines,
the state information or resource must be protected by a spin lock. (See Spin Locks.)
If the StartIo routine shares access to state information or resources with the driver's InterruptService
routine, StartIo must use KeSynchronizeExecution to call a SynchCritSection routine that accesses the
state or resource information. (See Using Critical Sections.)
Assigning a sequence number to the IRP in case the driver must log a device I/O error while processing the
IRP.
See Logging Errors for more information.
If necessary, translating the parameters in the driver's I/O stack location into device-specific values.
For example, a disk driver might need to calculate the starting sector or byte offset to the physical disk
address for a transfer operation, and whether the requested length of the transfer will cross a particular
sector boundary or exceed the transfer capacity of its physical device.
If the driver controls a removable-media device, checking for media changes before programming the
device for I/O and notifying its overlying file system if the media has changed.
For more information, see Supporting Removable Media.
If the device uses DMA, checking whether the requested Length (number of bytes to be transferred, found
in the driver's I/O stack location of the IRP ) should be split into partial-transfer operations, as explained in
Input/Output Techniques, assuming a closely coupled higher-level driver does not presplit large transfers
for the device driver.
The StartIo routine of such a device driver also can be responsible for calling KeFlushIoBuffers and, if the
driver uses packet-based DMA, for calling AllocateAdapterChannel with the driver's AdapterControl
routine.
See Adapter Objects and DMA, and Maintaining Cache Coherency, for additional details.
If the device uses PIO, mapping the base virtual address of the buffer, described in the IRP at Irp-
>MdlAddress, to a system-space address with MmGetSystemAddressForMdlSafe.
For read requests, the device driver's StartIo routine can be responsible for calling KeFlushIoBuffers
before PIO operations begin. See Maintaining Cache Coherency for more information.
If a non-WDM driver uses a controller object, calling IoAllocateController to register its ControllerControl
routine.
If the driver handles cancelable IRPs, checking whether the input IRP has already been canceled.
If an input IRP can be canceled before it is processed to completion, the StartIo routine must call
IoSetCancelRoutine with the IRP and the entry point of the driver's Cancel routine. The StartIo routine
must acquire the cancel spin lock for its call to IoSetCancelRoutine. Alternatively, a driver can use
IoSetStartIoAttributes to set the NonCancelable attribute for the StartIo routine to TRUE. This prevents
the system from trying to cancel an IRP that has been passed to StartIo by a call to IoStartPacket.
As a general rule, a driver that uses buffered I/O has a simpler StartIo routine than one that uses direct I/O.
Drivers that use buffered I/O transfer small amounts of data for each transfer request, while those that use direct
I/O (whether DMA or PIO ) transfer large amounts of data to or from locked-down buffers that can span physical
page boundaries in system memory.
Higher-level drivers layered above physical device drivers usually set up their device objects to match those of
their respective device drivers. However, a highest-level driver, particularly a file system driver, can set up device
objects for neither direct nor buffered I/O.
Drivers that set up their device objects for buffered I/O can rely on the I/O manager to pass valid buffers in all
IRPs it sends to the driver. Lower-level drivers that set up device objects for direct I/O can rely on the highest-level
driver in their chain to pass valid buffers in all IRPs sent through any intermediate drivers to the underlying lower-
level device driver.
Using Buffered I/O in StartIo Routines
If a driver's DispatchRead, DispatchWrite, or DispatchDeviceControl routine determines that a request is valid and
calls IoStartPacket, the I/O manager calls the driver's StartIo routine to process the IRP immediately if the device
queue is empty. If the queue is not empty, IoStartPacket queues the IRP. Eventually, a call to IoStartNextPacket
from the driver's DpcForIsr or CustomDpc routine causes the I/O manager to dequeue the IRP and call the driver's
StartIo routine.
The StartIo routine calls IoGetCurrentIrpStackLocation and determines which operation must be performed to
satisfy the request. It preprocesses the IRP in any way necessary before programming the physical device to carry
out the I/O request.
If access to the physical device (or the device extension) must be synchronized with an InterruptService routine, the
StartIo routine must call a SynchCritSection routine to perform the necessary device programming. For more
information, see Using Critical Sections.
A physical device driver that uses buffered I/O transfers data either to or from a system-space buffer, allocated by
the I/O manager, that the driver finds in each IRP at Irp->AssociatedIrp.SystemBuffer.
Using Direct I/O in StartIo Routines
If a driver's DispatchRead, DispatchWrite, or DispatchDeviceControl routine determines that a request is valid and
calls IoStartPacket, the I/O manager calls the driver's StartIo routine to process the IRP immediately if the device
queue is empty. If the queue is not empty, IoStartPacket queues the IRP. Eventually, a call to IoStartNextPacket
from the driver's DpcForIsr or CustomDpc routine causes the I/O manager to dequeue the IRP and call the driver's
StartIo routine.
The StartIo routine calls IoGetCurrentIrpStackLocation and determines which operation must be performed to
satisfy the request. It preprocesses the IRP in any way necessary, such as splitting up a large DMA transfer request
into partial-transfer ranges and saving state about the Length of an incoming transfer request that must be split.
Then it programs the physical device to carry out the I/O request.
If access to the physical device (or the device extension) must be synchronized with the driver's ISR, the StartIo
routine must use a driver-supplied SynchCritSection routine to perform the necessary programming. For more
information, see Using Critical Sections.
Any driver that uses direct I/O either reads data into or writes data from a locked-down buffer, described by a
memory descriptor list (MDL ), that the driver finds in the IRP at Irp->MdlAddress. Such a driver commonly uses
buffered I/O for device control requests. For more information, see Handling I/O Control Requests in StartIo
Routines.
The MDL type is an opaque type that drivers do not access directly. Instead, drivers that use PIO remap user-space
buffers by calling MmGetSystemAddressForMdlSafe with Irp->MdlAddress as a parameter. Drivers that use
DMA also pass Irp->MdlAddress to support routines during their transfer operations to have the buffer
addresses remapped to logical ranges for their devices.
Unless a closely coupled higher-level driver splits up large DMA transfer requests for the underlying device driver,
a lowest-level device driver's StartIo routine must split up each transfer request that is larger than its device can
manage in a single transfer operation. Drivers that use system DMA are required to split transfer requests that are
too large for the system DMA controller or for their devices to handle in a single transfer operation.
If the device is a subordinate DMA device, its driver must synchronize transfers through a system DMA controller
with a driver-allocated adapter object, representing the DMA channel, and a driver-supplied AdapterControl
routine. The driver of a bus-master DMA device also must use a driver-allocated adapter object to synchronize its
transfers and must supply an AdapterControl routine if it uses the system's packet-based DMA support, or an
AdapterListControl routine if it uses the system's scatter/gather support.
Depending on the driver's design, it might synchronize transfer and device control operations on a physical device
with a controller object and supply a ControllerControl routine.
See Adapter Objects and DMA and Controller Objects for more information.
Handling I/O Control Requests in StartIo Routines
In general, only a subset of device I/O control requests are passed on from a driver's DispatchDeviceControl or
DispatchInternalDeviceControl routine for further processing by the driver's StartIo routine. The driver's StartIo
routine only has to handle valid device control requests that require device state changes or return volatile
information about the current device state.
Each new driver must support the same set of public I/O control codes as all other drivers for the same kind of
device. The system defines public, device-type-specific I/O control codes for IRP_MJ_DEVICE_CONTROL
requests as buffered requests.
Consequently, physical device drivers make data transfers to or from a system-space buffer that each driver finds
in the IRP at Irp->AssociatedIrp.SystemBuffer for device control requests. Even drivers that set up their device
objects for direct I/O use buffered I/O to satisfy device control requests with public I/O control codes.
The definition of each I/O control code determines whether data transferred for that request is buffered. Any
privately defined I/O control codes for driver-specific IRP_MJ_INTERNAL_DEVICE_CONTROL requests
between paired drivers can define a code with method buffered, method direct, or method neither. As a general
rule, any privately defined I/O control code should be defined with method neither if a closely coupled higher-level
driver must allocate a buffer for that request.
Programming the Device for I/O Operations
Usually, the StartIo routine in a lowest-level device driver must synchronize access to any memory or device
registers it shares with the driver's ISR by using KeSynchronizeExecution to call a driver-supplied
SynchCritSection routine. The driver's StartIo routine uses the SynchCritSection routine to actually program the
physical device for I/O at DIRQL. For more information, see Using Critical Sections.
Before calling KeSynchronizeExecution, the StartIo routine must do any preprocessing necessary for the
request. Preprocessing might include calculating an initial partial-transfer range and saving any state information
about the original request for other driver routines.
If a device driver uses DMA, its StartIo routine usually calls AllocateAdapterChannel with a driver-supplied
AdapterControl routine. In these circumstances, the StartIo routine postpones the responsibility for programming
the physical device to the AdapterControl routine. It, in turn, can call KeSynchronizeExecution to have a driver-
supplied SynchCritSection routine program the device for a DMA transfer.
StartIo Routines in Higher-Level Drivers
6/25/2019 • 2 minutes to read • Edit Online

Any higher-level driver can have a StartIo routine. However, such a driver is unlikely to be interoperable with
existing lower-level drivers and is likely to exhibit poor performance characteristics.
A StartIo routine in a higher-level driver has the following effects:
Incoming IRPs can be queued by calling IoStartPacket from the driver's DispatchXxx routine(s) and
IoStartNextPacket from its IoCompletion routine(s), thereby causing IRPs to be processed one at a time
through the StartIo routine.
The driver's I/O throughput could become noticeably slower during periods of heavy I/O demand, because
its StartIo routine can become a bottleneck.
The driver's StartIo routine calls IoCallDriver with each IRP at IRQL = DISPATCH_LEVEL, thereby causing
all lower-level drivers' dispatch routines also to run at IRQL = DISPATCH_LEVEL. This restricts the set of
support routines that lower drivers can call in their dispatch routines. Because most driver writers assume
their drivers' dispatch routines run at IRQL < DISPATCH_LEVEL, the higher-level driver is unlikely to be
interoperable with many existing lower-level drivers.
The StartIo routine reduces overall system throughput because it and the dispatch routines of all lower-level
drivers in its chain are run at IRQL = DISPATCH_LEVEL.
For more information about the IRQLs at which standard driver routines are run, see Managing Hardware
Priorities.
None of the system-supplied higher-level drivers has a StartIo routine, because it can slow IRP processing for the
driver itself, for all drivers above and below it, and for the system overall.
Most higher-level drivers simply send IRPs to lower-level drivers from their dispatch routines and do any
necessary clean-up processing in their IoCompletion routines.
However, higher-level drivers can set up internal queues for IRPs that request particular kinds of operations, or set
up internal queues to hold IRPs bound for a set of heterogeneous underlying devices like the SCSI port driver. For
more information, see Queuing and Dequeuing IRPs.
Points to Consider for StartIo Routines
6/25/2019 • 2 minutes to read • Edit Online

Keep the following points in mind when implementing a StartIo routine:


A StartIo routine must synchronize its access to a physical device and to any shared state information or
resources that the driver maintains in the device extension with the driver's other routines that access the
same device, memory location, or resources.
If the StartIo routine shares the device or state with the ISR, it must use KeSynchronizeExecution to call a
driver-supplied SynchCritSection routine to program the device or to access the shared state. For more
information, see Using Critical Sections.
If the StartIo routine shares state or resources with routines other than the ISR, it must protect the shared
state or resources with a driver-initialized executive spin lock for which the driver provides the storage. For
more information, see Spin Locks.
If a monolithic non-WDM device driver sets up a controller object, its StartIo routine can use the controller
object to synchronize operations through a shared physical device with attached (similar) devices.
See Controller Objects for more information.
Unless a closely coupled higher-level driver presplits large DMA transfer requests for its underlying device
driver, the underlying device driver's StartIo routine must split large transfer requests into partial-transfer
ranges and the driver must carry out a sequence of partial-transfer device operations. Each partial transfer
must be sized to suit the capabilities of the hardware: either the capabilities of the driver's device or, for a
subordinate DMA device, the capabilities of the system DMA controller, whichever has stricter constraints.
See Adapter Objects and DMA for more information about using system or bus-master DMA.
The StartIo routine of a driver that uses DMA must synchronize transfers using an adapter object.
A StartIo routine is run at IRQL = DISPATCH_LEVEL, which restricts the set of support routines it can call.
For example, a StartIo routine can neither access nor allocate pageable memory, and it cannot wait for a
dispatcher object to be set to the signaled state. On the other hand, a StartIo routine can acquire and release
a driver-allocated executive spin lock with KeAcquireSpinLockAtDpcLevel and
KeReleaseSpinLockFromDpcLevel, which run faster than KeAcquireSpinLock and
KeReleaseSpinLock.
See Managing Hardware Priorities and Spin Locks for more information.
If the driver holds IRPs in a cancelable state, its StartIo routine must check whether the input IRP has
already been canceled before it begins any processing for that request on its device. For more information,
see Canceling IRPs.
Driver-Managed IRP Queues
6/25/2019 • 2 minutes to read • Edit Online

Except for file system drivers, the I/O manager associates a device queue object (for queuing IRPs) with each
device object that a driver creates.
Most device drivers call the I/O manager's support routines to use the associated device queue, which holds IRPs
whenever device I/O requests for a target device object come in faster than the driver can process them to
completion. With this technique, IRPs are queued to a driver-supplied StartIo routine.
For good performance, most intermediate drivers simply pass IRPs on to lower drivers as fast as they come in, so
intermediate drivers almost never use the device queues associated with their respective device objects.
However, you can design a driver to manage internal queues of IRPs by explicitly setting up one or more device
queues, interlocked queues, or cancel-safe queues. This approach can be particularly useful if the driver controls a
device that overlaps I/O operations. For such a device, it can be difficult to manage concurrent processing of two
or more IRPs for the same target device object using only a single queue.
The simplest way to build an internal queue is to use the cancel-safe IRP queue framework. You can implement
the queuing mechanism of your choice in your driver. You can then use IoCsqInitialize to register a set of
callback routines that handle IRP insertion and deletion, as well as locking and unlocking your queue. The cancel-
safe IRP queue framework provides the IoCsqInsertIrp, IoCsqRemoveIrp, and IoCsqRemoveNextIrp routines
that automatically use the callback routines to safely insert and remove IRPs from the driver's queue. The system
also uses your callback routines to safely remove any IRPs that are canceled.
You also might opt to set up supplemental queues for IRPs in the driver of a device controller for a set of
heterogeneous physical devices. For example, the SCSI port driver uses device queue objects for internal queues.
This driver both has a StartIo routine and sets up device queue objects as supplemental queues, in addition to the
device queue associated with the device object it creates to represent an HBA. The SCSI port driver uses its
supplemental device queues to hold IRPs bound for particular logical units on the HBA-controlled SCSI bus(es).
The system floppy controller driver is an example of a driver that has no StartIo routine and uses an interlocked
queue. This driver sets up a doubly linked interlocked queue into which and from which the driver and its device-
dedicated thread insert and remove IRPs.
The Kernel defines the device queue object type. The executive support component provides routines for inserting
and removing IRPs in interlocked queues. Drivers for Windows XP and later versions of Windows can use cancel-
safe IRP queues to handle IRP queuing.
The following sections explain how to use device queues, interlocked queues, and cancel-safe queues:
Setting up and Using Device Queues
Managing Device Queues
Setting Up and Using Interlocked Queues
Managing Interlocked Queues with a Driver-Created Thread
Cancel-Safe IRP Queues
Setting Up and Using Device Queues
6/25/2019 • 2 minutes to read • Edit Online

A driver sets up a device queue object by calling KeInitializeDeviceQueue at driver or device initialization. After
starting its device(s), the driver inserts IRPs into this queue by calling KeInsertDeviceQueue or
KeInsertByKeyDeviceQueue. The following figure illustrates these calls.

As this figure shows, the driver must provide the storage for a device queue object, which must be resident.
Drivers that set up a device queue object usually provide the necessary storage in the device extension of a driver-
created device object, but the storage can be in a controller extension if the driver uses a controller object or in
nonpaged pool allocated by the driver.
If the driver provides storage for the device queue object in a device extension, it calls KeInitializeDeviceQueue
after creating the device object and before starting the device. In other words, the driver can initialize the queue
from its AddDevice routine or when it handles a PnP IRP_MN_START_DEVICE request. In the call to
KeInitializeDeviceQueue, the driver passes a pointer to the storage it provides for the device queue object.
After starting its device(s), the driver can insert an IRP into its device queue by calling KeInsertDeviceQueue,
which places the IRP at the tail of the queue, or KeInsertByKeyDeviceQueue, which places the IRP into the
queue according to a driver-determined SortKey value, as shown in the previous figure.
Each of these support routines returns a Boolean value indicating whether the IRP was inserted into the queue.
Each of these calls also sets the state of the device queue object to Busy if the queue is currently empty (Not-Busy).
However, if the queue is empty (Not-Busy), neither KeInsertXxxDeviceQueue routine inserts the IRP into the
queue. Instead, it sets the state of the device queue object to Busy and returns FALSE. Because the IRP has not
been queued, the driver must pass it on to another driver routine for further processing.
When setting up supplemental device queues, follow this implementation guideline:
When a call to KeInsertXxxDeviceQueue returns FALSE, the caller must pass the IRP it attempted to queue on
for further processing to another driver routine. However, the call to KeInsertXxxDeviceQueue changes the
state of the device queue object to Busy, so the next IRP to come in is inserted in the queue unless the driver calls
KeRemoveXxxDeviceQueue first.
When the device queue object's state is set to Busy, the driver can dequeue an IRP for further processing or reset
the state to Not-Busy by calling one of the following support routines:
KeRemoveDeviceQueue to remove the IRP at the head of the queue
KeRemoveByKeyDeviceQueue to remove an IRP chosen according to a driver-determined SortKey value
KeRemoveEntryDeviceQueue to remove a particular IRP in the queue or to determine whether a
particular IRP is in the queue
KeRemoveEntryDeviceQueue returns a Boolean indicating whether the IRP was in the device queue.
Calling any of these routines to remove an entry from a device queue that is empty but Busy changes the queue
state to Not-Busy.
Each device queue object is protected by a built-in executive spin lock (not shown in the Using a Device Queue
Object figure). As a result, a driver can insert IRPs into the queue and remove them in a multiprocessor-safe
manner from any driver routine running at less than or equal to IRQL = DISPATCH_LEVEL. Because of this IRQL
restriction, a driver cannot call any KeXxxDeviceQueue routine from its ISR or SynchCritSection routines, which
run at DIRQL.
See Managing Hardware Priorities and Spin Locks for more information. For IRQL requirements for a specific
support routine, see the routine's reference page.
Managing Device Queues
6/25/2019 • 5 minutes to read • Edit Online

The I/O manager usually (except for FSDs) creates an associated device queue object when a driver calls
IoCreateDevice. It also provides IoStartPacket and IoStartNextPacket, which drivers can call to have the I/O
manager insert IRPs into the associated device queue or call their StartIo routines.
Consequently, it is rarely necessary (or particularly useful) for a driver to set up its own device queue objects for
IRPs. Likely candidates are drivers, such as the SCSI port driver, that must coordinate incoming IRPs from some
number of closely coupled class drivers for heterogeneous devices that are serviced through a single controller or
bus adapter.
In other words, a driver for a disk array controller is more likely to use a driver-created controller object than to set
up supplemental device queue object(s), while a driver for an add-on bus adapter and of a set of class drivers is
slightly more likely to use supplemental device queues.
Using Supplemental Device Queues with a StartIo Routine
By calling IoStartPacket and IoStartNextPacket, a driver's Dispatch and DpcForIsr (or CustomDpc) routines
synchronize calls to its StartIo routine using the device queue that the I/O manager created when the driver
created the device object. For a port driver with a StartIo routine, IoStartPacket and IoStartNextPacket insert
and remove IRPs in the device queue for the port driver's shared device controller/adapter. If the port driver also
sets up supplemental device queues to hold requests coming in from closely coupled higher-level class drivers, it
must "sort" incoming IRPs into its supplemental device queues, usually in its StartIo routine.
The port driver must determine which supplemental device queue each IRP belongs in before trying to insert that
IRP into the appropriate queue. A pointer to the target device object is passed with the IRP to the driver's Dispatch
routine. The driver should save the pointer for use in "sorting" the incoming IRPs. Note that the device object
pointer passed to the StartIo routine is the driver's own device object, which represents the device
controller/adapter, so it cannot be used for this purpose.
After queuing any IRPs, the driver programs its shared controller/adapter to carry out the request. Thus, the port
driver can process incoming requests for all devices on a first-come, first-served basis until a call to
KeInsertDeviceQueue puts an IRP into a particular class driver's device queue.
By using its own device queue for all IRPs to be processed through its StartIo routine, the underlying port driver
serializes operations through the shared device (or bus) controller/adapter to all attached devices. By sometimes
holding IRPs for each supported device in a separate device queue, this port driver inhibits the processing of IRPs
for an already busy device while increasing I/O throughput for every other device that does I/O through its shared
hardware.
In response to the call to IoStartPacket from the port driver's Dispatch routine, the I/O manager either calls that
driver's StartIo routine immediately or puts the IRP into the device queue associated with the device object for the
port driver's shared controller/adapter.
The port driver must maintain its own state information about each of the heterogeneous devices that it services
through the shared device controller/adapter.
Keep in mind the following when designing class/port drivers with supplemental device queues:
A driver cannot easily get a pointer to a device object created by any driver layered above itself, except for
the device object at the top of its device stack.
By design, the I/O manager does not provide a support routine for getting such a pointer. Moreover, the
order in which drivers are loaded makes it impossible for lower drivers to get pointers for higher-level
drivers' device objects, which have not yet been created when any lower-level driver is adding its device.
Although IoGetAttachedDeviceReference returns a pointer to the highest-level device object in a driver's
stack, a driver should use this pointer only to designate a target for I/O requests to its stack. A driver should
not attempt to read or write the device object.
A driver cannot use a pointer to a device object created by any driver layered above itself, except to send
requests to the top of its own device stack.
There is no way to synchronize access to a single device object (and its device extension) between two
drivers in a multiprocessor-safe manner. Neither driver can make any assumptions about what I/O
processing the other driver is currently doing.
Even for closely coupled class/port drivers, each class driver should use the pointer to the port driver's device
object(s) only to pass on IRPs using IoCallDriver. The underlying port driver must maintain its own state,
probably in the port driver's device extension, about requests that it processes for any closely coupled class
driver(s)' device(s).
Managing Supplemental Device Queues Across Driver Routines
Any port driver that queues IRPs in supplemental device queues for a closely coupled set of class drivers also
must handle the following situation efficiently:
1. Its Dispatch routines have inserted IRPs for a particular device in the driver-created device queue for that
device.
2. IRPs for other devices continue to come in, to be queued to the driver's StartIo routine with IoStartPacket,
and to be processed through the shared device controller.
3. The device controller does not become idle, but each IRP held in the driver-created device queue also must
be queued to the driver's StartIo routine as soon as possible.
Consequently, the port driver's DpcForIsr routine must attempt to transfer an IRP from the driver's internal device
queue for a particular device into the device queue for the shared adapter/controller whenever the port driver
completes an IRP, as follows:
1. The DpcForIsr routine calls IoStartNextPacket to have the StartIo routine begin processing the next IRP
queued to the shared device controller.
2. The DpcForIsr routine calls KeRemoveDeviceQueue to dequeue the next IRP (if any) that it is holding in
its internal device queue for the device on whose behalf it is about to complete an IRP.
3. If KeRemoveDeviceQueue returns a non-NULL pointer, the DpcForIsr routine calls IoStartPacket with
the just dequeued IRP to have it queued to the shared device controller/adapter. Otherwise, the call to
KeRemoveDeviceQueue simply resets the state of the device queue object to Not-Busy, and the
DpcForIsr routine omits the call to IoStartPacket.
4. Then, the DpcForIsr routine calls IoCompleteRequest with the input IRP for which the port driver has just
completed I/O processing, either by setting the I/O status block with an error or by satisfying the I/O
request.
Note that the preceding sequence implies that the DpcForIsr routine also must determine the device for which it is
completing the current (input) IRP in order to manage internal queuing of IRPs efficiently.
If the port driver attempts to wait until its shared controller/adapter is idle before dequeuing IRPs held in its
supplemental device queues, the driver might starve a device for which there was heavy I/O demand while it
promptly serviced every other device for which the current I/O demand was actually much lighter.
Setting Up and Using Interlocked Queues
6/25/2019 • 3 minutes to read • Edit Online

New drivers should use the cancel-safe IRP queue framework in preference to the methods outlined in this section.
Drivers with device-dedicated threads or drivers that use executive worker threads, such as most system FSDs, are
the most likely types of drivers to manage their own run-time internal queuing of IRPs in an interlocked queue. All
PnP drivers, including WDM drivers, also must queue certain IRPs internally while making PnP and power state
transitions.
Usually, these drivers set up a doubly linked interlocked queue; every IRP contains a member of type
LIST_ENTRY, which a driver can use to doubly link IRPs that it is currently holding. A driver cannot requeue IRPs
for retries if it sets up a singly linked interlocked queue.
A driver must set up its interlocked queue at device initialization. The following figure illustrates a doubly linked
interlocked queue, the support routines a driver must call to set up such a queue, and a set of ExInterlockedXxx
routines a driver can call to insert IRPs into and remove IRPs from the queue.

As this figure shows, a driver must provide the storage for the queue itself and for the following in order to set up
a doubly linked interlocked queue:
An executive spin lock, which the driver must call KeInitializeSpinLock to initialize. Usually, a driver
initializes the spin lock when it sets up the device extension(s) for its device object(s) in its AddDevice
routine.
The list head for the queue, which the driver must initialize by calling InitializeListHead.
Most drivers that use doubly linked interlocked queues provide the necessary storage in the device extension of a
driver-created device object. The queue and executive spin lock can instead be in a controller extension (if the
driver uses a controller object) or in nonpaged pool allocated by the driver.
While the driver is accepting I/O requests, it can insert an IRP into its queue by calling either of the following
support routines if the ListHead is of type LIST_ENTRY, as shown in the previous figure:
ExInterlockedInsertTailList to place the IRP at the end of the queue
ExInterlockedInsertHeadList to place the IRP at the front of the queue. Drivers usually call this routine only
when they must retry a particular request.
The driver must pass pointers to the IRP (ListEntry), as well the ListHead and executive spin lock (Lock) pointers
that it previously initialized, to each of these ExInterlockedInsertXxxList routines. Only pointers to the ListHead
and Lock are required when the driver dequeues an IRP by calling ExInterlockedRemoveHeadList. To prevent
deadlocks, the driver must not be holding an ExecutiveSpinLock that it passes to any ExInterlockedXxx routine.
Because an interlocked queue is protected by the executive spin lock, the driver can insert IRPs into its doubly
linked queue and remove them in a multiprocessor-safe manner from any driver routine running at less than or
equal to IRQL = DISPATCH_LEVEL.
A queue with a ListHead of type LIST_ENTRY, as shown in the previous figure, is a doubly linked list. One with a
ListHead of type SLIST_HEADER is a sequenced, singly linked list. A driver initializes the ListHead for a
sequenced singly linked interlocked queue by calling ExInitializeSListHead.
A driver that never retries I/O operations can use ExInterlockedPushEntrySList and
ExInterlockedPopEntrySList to manage its queuing of IRPs internally in a sequenced, singly linked interlocked
queue. Any driver that uses this type of interlocked queue also must provide resident storage for a ListHead of
type SLIST_HEADER and for an ExecutiveSpinLock, as shown in the previous figure. It must initialize the spin
lock and set up its queue before calling ExInterlockedPushEntrySList to insert the initial entry into its queue.
For more information, see Managing Hardware Priorities and Spin Locks. For IRQL requirements for a specific
support routine, see the routine's reference page.
Managing Interlocked Queues with a Driver-Created
Thread
6/25/2019 • 3 minutes to read • Edit Online

New drivers should use the cancel-safe IRP queue framework in preference to the methods outlined in this
section.
Like the system floppy controller driver, a driver with a device-dedicated thread, rather than a StartIo routine,
usually manages its own queuing of IRPs in a doubly linked interlocked queue. The driver's thread pulls IRPs from
its interlocked queue when there is work to be done on the device.
In general, the driver must manage synchronization with its thread to any resources shared between the thread
and other driver routines. The driver also must have some way to notify its driver-created thread that IRPs are
queued. Usually, the thread waits on a dispatcher object, stored in the device extension, until the driver's Dispatch
routines set the dispatcher object to the Signaled state after inserting an IRP into the interlocked queue.
When the driver's Dispatch routines are called, each checks the parameters in the I/O stack location of the input
IRP and, if they are valid, queues the request for further processing. For each IRP queued to a driver-dedicated
thread, the dispatch routine should set up whatever context its thread needs to process that IRP before it calls
ExInterlockedInsertXxxList. The driver's I/O stack location in each IRP gives the driver's thread access to the
device extension of the target device object, where the driver can share context information with its thread, as the
thread removes each IRP from the queue.
A driver that queue cancelable IRPs must implement a Cancel routine. Since IRPs are canceled asynchronously,
you must ensure that your driver avoids the race conditions that can result. See Synchronizing IRP Cancellation
For more information about race conditions associated with canceling IRPs and techniques to avoid them.
Any driver-created thread runs at IRQL = PASSIVE_LEVEL and at a base run-time priority previously set when the
driver called PsCreateSystemThread. The thread's call to ExInterlockedRemoveHeadList temporarily raises
the IRQL to DISPATCH_LEVEL on the current processor while the IRP is being removed from the driver's internal
queue. The original IRQL is restored to PASSIVE_LEVEL on return from this call.
Any driver thread (or driver-supplied worker-thread callback) must carefully manage the IRQLs at which
it runs. For example, consider the following:
Because system threads generally run at IRQL = PASSIVE_LEVEL, it is possible for a driver thread to wait
for kernel-defined dispatcher objects to be set to the signaled state.
For example, a device-dedicated thread might wait for other drivers to satisfy an event and complete some
number of partial-transfer IRPs that the thread sets up with IoBuildSynchronousFsdRequest.
However, such a device-dedicated thread must raise IRQL on the current processor before it calls certain
support routines.
For example, if a driver uses DMA, its device-dedicated thread must nest its calls to
AllocateAdapterChannel and FreeAdapterChannel between calls to KeRaiseIrql and KeLowerIrql
because these routines and certain other support routines for DMA operations must be called at IRQL =
DISPATCH_LEVEL.
Remember that StartIo routines are run at DISPATCH_LEVEL, so drivers that use DMA need not make calls
to the KeXxxIrql routines from their StartIo routines.
A driver-created thread can access pageable memory because it runs in a nonarbitrary thread context (its
own) at IRQL = PASSIVE_LEVEL, but many other standard driver routines run at IRQL >=
DISPATCH_LEVEL. If a driver-created thread allocates memory that can be accessed by such a routine, it
must allocate the memory from nonpaged pool. For example, if a device-dedicated thread allocates any
buffer that will be accessed later by the driver's ISR or SynchCritSection, AdapterControl,
AdapterListControl, ControllerControl, DpcForIsr, CustomDpc, IoTimer, CustomTimerDpc, or, in a higher-
level driver, IoCompletion routine, the thread-allocated memory cannot be pageable.
If the driver maintains shared state information or resources in a device extension, a driver thread (like a
StartIo routine) must synchronize its access to a physical device and to the shared data with the driver's
other routines that access the same device, memory location, or resources.
If the thread shares the device or state with the ISR, it must use KeSynchronizeExecution to call a driver-
supplied SynchCritSection routine to program the device or to access the shared state. See Using Critical
Sections.
If the thread shares state or resources with routines other than the ISR, the driver must protect the shared
state or resources with a driver-initialized executive spin lock for which the driver provides the storage. For
more information, see Spin Locks.
For more information about the design tradeoffs of a using a driver thread for a slow device, see Polling a Device.
See also Managing Hardware Priorities. For specific information about IRQLs for particular support routines, see
the routine's reference page.
Cancel-Safe IRP Queues
6/25/2019 • 6 minutes to read • Edit Online

Drivers that implement their own IRP queuing should use the cancel-safe IRP queue framework. Cancel-safe
IRP queues split IRP handling into two parts:
1. The driver provides a set of callback routines that implement standard operations on the driver's IRP
queue. The provided operations include inserting and removing IRPs from the queue, and locking and
unlocking the queue. See Implementing the Cancel-Safe IRP Queue.
2. Whenever the driver needs to actually insert or remove an IRP from the queue, it uses the system-
provided IoCsqXxx routines. These routines handle all synchronization and IRP canceling logic for the
driver.
Drivers that use cancel-safe IRP queues do not implement Cancel routines to support IRP cancellation.
The framework ensures that drivers insert and remove IRPs from their queue atomically. It also ensures that IRP
cancellation is implemented correctly. Drivers that do not use the framework must manually lock and unlock the
queue before performing any insertions and deletions. They must also avoid the race conditions that can result
when implementing a Cancel routine. (For a description of the race conditions that can arise, see Synchronizing
IRP Cancellation.)
The cancel-safe IRP queue framework is included with Windows XP and later versions of Windows. Drivers that
must also work with Windows 2000 and Windows 98/Me can link to the Csq.lib library that is included in the
Windows Driver Kit (WDK). The Csq.lib library provides an implementation of this framework.
The IoCsqXxx routines are declared in the Windows XP and later versions of Wdm.h and Ntddk.h. Drivers that
must also work with Windows 2000 and Windows 98/Me must include Csq.h for the declarations.
You can see a complete demonstration of how to use cancel-safe IRP queues in the \src\general\cancel directory
of the WDK. For more information about these queues, also see the Flow of Control for Cancel-Safe IRP
Queuing white paper.
Implementing the Cancel-Safe IRP Queue
To implement a cancel-safe IRP queue, drivers must provide the following routines:
Either of the following routines to insert IRPs into the queue: CsqInsertIrp or CsqInsertIrpEx.
CsqInsertIrpEx is an extended version of CsqInsertIrp; the queue is implemented using one or the other.
A CsqRemoveIrp routine that removes the specified IRP from the queue.
A CsqPeekNextIrp routine that returns a pointer to the next IRP following the specified IRP in the queue.
This is where the system passes the PeekContext value that it receives from IoCsqRemoveNextIrp. The
driver can interpret that value in any way.
Both of the following routines to allow the system to lock and unlock the IRP queue: CsqAcquireLock and
CsqReleaseLock.
A CsqCompleteCanceledIrp routine that completes a canceled IRP.
Pointers to the driver's routines are stored in the IO_CSQ structure that describes the queue. The driver allocates
the storage for the IO_CSQ structure. The IO_CSQ structure is guaranteed to remain a fixed size, so a driver can
safely embed the structure inside its device extension.
The driver uses either IoCsqInitialize or IoCsqInitializeEx to initialize the structure. Use IoCsqInitialize if
the queue implements CsqInsertIrp, or IoCsqInitializeEx if the queue implements CsqInsertIrpEx.
Drivers need only provide the essential functionality in each callback routine. For example, only the
CsqAcquireLock and CsqReleaseLock routines implement lock handling. The system automatically calls these
routines to lock and unlock the queue as necessary.
You can implement any type of IRP queuing mechanism in your driver, as long as the appropriate dispatch
routines are provided. For example, the driver could implement the queue as a linked list, or as a priority queue.
CsqInsertIrpEx provides a more flexible interface to the queue than does CsqInsertIrp. The driver can use its
return value to indicate the result of the operation; if it returns an error code, the insertion failed. A CsqInsertIrp
routine does not return a value, so there is no simple way to indicate that an insertion failed. Also, CsqInsertIrpEx
takes an additional driver-defined InsertContext parameter that can be used to specify additional driver-specific
information to be used by the queue implementation.
Drivers can use CsqInsertIrpEx to implement more sophisticated IRP handling. For example, if there are no
pending IRPs, the CsqInsertIrpEx routine can return an error code and the driver can process the IRP
immediately. Similarly, if IRPs can no longer be queued, the CsqInsertIrpEx can return an error code to indicate
that fact.
The driver is insulated from all IRP cancellation handling. The system provides a Cancel routine for IRPs in the
queue. This routine calls CsqRemoveIrp to remove the IRP from the queue, and CsqCompleteCanceledIrp to
complete the IRP cancellation.
The following diagram illustrates the flow of control for IRP cancellation.

A basic implementation of CsqCompleteCanceledIrp is as follows.

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {


Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;

IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

Drivers can use any of the operating system's synchronization primitives to implement their CsqAcquireLock
and CsqReleaseLock routines. Available synchronization primitives include spin locks and mutex objects.
Here is an example of how a driver can implement locking using spin locks.
/*
The driver has previously initialized the SpinLock variable with
KeInitializeSpinLock.
*/

VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)


{
KeAcquireSpinLock(SpinLock, PIrql);
}

VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)


{
KeReleaseSpinLock(SpinLock, Irql);
}

The system passes a pointer to an IRQL variable to CsqAcquireLock and CsqReleaseLock. If the driver uses a
spin lock to implement locking for the queue, the driver can use this variable to store the current IRQL when the
queue is locked.
Drivers are not required to use spin locks. For example, the driver could use a mutex to lock the queue. For a
description of the synchronization techniques that are available to drivers, see Synchronization Techniques.
Using the Cancel-Safe IRP Queue
Drivers use the following system routines when queuing and dequeuing IRPs:
Either of the following to insert an IRP into the queue: IoCsqInsertIrp or IoCsqInsertIrpEx.
IoCsqRemoveNextIrp to remove the next IRP in the queue. The driver can optionally specify a key
value.
The following diagram illustrates the flow of control for IoCsqRemoveNextIrp.
IoCsqRemoveIrp to remove the specified IRP from the queue.
The following diagram illustrates the flow of control for IoCsqRemoveIrp.
These routines, in turn, dispatch to driver-supplied routines.
The IoCsqInsertIrpEx routine provides access to the extended features of a CsqInsertIrpEx routine. It returns
the status value that was returned by CsqInsertIrpEx. The caller can use this value to determine if the IRP was
successfully queued or not. IoCsqInsertIrpEx also allows the caller to specify a value for the InsertContext
parameter of CsqInsertIrpEx.
Note that both IoCsqInsertIrp and IoCsqInsertIrpEx can be called on any cancel-safe queue, whether the
queue has a CsqInsertIrp routine or a CsqInsertIrpEx routine. IoCsqInsertIrp behaves the same in either case. If
IoCsqInsertIrpEx is passed a queue that has a CsqInsertIrp routine, it behaves identically to IoCsqInsertIrp.
The following diagram illustrates the flow of control for IoCsqInsertIrp.
The following diagram illustrates the flow of control for IoCsqInsertIrpEx.
There are several natural ways to use the IoCsqXxx routines to queue and dequeue IRPs. For example, a driver
could simply queue IRPs to be processed in the order in which they are received. The driver could queue an IRP
as follows:

status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

If the driver is not required to distinguish between particular IRPs, it could then simply dequeue them in the
order in which they were queued, as follows:

IoCsqRemoveNextIrp(IoCsq, NULL);

Alternatively, the driver could queue and dequeue specific IRPs. The routines use the opaque
IO_CSQ_IRP_CONTEXT structure to identify particular IRPs in the queue. The driver queues the IRP as
follows:

IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

The driver can then dequeue the same IRP by using the IO_CSQ_IRP_CONTEXT value.

IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

The driver might also be required to remove IRPs from the queue based on a particular criterion. For example,
the driver might associate a priority with each IRP, such that higher priority IRPs get dequeued first. The driver
might pass a PeekContext value to IoCsqRemoveNextIrp, which the system passes back to the driver when it
requests the next IRP in the queue.
Completing IRPs
6/25/2019 • 2 minutes to read • Edit Online

"Completing an IRP" is a shorthand phrase that means "allowing all members of the driver stack to complete an
I/O operation." After the IRP has been completed, the I/O manager notifies the initiating application that the
requested I/O operation has finished.
When a driver has finished processing an IRP, it calls IoCompleteRequest (typically from within a DpcForIsr
routine). This causes the I/O manager to determine whether any higher-level drivers have set up IoCompletion
routines for the IRP. If so, each IoCompletion routine is called, in turn, until every layered driver in the chain has
completed the IRP.
When all drivers have completed the IRP, the I/O manager returns status to the original requester of the
operation. Note that a higher-level driver that sets up a driver-created IRP must supply an IoCompletion routine
to release the IRP it created.
This section contains the following topics:
When to Complete an IRP
Completing IRPs in Dispatch Routines
Using IoCompletion Routines
When to Complete an IRP
6/25/2019 • 2 minutes to read • Edit Online

A driver should initiate IRP completion when any of the following conditions is met:
The driver determines that IRP processing cannot progress because of invalid parameters or other
conditions.
The driver is able to handle the requested I/O operation without passing the IRP down the driver stack, and
the operation has finished.
The IRP is being canceled. (See Canceling IRPs.)
If these conditions are not met, a driver's dispatch routine must pass the IRP down to the next-lower driver, or it
must handle processing of the I/O request. If one of the conditions is met, the driver must call
IoCompleteRequest.
If a driver completes a request because processing cannot progress, or if it completes a request by handling the
requested operation without actually accessing the device, it typically calls IoCompleteRequest from one of its
dispatch routines. For more information, see Completing IRPs in Dispatch Routines.
If a driver must access a device to satisfy the request, it typically calls IoCompleteRequest from a DpcForIsr
routine. These routines are discussed extensively in Servicing Interrupts.
How to Complete an IRP in a Dispatch Routine
6/25/2019 • 2 minutes to read • Edit Online

If an input IRP can be completed immediately, a dispatch routine does the following:
1. Sets the Status and Information members of the IRP's I/O status block with appropriate values, in general:
The dispatch routine sets Status either to STATUS_SUCCESS or to an appropriate error
(STATUS_XXX), which can be the value returned by a call to a support routine or, for certain
synchronous requests, by a lower driver.
If a lower-level driver returns STATUS_PENDING, a higher-level driver should not call
IoCompleteRequest for the IRP, with one exception: The higher-level driver can use an event to
synchronize between its IoCompletion routine and its dispatch routine, in which case the
IoCompletion routine signals the event and returns STATUS_MORE_PROCESSING_REQUIRED. The
dispatch routine waits for the event and then calls IoCompleteRequest to complete the IRP.
It sets Information to the number of bytes successfully transferred if a request to transfer data, such
as a read or write request, was satisfied.
It sets Information to a value that varies according to the specific request for other IRPs that it
completes with STATUS_SUCCESS.
It sets Information to a value that varies according to the specific request for IRPs that it completes
with a warning STATUS_XXX. For example, it would set Information to the number of bytes
transferred for such a warning as STATUS_BUFFER_OVERFLOW.
Usually, it sets Information to zero for requests that it completes with an error STATUS_XXX.
2. Calls IoCompleteRequest with the IRP and with PriorityBoost = IO_NO_INCREMENT.
3. Returns the appropriate STATUS_XXX that it already set in the I/O status block. Note that a call to
IoCompleteRequest makes the given IRP inaccessible by the caller, so the return value from a dispatch
routine cannot be set from the I/O status block of an already completed IRP.
Follow this implementation guideline for calling IoCompleteRequest with an IRP:
Always release any spin lock(s) the driver is holding before calling IoCompleteRequest.
It takes an indeterminate amount of time to complete an IRP, particularly in a chain of layered drivers. Moreover, a
deadlock can occur if a higher-level driver's IoCompletion routine sends an IRP back down to a lower driver that is
holding a spin lock.
When to Complete an IRP in a Dispatch Routine
7/9/2019 • 2 minutes to read • Edit Online

Usually, drivers do not complete IRPs in their dispatch routines unless the parameters for the given request are
invalid or, in a device driver, unless the particular IRP_MJ_XXX requires no device I/O operations.
Every driver in a chain of layered drivers can check the validity of parameters in its own I/O stack location, for each
IRP received by the driver's dispatch routines. Completing IRPs with invalid parameters in the dispatch routine of
the highest possible driver improves I/O throughput for any chain of drivers and for the system overall.
A dispatch routine in a higher-level driver should either complete an IRP or pass it on for processing by lower
drivers, according to the following guidelines:
If the dispatch routine determines that any parameters in its own I/O stack location are invalid, it should
complete that IRP immediately with an appropriate error status, such as STATUS_INVALID_PARAMETER.
If the IRP contains the function code IRP_MJ_CLEANUP, the DispatchCleanup routine must complete
every IRP currently queued to the target device object, for the file object specified in the driver's I/O stack
location, and complete the cleanup IRP.
A cleanup request indicates that an application is being terminated or has closed a file handle for the file
object that represents the driver's device object. When the DispatchCleanup routine returns, usually the
driver's DispatchClose routine is called next.
Otherwise, a higher-level driver can satisfy the request only by passing it on to the next-lower driver.
A dispatch routine in a lowest-level driver should complete an IRP according to the following guidelines:
If the dispatch routine determines that any parameters in its own I/O stack location are invalid, or if the
driver does not support the IRP, it should complete that IRP immediately with an appropriate error status.
In such cases the driver must not complete the IRP with a status value of STATUS_SUCCESS.
Usually, any higher-level driver has already checked the parameters for a requested operation, but lowest-
level device drivers should perform their own parameters checks as well.
If the IRP contains the function code IRP_MJ_CLEANUP, the DispatchCleanup routine must complete
every IRP currently queued to the target device object, for the given file object in the driver's I/O stack
location, and then complete the cleanup IRP.
A cleanup request indicates that an application is being terminated or has closed a file handle for the file
object that represents the driver's device object. When the DispatchCleanup routine returns, usually the
driver's DispatchClose routine is called next.
If the request requires no device I/O operation, the dispatch routine should satisfy the request and complete
the IRP.
For example, a driver might save the current mode of its device in the device extension, particularly if it
seldom changes device modes after initialization. Its DispatchDeviceControl routine could then satisfy a
request that queried the current device mode by returning this stored information.
Otherwise, the dispatch routine must call IoMarkIrpPending, queue the IRP to other driver routines for further
processing, and return STATUS_PENDING.
Using IoCompletion Routines
6/25/2019 • 2 minutes to read • Edit Online

Higher-level drivers that monitor on an IRP -specific basis how lower-level drivers carried out particular requests
can have one or more IoCompletion routines. Higher-level drivers that allocate IRPs to send requests to lower
drivers must have an IoCompletion routine.
A highest-level or intermediate driver's DispatchRead or DispatchWrite routine is most likely to set an
IoCompletion routine for an IRP, because lower-level drivers must handle transfer requests asynchronously.
The lowest-level driver in a driver stack cannot register IoCompletion routines.
Drivers generally do not register IoCompletion routines for IRPs associated with synchronous I/O operations. For
instance, a higher-level driver's DispatchDeviceControl routine can allocate an IRP using
IoBuildDeviceIoControlRequest. In this case, the dispatch routine typically does not register an IoCompletion
routine, because device control requests are generally handled synchronously. Instead, the driver can allocate and
initialize an event object, and its DispatchDeviceControl routine can wait for an event to be initialized when it sends
on driver-allocated IRPs. Usually, a higher-level driver does not register an IoCompletion routine for an IRP
allocated with IoBuildSynchronousFsdRequest, for the same reason.
This section contains the following topics:
Registering an IoCompletion Routine
Implementing an IoCompletion Routine
Registering an IoCompletion Routine
6/25/2019 • 3 minutes to read • Edit Online

To register an IoCompletion routine, a dispatch routine calls IoSetCompletionRoutine, supplying the


IoCompletion routine's address and the IRP that it will subsequently pass on to lower drivers using IoCallDriver.
When it calls IoSetCompletionRoutine, the dispatch routine specifies the circumstances in which the I/O
manager should call the specified IoCompletion routine. You can choose to have the IoCompletion routine called if
a lower level driver completes the IRP successfully (InvokeOnSuccess), completes the IRP with an error status
value (InvokeOnError), or cancels the IRP (InvokeOnCancel), in any combination.
The purpose of an IoCompletion routine is to monitor what lower-level drivers did with the IRP and to do
additional completion processing, if necessary. Specifically, the most common uses for a driver's IoCompletion
routines are the following:
To dispose of an IRP that the driver allocated with IoAllocateIrp or IoBuildAsynchronousFsdRequest
Any higher-level driver that allocates an IRP using either of these support routines must supply an
IoCompletion routine for that IRP. The IoCompletion routine must call IoFreeIrp to dispose of driver-
allocated IRPs.
To reuse an incoming IRP to request that lower drivers complete some number of operations, such as
partial transfers, until the original request can be satisfied and completed by the IoCompletion routine
To retry a request that a lower driver completed with an error
Highest-level drivers, such as file systems, are more likely to have IoCompletion routines that attempt to
retry requests than are intermediate drivers, except possibly class drivers layered above a closely coupled
port driver. However, any intermediate driver use IoCompletion routines to retry requests.
While a highest-level or intermediate driver's DispatchReadWrite routine is most likely to process IRPs that
require an IoCompletion routine, any dispatch routine in any driver that passes IRPs on to lower drivers can
register an IoCompletion routine.
For driver-allocated IRPs and reused IRPs, the dispatch routine must call IoSetCompletionRoutine with the
following Boolean parameters:
InvokeOnSuccess set to TRUE
InvokeOnError set to TRUE
InvokeOnCancel set to TRUE if any lower driver in the chain might handle cancelable IRPs
Usually, InvokeOnCancel is set to TRUE, regardless of whether an IRP might be returned with
STATUS_CANCELLED, to ensure that the IoCompletion routine frees each driver-allocated IRP or checks
the completion status of each reuse of an IRP.
A dispatch routine that allocates IRPs for lower drivers using IoAllocateIrp or
IoBuildAsynchronousFsdRequestmust set an IoCompletion routine for each driver-allocated IRP.
The dispatch routine must set up state about both the original IRP and its allocated IRP (s) for the
IoCompletion routine to use. At a minimum, the IoCompletion routine needs access to the original IRP and
a count of how many additional IRPs were allocated.
The dispatch routine should call IoSetCompletionRoutine with all InvokeOnXxx parameters set to TRUE
for the IRP (s) it allocates.
A dispatch routine that reuses IRPs for a sequence of operations, or that retries I/O operation, must call
IoSetCompletionRoutine for each IRP that will be reused or retried.
The dispatch routine must save the original IRP's state information, for subsequent use by the
IoCompletion routine.
For example, a DispatchReadWrite routine must save the relevant transfer parameters of an input IRP for
the IoCompletion routine before setting up a partial transfer for the next-lower driver in that IRP. Saving
the parameters is particularly important if the DispatchReadWrite routine modifies any parameters that the
IoCompletion routine needs to determine when the original request has been satisfied.
If the IoCompletion routine can retry the request, the dispatch routine must set up a driver-determined
upper limit for the number of retries its IoCompletion routine should attempt before it completes the
original IRP with an error.
If an IRP is to be reused, the dispatch routine should call IoSetCompletionRoutine with all InvokeOnXxx
parameters set to TRUE.
For an asynchronous request, the dispatch routine of any intermediate driver must call IoMarkIrpPending
for the original IRP. It must then return STATUS_PENDING after it has sent the IRP on to lower drivers.
Implementing an IoCompletion Routine
6/25/2019 • 5 minutes to read • Edit Online

On entry, an IoCompletion routine receives a Context pointer. When a dispatch routine calls
IoSetCompletionRoutine, it can supply a Context pointer. This pointer can reference whatever driver-determined
context information the IoCompletion routine requires to process an IRP. Note that the context area cannot be
pageable because the IoCompletion routine can be called at IRQL = DISPATCH_LEVEL.
Consider the following implementation guidelines for IoCompletion routines:
An IoCompletion routine can check the IRP's I/O status block to determine the result of the I/O operation.
If the input IRP was allocated by the dispatch routine using IoAllocateIrp or
IoBuildAsynchronousFsdRequest, the IoCompletion routine must call IoFreeIrp to release that IRP,
preferably before it completes the original IRP.
The IoCompletion routine must release any per-IRP resources the dispatch routine allocated for the
driver-allocated IRP, preferably before it frees the corresponding IRP.
For example, if the dispatch routine allocates an MDL with IoAllocateMdl and calls
IoBuildPartialMdl for a partial-transfer IRP it allocates, the IoCompletion routine must release the
MDL with IoFreeMdl. If it allocates resources to maintain state about the original IRP, it must free
those resources, preferably before it calls IoCompleteRequest with the original IRP and definitely
before it returns control.
In general, before freeing or completing an IRP, the IoCompletion routine should free any per-IRP
resources allocated by the Dispatch routine. Otherwise, the driver must maintain state about the
resources to be freed before its IoCompletion routine returns control from completing the original
request.
If the IoCompletion routine cannot complete the original IRP with STATUS_SUCCESS, it must set the
I/O status block in the original IRP to the value returned in the driver-allocated IRP that caused the
IoCompletion routine to fail the original request.
If the IoCompletion routine will complete the original request with STATUS_PENDING, it must call
IoMarkIrpPending with the original IRP before it calls IoCompleteRequest.
If the IoCompletion routine must fail the original IRP with an error STATUS_XXX, it can log an error.
However, it is the responsibility of the underlying device driver to log any device I/O errors that
occur, so IoCompletion routines usually do not log errors.
When the IoCompletion routine has processed and freed the driver-allocated IRP, the routine must
return control with STATUS_MORE_PROCESSING_REQUIRED.
Returning STATUS_MORE_PROCESSING_REQUIRED from the IoCompletion routine forestalls the
I/O manager's completion processing for a driver-allocated and freed IRP. A second call to
IoCompleteRequest causes the I/O manager to resume calling the IRP's completion routines,
starting with the completion routine immediately above the routine that returned
STATUS_MORE_PROCESSING_REQUIRED.
If the IoCompletion routine reuses an incoming IRP to send one or more requests to lower drivers, or if the
routine retries failed operations, it should update whatever context the IoCompletion routine maintains
about each reuse or retry of the IRP. Then it can set up the next-lower driver's I/O stack location again, call
IoSetCompletionRoutine with its own entry point, and call IoCallDriver for the IRP.
The IoCompletion routine should not call IoMarkIrpPending at each reuse or retry of the IRP.
The dispatch routine already marked the original IRP as pending. Until all drivers in the chain
complete the original IRP with IoCompleteRequest, it remains pending.
Before retrying a request, the IoCompletion routine should reset the I/O status block with
STATUS_SUCCESS for Status and zero for Information, possibly after saving the returned error
information.
For each retry, the IoCompletion routine usually decrements a retry count set up by the Dispatch
routine. Typically, the IoCompletion routine must call IoCompleteRequest to fail the IRP when
some limited number of retries have failed.
The IoCompletion routine must return STATUS_MORE_PROCESSING_REQUIRED after it calls
IoSetCompletionRoutine and IoCallDriver with an IRP that is being reused or retried.
Returning STATUS_MORE_PROCESSING_REQUIRED from the IoCompletion routine forestalls the
I/O manager's completion processing of a reused or retried IRP.
If the IoCompletion routine cannot complete the original IRP with STATUS_SUCCESS, it must leave
the I/O status block as returned by lower drivers for the reuse or retry operation that causes the
IoCompletion routine to fail the IRP.
If the IoCompletion routine will complete the original request with STATUS_PENDING, it must call
IoMarkIrpPending with the original IRP before it calls IoCompleteRequest.
If the IoCompletion routine must fail the original IRP with an error STATUS_XXX, it can log an error.
However, it is the responsibility of the underlying device driver to log any device I/O errors that
occur, so IoCompletion routines usually do not log errors.
Any driver that sets an IoCompletion routine in an IRP and then passes the IRP down to a lower driver
should check the IRP ->PendingReturned flag in the IoCompletion routine. If the flag is set, the
IoCompletion routine must call IoMarkIrpPending with the IRP. Note, however, that a driver that passes
down the IRP and then waits on an event should not mark the IRP pending. Instead, its IoCompletion
routine should signal the event and return STATUS_MORE_PROCESSING_REQUIRED.
The IoCompletion routine must release any resources the dispatch routine allocated for processing the
original IRP, preferably before the IoCompletion routine calls IoCompleteRequest with the original IRP
and definitely before the IoCompletion routine returns control from completing the original IRP.
If any higher-level driver has set its IoCompletion routine in the original IRP, that driver's IoCompletion routine is
not called until the IoCompletion routines of all lower-level drivers have been called.
Supplying a Priority Boost in Calls to IoCompleteRequest
If a lowest-level device driver can complete an IRP in its dispatch routine, it calls IoCompleteRequest with a
PriorityBoost of IO_NO_INCREMENT. No run-time priority increase is needed because the driver can assume that
the original requester did not wait for its I/O operation to be completed.
Otherwise, the lowest-level driver supplies a system-defined and device-type-specific value that boosts the
requester's run-time priority to compensate for the time the requester waited on its device I/O request. See Wdm.h
or Ntddk.h for the boost values.
Higher-level drivers apply the same PriorityBoost as their respective underlying device drivers when they call
IoCompleteRequest.
Effect of Calling IoCompleteRequest
When a driver calls IoCompleteRequest, the I/O manager fills that driver's I/O stack location with zeros before
calling the next higher-level driver, if any, that has set up an IoCompletion routine to be called for the IRP.
A higher-level driver's IoCompletion routine can check only the IRP's I/O status block to determine how all lower
drivers handled the request.
The caller of IoCompleteRequest must not attempt to access the just-completed IRP. Such an attempt is a
programming error that causes a system crash.
Canceling IRPs
6/25/2019 • 2 minutes to read • Edit Online

Drivers in which IRPs might remain queued for an indefinite interval (so a user could cancel a previously
submitted I/O request) must have one or more Cancel routines to complete user-canceled I/O requests. For
example, keyboard, mouse, parallel, serial, and sound device drivers (or drivers layered over them) and file system
drivers should have Cancel routines.
Drivers for Microsoft Windows XP and later operating systems can use cancel-safe IRP queues rather than
implement their own Cancel routines.
To "cancel an IRP" means to complete the IRP as quickly as possible while still maintaining system integrity. For a
general discussion of IRP completion, see Completing IRPs.
The cancellation process begins when either the system or a driver calls IoCancelIrp. This routine is called for
each IRP that is associated with the thread that has not yet fully completed. The system cancels unprocessed IRPs
if the thread that initiated the I/O request exits. Drivers can cancel only IRPs that they have created (see Creating
IRPs for Lower-Level Drivers.)
If an IRP is not completed within 5 minutes, the I/O manager considers the IRP timed out. Such IRPs are
disassociated from the thread, and an error is logged for the device that currently owns the IRP. You should
ensure that any requests that might take a long time to complete in your driver are cancelable. To ensure that
potentially long requests are cancelable, you can use cancel-safe IRP queues or Kernel-Mode Driver Framework,
which abstracts cancellation away from the driver developer.
This section provides the following topics:
Introduction to Cancel Routines
Registering a Cancel Routine
Synchronizing IRP Cancellation
Implementing a Cancel Routine
Points to Consider When Canceling IRPs
Introduction to Cancel Routines
6/25/2019 • 2 minutes to read • Edit Online

Any driver in which IRPs can be held in a pending state for an indefinite interval must have one or more Cancel
routines. For example, a keyboard driver might wait indefinitely for a user to press a key. Conversely, if a driver will
never queue more IRPs than it can complete in five minutes, it probably does not need a Cancel routine.
Suppose a user-mode thread makes an I/O request, which is queued by a highest-level device driver's dispatch
routine, and the requesting thread is terminated while the IRP is queued. IRPs queued on behalf of a terminated
thread should be canceled. Consequently, the driver must set a driver-supplied Cancel routine in each IRP that it
queues.
A driver that creates associated IRPs must cancel them when the master IRP is canceled. Because associated IRPs
are not associated with a requesting thread, the master IRP's Cancel routine is responsible for canceling any
associated IRPs when the master IRP is canceled.
The number of Cancel routines any driver has depends on the driver's design. In general, a driver should have a
Cancel routine for each stage in its I/O processing at which an IRP might be held in a pending state for an
indefinite interval. Such pending IRPs are said to be held in a cancelable state.
Consider the following design guidelines:
The highest-level driver in a chain of layered drivers must have at least one Cancel routine if it queues IRPs
or otherwise holds IRPs in a cancelable state. It can have more than one Cancel routine, if necessary.
Lower-level drivers in which IRPs can be held in a cancelable state for relatively long intervals also should
have one or more Cancel routines.
If a driver manages its own internal queues of IRPs, it should have a separate Cancel routine for each of its
queues.
Some highest-level drivers for interactive devices, such as keyboard, mouse, sound, parallel class and serial drivers,
must have Cancel routines. Some lower-level drivers, such as a parallel port driver that holds IRPs queued for
some number of higher-level class drivers for relatively long intervals, also should have Cancel routines.
Mass-storage device drivers, along with intermediate drivers layered over them, are unlikely to have Cancel
routines. It is the responsibility of a file system driver to handle the cancellation of file I/O requests, while the IRPs
input to lower-level mass-storage drivers are usually processed to completion too quickly to be cancelable.
Registering a Cancel Routine
6/25/2019 • 2 minutes to read • Edit Online

If a device driver has a StartIo routine, its dispatch routines can register