52964.fb2 Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 3

Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 3

Chapter 2The Big Picture

A device driver has many jobs to do, some mandatory and others optional. In this chapter, I will give an overview of these jobs, so you know what tasks your driver will have to implement. I will also describe the big picture to show the different types of device drivers and the environment in which drivers run. If possible, you should consider writing a driver that uses one of the Windows Driver Model system drivers, as it simplifies your work and makes it fit in.

Choosing how to implement your driver is one of the most crucial decisions you will need to make. You need to know which jobs it must perform and which tasks it can offload to a class driver. If you are writing a driver for a Universal Serial Bus (USB) device, it soon becomes obvious that you should use the USB class driver. However, if you are going to support Power Management, it is best to decide this at the start as it may well effect the rest of your design.

Device Driver Components

Here are some of the jobs that a device driver can do:

• Initialize itself

• Create and delete devices

• Process Win32 requests to open and close a file handle

• Process Win32 Input/Output (I/O) requests

• Serialize access to hardware

• Talk to hardware

• Call other drivers

• Cancel I/O requests

• Time-out I/O requests

• Cope if a hot-pluggable device is added or removed

• Handle Power Management requests

• Report to administrators using Windows Management Instrumentation and NT events

Figure 2.1 Device driver components

Figure 2.1 shows how I have divided up a device driver's functionality into different modules. The figure also shows the filenames that I have used for the modules in this book.

Strictly speaking, only the Initialization module is mandatory. In practice, all drivers have dispatch routines to handle user I/O requests. A WDM device driver needs a Plug and Play module, along with an installation INF file. NT style drivers will usually create their devices in their Initialization routine and delete them in an Unload routine. All other modules are optional, though in WDM drivers it is best to write minimal Power Management and Windows Management Instrumentation modules, simply to pass any requests to lower drivers.

Obviously, there will be many interactions between these different modules. Some of these interactions will be direct function calls. However, a lot of information will be passed in data structures. For example, a "device object" data structure stores information about each device.

If writing your first driver, you will no doubt be keen to know how to process reads and 1 writes. As the figure shows, the module that handles these basic I/O requests is a depressingly small part of the whole device driver. The only consolation I can offer is this: if you base your driver on one of the examples in this book, you should be able to concentrate on your device's functionality. However, you cannot ignore what is going on in all the other modules.

Driver Entry Points and Callbacks

The kernel usually runs code in your driver by sending I/O Request Packets (IRPs). For example, a Win32 ReadFile call arrives in a device driver as a Read IRP. The size and location of the read buffer are specified as parameters within the IRP structure. The IRP structure is fundamental to device drivers. I shall be looking at IRPs more in the next chapter, and throughout the rest of the book.

A driver has one main initialization entry point — a routine that you must call DriverEntry. It has a standard function prototype. The kernel calls your DriverEntry routine when your driver is loaded, as is shown in Chapter 4.

Subsequently, the kernel may call many other routines in your driver. These routines are given the general name of callbacks. You tell the kernel the name of the routine and later on the kernel calls the routine back in the right circumstances. For example, if you want to handle interrupts, you must tell the kernel the name of your Interrupt Service Routine (ISR) callback. Each callback has a standard function prototype, appropriate for the circumstance in which it is called.

Table 2.1 lists all the driver entry points and callbacks. I will briefly describe these routines in this chapter and fully explain them later in the book, so do not worry about the details yet. New drivers can also provide a Common Object Model (COM) interface to the kernel, a defined series of routines that the driver implements.

Table 2.1 Standard driver entry points and callback routines

DriverEntryInitial driver entry point. Sets up main callbacks.
I/O Request Packet (IRP) handlersCalled to process the IRPs that you wish to handle.
UnloadUnload the driver.
AddDeviceA new Plug and Play device has been added.
StartIoA callback to handle IRPs serially.
Interrupt Service Routine (ISR)Called to handle a hardware interrupt. Usually schedules a Deferred Procedure Call to do most interrupt servicing.
DpcForIsrDeferred Procedure Call routine. Starts off another interrupt-driven transfer or completes an I/O request.
Critical section routineCalled to synchronize execution on one processor with no interrupts. Called by low IRQL tasks to interact with hardware
CancelCalled to cancel an IRP
CompletionCalled when a lower-level driver has completed processing an IRP. This lets the current driver do more work.
AdapterControlCalled when a DMA adapter channel is available.
ControllerControlCalled when a controller becomes free. NT and W2000 only.
TimerA one-second timer callback.
CustomTimerDpcFor time-outs of less than one second.
CustomDpcUsually used to handle work queues.
ReinitializeCalled if a driver takes a long time to initialize itself.
ConfigCallbackQuery device hardware description callback. NT and W2000 only.
Plug and Play NotificationCalled to notify you when devices have arrived, when the hardware profile changes, or when a device is being removed.
CallbackW2000 callback object handler

Dispatch Routines

A driver's DriverEntry routine must set up a series of callbacks for processing IRPs. It also sets the Unload, AddDevice, and StartIo routine callbacks, if these are needed. Table 2.2 shows the common Win32 device I/O functions and their corresponding IRPs. For example, a call to CreateFileends up as a create irp sent to your driver.

Table 2.2 Dispatch routine IRPs

Win32 functionIRP
CreateFileCreate IRP
CloseHandleClose IRP
ReadFile, etc.Read IRP
WriteFile, etc.Write IRP
DeviceIoControlIOCTL IRP
Internal IOCTL IRP

One common IRP cannot be generated from user mode code[1]. The Internal IOCTL IRP can only be generated from within the kernel. This allows drivers to expose an interface that cannot be used from Win32. These Internal IOCTLs are often made available by generic drivers. For example, the Universal Serial Bus (USB) class drivers only accept commands in Internal IOCTLs; they do not support ordinary reads and writes.

The handlers of the Create, Close, Read, Write, IOCTL, and Internal IOCTL IRPs are commonly called dispatch routines because they often perform only some initial processing of the IRP, such as checking that all the parameters are valid. They then dispatch the IRP for processing elsewhere within the driver. Quite often, IRPs need to be processed serially so that the driver interacts with hardware in a safe way.

Processing in these basic routines is not quite as straightforward as you might think. Two or more IRP dispatch routines may be running "simultaneously". The problem is particularly acute in multiprocessor systems, but can easily happen when there is just one processor. For example, a dispatch routine on a single processor may block waiting for a call to a lower driver to complete. Or the dispatch routine's thread may run out of time in its execution slot. In both cases, another IRP dispatch routine may called. In due course, this second IRP will block or be completed, and work will continue on the first IRP. This is a common scenario and much of the difficult work of a driver is coping correctly with synchronization issues.

Creating Devices

How do devices come to exist in the first place? Quite simply, you have to create them, either in your DriverEntry routine or when the Plug and Play (PnP) Manager tells you to. In due course, you will delete the devices when your driver unloads or when the PnP Manager tells you that the device has been removed.

Most WDM device objects are created when the PnP Manager calls your AddDevice entry point. This routine is called when a new device has been inserted and the installation INF files indicate that your driver is the one to run. After this, a series of PnP IRPs are sent to your driver to indicate when the device should be started and to query its capabilities. Finally a Remove Device PnP IRP indicates that the device has been removed, so your device object must be deleted.

NT style drivers create their devices when they want to. Usually their DriverEntry routine roots around to find any hardware that can be represented as a device. For example, the system parallel port driver finds out how many parallel ports have been detected and creates an appropriate kernel device object for each one. The driver's unload routine is usually responsible for deleting any device objects.

How do user mode programs know what devices exist? You must make a symbolic link for each device object that is visible to Win32. There are two different techniques for making these symbolic links. The first is to use an explicit "hard-coded" symbolic link name. The user mode program must similarly have the device name hard-coded into its source[2]. The alternative is to use device interfaces, in which each device interface is identified by a Globally Unique Identifier (GUID). Registering your device as having a particular device interface creates a symbolic link. A user mode program can search for all devices that have a particular GUID.

Hardware Resource Assignments

Low-level drivers need to know what hardware resources have been assigned to them. The most common hardware resources are I/O ports, memory addresses, interrupts, and DMA lines. You cannot just jump straight in and access an I/O port, for example. You must be told that it is safe to use this port.

WDM drivers that handle Plug and Play (PnP) IRPs are informed of a device's resources when the Start Device PnP IRP is received. An NT style driver must find what resources each device needs and request use of them.

A significant number of drivers will not need any low-level hardware resources. For example, a USB client driver does not need any hardware resources. The USB bus driver does all the nitty-gritty work of talking to hardware, so only it has to know about the hardware resources that the electronics use. A USB client driver simply has to issue requests to the bus driver. The USB bus driver talks to the hardware to do your job.

Calling Other Drivers

WDM drivers spend a lot of time talking to other drivers. A Plug and Play device is in a stack of device objects. It is very common to pass IRPs to the next device down the stack.

Some types of IRP, such as Plug and Play, Power Management, and Windows Management Instrumentation IRPs, are often passed immediately to the next device. Only minimal processing is required in a driver.

In other cases, a driver's main job is achieved by calling the next device down the stack. A USB client driver often calls the USB bus drivers by passing an IRP down the stack. Indeed, a driver often creates new IRPs to do this same job. For example, it is quite common for a Read IRP handler in a USB driver to do its job by issuing many Internal IOCTL IRP requests to the USB bus drivers.

Serializing Access to Hardware

Any device that accesses hardware has to use some mechanism to ensure that different parts of the driver do not access the hardware at the same time. In a multiprocessor system, the Write IRP handler could be running at the same time on two different processors. If they both try to access hardware then very unpredictable results will occur. Similarly, if an interrupt occurs while a Write IRP handler is trying to access hardware, it is quite likely that both tasks will go seriously wrong.

There are two different mechanisms to sort out these sources of conflict. First, Critical section routines are used to ensure that code cannot be interrupted by an interrupt handler, even on another processor.

Second, you should use StartIo routines to serialize the processing of IRPs. Each device object has a built in IRP queue. A driver's dispatch routines insert the IRPs into this device queue. The kernel I/O Manager takes IRPs out of this queue one by one, and passes them to the driver's StartIo routine. The StartIo routine, therefore, processes IRPs serially, ensuring no conflict with other IRP handlers. The StartIo routine will still need to use Critical section routines to avoids conflicts with hardware interrupts.

If you hold an IRP in any sort of queue, you must be prepared to cancel it if the user thread aborts suddenly or it calls the Win32 CancelIo function. You do this by attaching a cancel callback routine to each IRP that you queue. Cancelling can be tricky, as you have to cope if the IRP has been dequeued and is being processed in your StartIo routine.

If the user mode application closes its file handle to your device with overlapped requests outstanding, you must handle the Cleanup IRP. The Cleanup IRP asks you to cancel all IRPs associated with a file handle.

Talking to Hardware

Once you have an address for an I/O Port or memory, it is straightforward to read and write hardware registers and the like. You should not hog the processor for more than 50 microseconds. Consider using system threads or system worker threads, described later, if you need prolonged access to hardware.

Handling interrupts is slightly more complicated. As mentioned earlier, you have to register your Interrupt Service Routine. This must check whether your device caused the interrupt and act on it as soon as possible.

However, this is where is gets complicated. It is not safe for an interrupt handler to call most kernel functions. If an interrupt signals that the last part of a Write request has completed, you will want to tell the I/O Manager that you have completed processing the IRP. However, interrupt handlers cannot do this job. Instead, interrupt handlers must ask for your driver's Deferred Procedure Call (DPC) routine to be run in due course. Your DPC routine can use most kernel functions, thus letting it complete IRPs, etc.

Some drivers must set up Direct Memory Access (DMA) transfers of large amounts of data from devices into memory, or vice versa. DMA is usually done using the shared system DMA controllers. However, some new devices have a built-in bus mastering capability that lets them use DMA themselves. I will not explain how to set up DMA transfers. However, Chapter 24 describes the new DMA routines for the benefit of NT 4 driver writers.

Hardware Problems

As we all know, hardware is bound to go wrong (unlike our software:-). You should be able predict the common ways in which hardware problems arise: interrupts will not arrive, buffers will overrun, printers will run out paper, and cables will be disconnected. Some of these problems are timing related. You will have to ensure, as far as possible, that your driver is available at all times to process fast I/O events.

Make sure that you check all hardware status bits (e.g., the out-of-paper indication). Further, ensure that a valid error message gets back to the user mode application.

If things go wrong, make sure you implement time-outs and retry the transfer, if appropriate. The I/O Manager provides an easy way to check time-outs with a granularity of one second. However, it is straightforward to implement a timer for smaller intervals. If a transfer still fails after a few retries, you will have to abort the IRP and signal an appropriate error.

Power Management

If a device's power consumption can be controlled, its driver should support Power Management in W98 and W2000. This applies to both WDM and NT style devices. Power Management conserves battery power in portables and reduces energy consumption and wear in desktop systems. Conversely, some people will have a sleeping or hibernating system on all the time so that it can start up again quickly.

Power Management happens on a system-wide and device-specific scale. The Power Manager can request that the whole system powers down. There are six system power states, including fully on and off, with three sleeping and one hibernating state in between. At a device level, there are four device power states, with two sleeping states in between fully on and off. A device can power itself down even if the rest of the system is running at full speed.

Drivers support Power Management by handling the Power IRP. Quite a few drivers will just pass the Power IRP down the stack of devices. However, your driver will probably be the only one that knows how to change the power usage of your device, so you will have to support the Power IRP correctly.

Windows Management Instrumentation

If possible, a driver should implement the Windows Management Instrumentation (WMI) extensions for WDM. This reports diagnostic and performance information to engineers and management. Drivers make data sets available on request and can fire events when they want. Driver methods can be invoked on demand.

Drivers support WMI by handling the System Control IRP. Again, some drivers will just pass this IRP down the device stack.

You can either use standard WMI data or event blocks or you can define your own new ones in MOF format. These must be compiled and included as a resource in your driver.

NT Event Reporting

The system event log is available in NT and Windows 2000. This is the traditional way of reporting driver problems and should still be supported, if possible. Drivers build an error log entry with an event number and possibly some strings and data. The system event log combines the event number with message strings included in a driver's resources.

System Threads

A system thread lets you do some work "in the background". A system thread could talk to very slow devices, or do some lower priority post-processing of data. Alternatively, existing system worker threads let you queue a work item for execution at lower priority.

Types of Device Driver

This section gives a picture of where device drivers fit in the general scheme of things, and the different types of driver that exist.

Windows Overview

Figure 2.2 shows a general driver schematic of Windows. User mode programs usually run in Win32 or the Virtual DOS Machine provided for DOS and Win 16 applications. NT and W2000 also support other subsystems, such as OS/2 and Posix, but I think these are little used. Most user mode calls end up as requests to kernel mode.

The kernel mode executive services do any security and parameter checking that is appropriate before passing the request onto other parts of the kernel. In NT and W2000, a portion of the Win32 functionality is implemented in the kernel to achieve good performance. In particular, this Win32k.sys driver includes the Graphics Display Interface (GDI) engine. Print and display drivers live in here; these drivers are not covered by this book. Video requests are then processed by a generic video port driver. This port driver uses video miniport drivers to talk to specific display adapters.

Windows 95 and Windows 98 use Virtual Device Drivers (VxDs) to talk to hardware. These are not covered by this book. Windows NT, Windows 2000 and Windows 98 use WDM or NT style device drivers to talk to hardware. In NT and W2000, a Hardware Abstraction Layer (HAL) provides a portable interface to actual hardware.

Figure 2.2 Windows systems

I/O Request Processing

Figure 2.3 shows how an action by a user ends up being processed by device drivers. An application program makes a Win32 call for device I/O. This is received by the I/O System Services. The I/O Manager builds a suitable I/O Request Packet (IRP) out of the request.

In the simplest case, the I/O Manager just passes the IRP to one device driver. This interacts with hardware and, in due course, completes work on the IRP. The I/O Manager returns the data and results to Win32 and the user application.

It is very common now for an IRP to be processed by a stack of layered device drivers. Each driver breaks down the request into simpler requests. Highest level drivers, such as file system drivers, know how files are represented on disk, but not the details of how to get at the data. Intermediate level drivers process requests further (e.g., by breaking down a large request into a series of manageable chunks). Finally, lowest-level drivers actually interact with the hardware.

Wherever possible, drivers have been designed to be as generic as possible. For example, the SCSI port driver knows how to translate disk data requests into SCSI requests. However, it delegates the issuing of SCSI requests to SCSI miniport drivers that know how to talk to individual types of SCSI adapter.

One useful type of intermediate driver is a filter driver. A filter driver slips in between other driver layers to add functionality without effecting the higher or lower drivers. For example, a filter driver can be used to provide fault-tolerant disk access in which data is written to two separate physical disks to ensure that no data is lost.

Figure 2.3 Device driver calls

Plug and Play Device Stacks

The Windows Driver Model (WDM) redefines driver layering to fit in with the Plug and Play system. A device stack represents the layers of drivers that process requests, as shown in Figure 2.4. A bus driver controls access to all devices on their bus. For example, you must use the USB bus drivers if you want to talk to a USB device.

The bus driver is responsible for enumerating its bus. This means finding all devices on the bus and detecting when devices are added or removed. The bus driver creates a Physical Device Object (PDO) to represent the device it has found. Some bus drivers simply control access to the bus; you can do what you want with the bus once you have control. In other cases, the bus driver handles all transactions on the bus for you.

A function driver that does know how to control a device's main features is layered above the bus driver. A function driver creates a Function Device Object (FDO) that is put in the device stack. In the USB case, the function driver must use the USB class driver to talk to its device. However, in other cases, once the bus driver has given the go ahead, the function driver can access hardware directly.

It is quite possible for more function drivers to be layered on top of the first function driver. An example of this is given shortly.

Various types of filter drivers can be slipped into the device stack. Bus driver filters are added above the bus driver for all devices on the bus. Class filter drivers are added for all function drivers of a specific class. Device filter drivers are added only for a particular device. Upper-level filter drivers go above the function driver, while lower-level drivers go below.

It is important to realize that user requests always enter at the top of a device stack. Suppose a user program has identified a function device to which it wants to talk. The I/O Manager ensures that all its requests are sent to the top of the device stack, so that any higher filter or function drivers get a chance to process the requests first.

Figure 2.4 Generic WDM driver stack

Standard Bus and Class Drivers

Figure 2.5 shows the main class and bus drivers that are provided with Windows. These are general purpose drivers that can act as bus drivers and as function drivers. In most cases, the main system driver is accompanied by another driver that interfaces with hardware or another class driver. These helper drivers are usually called minidrivers but can be called miniclass or miniport drivers. For example, the Human Input Device (HID) class driver uses minidrivers to talk to hardware. One minidriver is provided to talk to HID devices on the USB bus. This HID minidriver translates requests from the HID class driver into requests to the USB bus driver.

I will discuss only the HID and USB class drivers in detail. Please consult the Driver Development Kit (DDK) documentation for details of the other system class and bus drivers. Any driver that uses a bus or class driver is often called a client driver.

Figure 2.5 Bus and class drivers

The Advanced Configuration and Power Interface (ACPI) bus driver interacts with the PC ACPI BIOS to enumerate the devices in the system and control their power use. The Peripheral Component Interconnect (PCI) bus driver enumerates and configures devices on the PCI bus. The PnPISA bus driver does a similar job for Industry Standard Architecture (ISA) devices that are configurable using Plug and Play.

The Stream class driver provides a basis for processing high bandwidth, time critical, and video and audio data. The stream class driver uses minidrivers to interface to actual hardware. Filter drivers can be written to transform data. In addition, a USB Camera helper minidriver is provided to make it easier to manage USB video and audio isochronous data streams.

The Stream class makes it easier to expose the different aspects of a piece of hardware as different subdevices. The audio Port class driver helps this further by using COM to identify the subdevices. Audio port and miniport are used to control the actual hardware.

The IEEE 1394 bus driver enumerates and controls the IEEE 1394 (Firewire) high speed bus. This bus driver uses port drivers to talk to the IEEE 1394 control electronics. IEEE 1394 client drivers issue IEEE 1394 Request Blocks (IRBs) to control their device.

The Universal Serial Bus (USB) bus driver enumerates and controls the lower-speed USB bus. Host controller drivers are provided as standard to talk to the two main types of USB host controller. USB client drivers use various IOCTLs to talk to their device via the USB class driver. The most important IOCTL lets clients issue USB Request Blocks (URBs) to control their device.

The SCSI and CDROM/DVD drivers are used to access hard disks, floppies, CDs, and DVDs. As usual, a variety of port and miniport drivers are used to talk to hardware. For example, requests to a CD-ROM on the IEEE 1394 bus would be routed to the IEEE 1394 bus driver.

The Human Input Device (HID) class driver provides an abstract view of input devices. The actual input hardware can be connected in different ways that are shielded by HID minidrivers. Quite a few HID devices are on the USB bus, so a HID minidriver for the USB bus is provided as standard. HID clients can either be user mode applications or, if need be, kernel mode drivers. Standard parsing routines are available to make sense of HID input and output reports.

Finally, the Still Image Architecture (STI) is not a driver at all, but a means of obtaining scanner and still image camera using minidrivers. STI currently supports SCSI, serial, parallel, and USB devices.

Example Driver Stack

With all these different types of drivers lurking around, it is worth while having a look at one real example to see how it all fits together. You do not need to know the detailed terminology yet.

Figure 2.6 shows the hardware and software components that are involved in the handling of a HID keyboard attached to the USB bus. On power up, the PCI bus driver finds the USB controller and so loads the USB class drivers. In this case, the USB OpenHCI host controller driver is used to talk directly to the hardware.

The USB host controller electronics detects when the keyboard is plugged in. The USB root hub bus driver is informed. It tells the Plug and Play Manager that a new device has arrived. The keyboard Hardware ID is used to find the correct installation INF file. In this case, the HID class driver and its USB minidriver are loaded, along with the standard HID keyboard client driver.

The HID keyboard driver uses Read IRPs to request keyboard data. The HID class driver asks the USB minidriver to retrieve any HID input reports. The minidriver uses an Internal IOCTL to submit URBs to the USB class drivers to read data. The USB class drivers talk directly to USB host controller electronics to get any available data. The electronics finally generates the correct signals on the USB bus. These are interpreted by the USB keyboard and it returns any keypress information. The results percolate up the device stack and will eventually reach a Win32 program in message form.

Figure 2.6 also shows that a user mode HID client can access the HID keyboard using standard Win32 ReadFile and WriteFile requests.

Figure 2.6 HID USB keyboard example

Driver Choices

You need to evaluate all the information in this chapter so you can decide at the outset what sort of driver to write for your device. There are various factors and choices that will influence your decisions.

Off-the-Shelf Drivers

The simplest approach is to buy an off-the-shelf driver that handles your device directly. Such drivers might not be available. However, there are various commercial general purpose drivers available, as described earlier. These can be tailored with scripts or the like to interact with many simple devices. The PHDIo and WDMIo drivers described later in this book can be used to talk to devices with I/O ports and a simple interrupt handling requirement.

Use Standard Drivers

If at all possible, you should use one of the standard bus or class drivers, as these will usually have implemented a large amount of the functionality that you need. Indeed, for many types of devices, using the system driver is the only way to access it. The only drawback is that you will have to study the detailed documentation in the DDK for the class of device that you are using. However, I can assure you that it is a darn sight easier to write a USB client than having to write a huge monolithic USB driver from scratch.

Operating System

If using a standard bus or class driver, you will usually be writing a driver that supports Plug and Play. Your driver should usually, therefore, run in W98 and W2000.

Most of the standard bus and class drivers are not available in NT 4 and earlier. In fact, any driver that supports NT 4 or earlier must be an NT style driver. In some cases, it still makes sense to write an NT style driver for W98 and W2000.

Layered Device Drivers

Plug and Play drivers are always layered drivers, as they always form a layer in a device stack. However, NT style drivers can also layer themselves over other drivers. Drivers in a device stack will receive IRPs sent to any of the devices in the stack.

It is quite possible for a driver to use a device stack but not include itself in the stack. For example, drivers that use Plug and Play Notification are usually NT style drivers. Whenever a suitable device is inserted, such a driver will want to use the device. It can layer itself into the device stack, and so receive all IRPs sent to the device stack. Alternatively, it can just store a pointer to the device object at the top of the device stack. This way, it can issue requests to the device without receiving unwanted requests from above. The HID client driver described in Chapter 23 shows how to use this technique.

Monolithic Drivers

It will soon become evident that in some cases, you need to write a low-level driver to talk to your device. The question now is whether yours is a general purpose device or not. If it is has just one specialized use, then writing a monolithic driver to handle all device requests may well be the simplest solution.

However if you want your device to interface into Windows smoothly, you will usually [ need to write a suitable minidriver, miniclass, or miniport driver. The DDK and supporting documentation must be used to determine the exact requirements for these types of drivers. In extreme cases, you may decide to write a whole new class driver, but I would not recommend it.

Recommended and Optional Features

As mentioned earlier, your driver should support Power Management, Windows Management Instrumentation (WMI), and NT event reporting. It is best to build in Power Management support from the beginning. It is possible to bolt on the other two aspects later. However, if you wish to collect performance information for your WMI reporting, it is best to design this in from the start.

WDM Rationale

I am willing to hazard a few comments about the Windows Driver Model (WDM) design to give you my ideas of how well it achieves its goals.

It is the role of device drivers to interface the operating system to hardware devices. They must eventually support the information flows that users and their applications require. In Windows, the specification of Win32, with multiple processes and threads, has implications for device drivers. And a user requirement for fast processing of video and audio data means that device drivers now have to do much more than just receive and send data.

One Core Model

Device drivers have to process a wide variety of data. The simplest example of input data is a keyboard keypress. Keypresses do not happen very often, but they do need to get to an application with visual feedback provided quickly. The next important type of data is the blocks of data that make up file systems. Speed and data integrity are the crucial issues here. Finally, for isochronous data, being able to keep up with the data is the most important factor, even if it means dropping some samples.

The Windows Driver Model defines one basic model to handle all these types of data. In effect, the model is extended for each type of device. For example, the USB class drivers provide an abstract model for all USB devices, with a defined interface that is used by all client drivers. Having a core driver model that is common to all device types makes it easier for driver writers to move from one type of device to another. And it also means that the kernel implementation of the driver model is more likely to be solid. Having a different model for each type of device would almost certainly make life harder for us.

Is this extensible core model too complicated? If you are writing a monolithic simple low-level driver, I think that IRPs will appear to you as quite difficult beasts. Supporting Plug and Play properly is a task too onerous for simple drivers, as described in the following text.

Complexity

Any discussion of device drivers can be fairly "heavy". By their very nature, you have to interact with complicated hardware systems that may have exacting timing constraints. Any operating system interface to hardware will have a complicated job to do. Another factor is that each type of device is different, so you need to know about the details of its technology, including its hardware implementation. For example, the network device drivers field is a subject that can easily fill a whole book. All this means that any aspiring device driver writer will have a steep learning curve.

Plug and Play and Layers

When Plug and Play is first described to you, it will probably sound fairly straightforward. You are told when a device arrives. You are told when it leaves. However, some of the follow-on implications are hard work for device drivers. In a brief look, I am not convinced that any of Microsoft examples do the job fully.

The first problem is that the sudden removal of a device will interrupt a whole host of activities that your driver may be doing. However, I guess that there is no easy way round this.

A stickier problem occurs when the Plug and Play (PnP) Manager tries to juggle the system resources when a new device is inserted. The PnP Manager can ask to "stop" your device (i.e., pause it while your resource assignments are changed). Again, this may interrupt the current flow of operations. However, more importantly, you are supposed to queue up any received IRPs while your device is stopped[3]. At worst, the user is only supposed to experience a tiny delay in the functioning of a device that is paused temporarily. However, implementing an IRP queue is not a trivial exercise, especially as you have to be prepared for the worst. For example, if a device is removed while stopped, you will have to chug through all the queued IRPs completing them with an appropriate error status. (The Ks… series of Kernel Streaming IRP handling routines might provide all this functionality.)

Another potential criticism of the Plug and Play system is that too many layers of drivers are involved. Passing requests between layers can take some time. If a request has to pass through many layers of filter drivers, it could have a noticeable effect on performance.

Range of Functionality

For good reasons, device drivers cannot use many standard C or C++ functions. This makes it more complicated for driver writers as they have to learn what routines to use instead. I find the Unicode string handling routines particularly unwieldy.

I understand that Microsoft is trying remove some older areas of functionality. For example, WMI is effectively going to replace NT events. However, you have to support both in the mean time, which is more effort.

Development Environment

There is plenty of documentation available in the various DDKs. However, it is slightly confusing to have Windows 98 and Windows 2000 DDKs covering the same subject. As is usual, the documentation lags a few months behind when any new developments arise, which can be frustrating. It is worth while checking the relevant Microsoft websites regularly for appropriate articles.

The provision of lots of example source in the DDKs is great. Give us more! These show how real drivers work and show us how to use various kernel calls and techniques.

The driver build environment is still essentially command line. Although it is easy enough to provide a Visual Studio wrapper for this build environment, a fully integrated system would be appreciated. The provision of a nice debugger that can be run on the same computer as the driver you are testing would be a boon.

Developer Support

You will have to make do with the DDK documentation and any colleagues that you can find to help out. First, scour the DDKs, the DDK source, documents in the DDK source, the Microsoft website, and the Platform SDK. Your next port of call might well be the newsgroups and mailing lists. Finally, you can pay Microsoft for help.

Conclusion

In this chapter, I have tried to give the device driver big picture, showing what components your driver will need and how it fits into the general scheme of things. There are different types of device driver and different ways of implementing the same driver functionality. Some driver components are not compulsory, but it is certainly recommended that you support Plug and Play, Power Management, and Windows Management Instrumentation, if possible.

A crucial part of the Windows Driver Model is the provision of several bus and class drivers. These make it considerably easier to write many types of driver.

In the next chapter, I will look at the crucial background concepts that are needed before device driver development in earnest.


  1. There are several other IRPs which are only generated by the kernel in response to kernel related events. For example, when the system decides to powers down, a Power IRP is sent all drivers.

  2. In NT and W2000 you can use the QueryDosDevice Win32 function to obtain a list of all symbolic links. I am not sure how useful this list is.

  3. You are also supposed to queue IRPs that arrive while your device is sleeping. The simple solution to this problem is to wake up your device when an IRP arrives.