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
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.
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
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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