Device Drivers in Win2K
Windows NT software executes in one of two modes.
User mode
(restricted to authorized activities only, no I/O or memory r/w)
Kernel mode
(able to execute any instruction the processor is capable of)
Schematically,
In response to a user-mode API call, the I/O Manager sends an I/O Request Packet (IRP) to the device driver entry point. As an example, suppose a user program executed the API ReadFile function. The system calls NtReadFile in the system DLL NTDLL.DLL, in response to which the I/O Manager sends an IRP with major function code IRP_MJ_READ (a constant defined in the DDK header file). When the device driver handles the IRP_MJ_READ function code, it makes a call to the HAL, perhaps READ_PORT_UCHAR to read a single byte from an I/O port. When done, the driver completes the IRP by calling a particular kernel-mode service routine.
Types of drivers
Kernel-mode Drivers
File System Drivers Implement standard PC file system on hard disks or over network connections
Legacy Drivers (NT-style) are kernel-mode drivers that directly control a hardware device without help from other drivers
PnP Drivers understand Plug and Play protocols
WDM Drivers also understand Power Management
Class Drivers manage well-defined classes of devices
Mini-Drivers supply vendor-specific help to a class driver
Video Drivers are kernel-mode drivers for displays and printers
The I/O Manager uses driver object and device object data structures to represent each device driver. From the ntddk.h file:
//
// Device Object structure definition – stores all needed state information for a particular piece of real or virtual hardware
//
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
struct _DRIVER_OBJECT *DriverObject;
struct _DEVICE_OBJECT *NextDevice;
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp;
PIO_TIMER Timer;
ULONG Flags; // See above: DO_...
ULONG Characteristics; // See ntioapi: FILE_...
PVPB Vpb;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
//
// The following field is for exclusive use by the filesystem to keep
// track of the number of Fsp threads currently using the device
//
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT;
// Driver Object structure definition – provides entry points for operating system
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
//
// The following links all of the devices created by a single driver
// together on a list, and the Flags word provides an extensible flag
// location for driver objects.
//
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
//
// The following section describes where the driver is loaded. The count
// field is used to count the number of times the driver has had its
// registered reinitialization routine invoked.
//
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
//
// The driver name field is used by the error log thread
// determine the name of the driver that an I/O request is/was bound.
//
UNICODE_STRING DriverName;
//
// The following section is for registry support. Thise is a pointer
// to the path to the hardware information in the registry
//
PUNICODE_STRING HardwareDatabase;
//
// The following section contains the optional pointer to an array of
// alternate entry points to a driver for "fast I/O" support. Fast I/O
// is performed by invoking the driver routine directly with separate
// parameters, rather than using the standard IRP call mechanism. Note
// that these functions may only be used for synchronous I/O, and when
// the file is cached.
//
PFAST_IO_DISPATCH FastIoDispatch;
//
// The following section describes the entry points to this particular
// driver. Note that the major function dispatch table must be the last
// field in the object so that it remains extensible.
//
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
Both these structures are defined in ntddk.h
A simple example
The following source files implement an example driver that does nothing but output debug messages and handles only CreateFile and CloseHandle Win32 API functions.
The header file
//////////////////////////////////////////////////////////////////////////////
//2002
//James A. Reising
//
//Drvr1 example
/////////////////////////////////////////////////////////////////////////////
//Drvr1.hCommon header
/////////////////////////////////////////////////////////////////////////////
//Version history
//4-Nov-021.0.0JARcreation
//
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//Include NT DDK standard header with C linkage
#ifdef __cplusplus
extern "C"
{
#endif
#include <ntddk.h>
#ifdef __cplusplus
}
#endif
/////////////////////////////////////////////////////////////////////////////
//DebugPrint header
#include "DebugPrint.h"
/////////////////////////////////////////////////////////////////////////////
//Our device extension -- extremely simple in this case
typedef struct _DRVR_DEVICE_EXTENSION
{
PDEVICE_OBJECTdrvrdo;
} DRVR_DEVICE_EXTENSION, *PDRVR_DEVICE_EXTENSION;
/////////////////////////////////////////////////////////////////////////////
// Forward declarations of global functions
NTSTATUS DrvrCreate(IN PDEVICE_OBJECT drvrdo,
IN PIRP Irp);
NTSTATUS DrvrClose(IN PDEVICE_OBJECT drvrdo,
IN PIRP Irp);
/////////////////////////////////////////////////////////////////////////////
The source file
//////////////////////////////////////////////////////////////////////////////
//2002 James A. Reising
//
//
//Drvr1 example -- does nothing but handle IRP_MJ_CREATE and IRP_MJ_CLOSE
//
//Simply returns after outputting debug messages, indicating success
//
/////////////////////////////////////////////////////////////////////////////
//Drvr1.cpp:Driver code
/////////////////////////////////////////////////////////////////////////////
//DriverEntryInitialisation entry point
//DrvrCreateDeviceCode to create device, called by DriverEntry
//DrvrCloseCode to handle IRP_MJ_CLOSE
//DrvrCreateCode to handle IRP_MJ_CREATE
//
/////////////////////////////////////////////////////////////////////////////
//Version history
//4-Nov-021.0.0JARcreation
//
//
//
/////////////////////////////////////////////////////////////////////////////
#include "Drvr1.h"
/////////////////////////////////////////////////////////////////////////////
PDEVICE_OBJECT drvrdo = NULL;
NTSTATUS DrvrCreateDevice( IN PDRIVER_OBJECT DriverObject);
void DrvrDeleteDevice();
/////////////////////////////////////////////////////////////////////////////
#pragma code_seg("INIT") // start INIT section
/////////////////////////////////////////////////////////////////////////////
//DriverEntry:
//
//Description:
//This function initializes the driver, and creates
//any objects needed to process I/O requests.
//
//Arguments:
//Pointer to the Driver object
//Registry path string for driver service key
//
//Return Value:
//This function returns STATUS_XXX
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status = STATUS_SUCCESS;
#if DBG
DebugPrintInit("Drvr1 checked");
#else
DebugPrintInit("Drvr1 free");
#endif
DebugPrint("RegistryPath is %T",RegistryPath);
// Export other driver entry points...
DriverObject->MajorFunction[IRP_MJ_CREATE] = DrvrCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DrvrClose;
status = DrvrCreateDevice(DriverObject);
DebugPrintMsg("DriverEntry completed");
return status;
}
//////////////////////////////////////////////////////////////////////////////
#defineNT_DEVICE_NAMEL"\\Device\\Drvr1"
#defineSYM_LINK_NAMEL"\\DosDevices\\Drvr1"
NTSTATUS DrvrCreateDevice( IN PDRIVER_OBJECT DriverObject)
{
NTSTATUS status = STATUS_SUCCESS;
// Initialise NT and Symbolic link names
UNICODE_STRING deviceName, linkName;
RtlInitUnicodeString( &deviceName, NT_DEVICE_NAME);
RtlInitUnicodeString( &linkName, SYM_LINK_NAME);
// Create our device
DebugPrint("Creating device %T",&deviceName);
status = IoCreateDevice(
DriverObject,
sizeof(DRVR_DEVICE_EXTENSION),
&deviceName,
FILE_DEVICE_UNKNOWN,
0,
TRUE,// Exclusive
&drvrdo);
if( !NT_SUCCESS(status))
{
DebugPrintMsg("Could not create device");
return status;
}
drvrdo->Flags |= DO_BUFFERED_IO;
// Initialise device extension
PDRVR_DEVICE_EXTENSION dx = (PDRVR_DEVICE_EXTENSION)drvrdo->DeviceExtension;
dx->drvrdo = drvrdo;
// Create a symbolic link so our device is visible to Win32...
DebugPrint("Creating symbolic link %T",&linkName);
status = IoCreateSymbolicLink( &linkName, &deviceName);
if( !NT_SUCCESS(status))
{
DebugPrintMsg("Could not create symbolic link");
IoDeleteDevice(drvrdo);
return status;
}
return status;
}
#pragma code_seg() // end INIT section
/////////////////////////////////////////////////////////////////////////////
//DrvrClose:
//
//Description:
//Handle IRP_MJ_CLOSE requests
//Allow closes to complete if device not started
//
//Arguments:
//Pointer to our DRVR DO
//Pointer to the IRP
//
//Return Value:
//This function returns STATUS_XXX
NTSTATUS DrvrClose(IN PDEVICE_OBJECT drvrdo,
IN PIRP Irp)
{
PDRVR_DEVICE_EXTENSION dx = (PDRVR_DEVICE_EXTENSION)drvrdo->DeviceExtension;
DebugPrintMsg("Close");
// Complete successfully
return 0;// which is what STATUS_SUCCESS is defined as
}
/////////////////////////////////////////////////////////////////////////////
//DrvrCreate:
//
//Description:
//Handle IRP_MJ_CREATE requests
//
//Arguments:
//Pointer to our Drvr DO
//Pointer to the IRP
//IrpStack->Parameters.Create.xxx has create parameters
//IrpStack->FileObject->FileName has file name of device
//
//Return Value:
//This function returns STATUS_XXX
NTSTATUS DrvrCreate(IN PDEVICE_OBJECT drvrdo,
IN PIRP Irp)
// Complete
{
DebugPrintMsg("Created OK");
return STATUS_SUCCESS;
}
Source for a console application to exercise the driver
//////////////////////////////////////////////////////////////////////////////
//2002 James A. Reising
//
//
//Drvr1Test example
//////////////////////////////////////////////////////////////////////////////
//Drvr1Test.cpp:Win32 console application to exercise Drvr1 devices
/////////////////////////////////////////////////////////////////////////////
//mainProgram main line
/////////////////////////////////////////////////////////////////////////////
//Version history
//4-Nov-021.0.0JARcreation
/////////////////////////////////////////////////////////////////////////////
#include "windows.h"
#include "stdio.h"
int main(int argc, char* argv[])
{
int TestNo = 1;
printf("\nDrvrTest\n");
/////////////////////////////////////////////////////////////////////////
// Open
printf("\nTest %d\n",TestNo++);
HANDLE hDrvr = CreateFile("\\\\.\\Drvr1", GENERIC_READ|GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if( hDrvr==INVALID_HANDLE_VALUE)
{
printf("XXX Could not open Drvr1 device\n");
return 1;
}
printf(" Opened OK\n");
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Close device
printf("\nTest %d\n",TestNo++);
if( !CloseHandle(hDrvr))
printf("XXX CloseHandle failed %d\n",GetLastError());
else
printf(" CloseHandle worked\n");
return 0;
}