Книга: Writing Windows WDM Device Drivers
Device Driver Components
Разделы на этой странице:
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
DriverEntry | Initial driver entry point. Sets up main callbacks. |
I/O Request Packet (IRP) handlers | Called to process the IRPs that you wish to handle. |
Unload | Unload the driver. |
AddDevice | A new Plug and Play device has been added. |
StartIo | A 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. |
DpcForIsr | Deferred Procedure Call routine. Starts off another interrupt-driven transfer or completes an I/O request. |
Critical section routine | Called to synchronize execution on one processor with no interrupts. Called by low IRQL tasks to interact with hardware |
Cancel | Called to cancel an IRP |
Completion | Called when a lower-level driver has completed processing an IRP. This lets the current driver do more work. |
AdapterControl | Called when a DMA adapter channel is available. |
ControllerControl | Called when a controller becomes free. NT and W2000 only. |
Timer | A one-second timer callback. |
CustomTimerDpc | For time-outs of less than one second. |
CustomDpc | Usually used to handle work queues. |
Reinitialize | Called if a driver takes a long time to initialize itself. |
ConfigCallback | Query device hardware description callback. NT and W2000 only. |
Plug and Play Notification | Called to notify you when devices have arrived, when the hardware profile changes, or when a device is being removed. |
Callback | W2000 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 function | IRP |
---|---|
CreateFile | Create IRP |
CloseHandle | Close IRP |
ReadFile, etc. | Read IRP |
WriteFile, etc. | Write IRP |
DeviceIoControl | IOCTL 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.
- 8.1.2. Device Driver Architecture
- Power Manager Components and Architecture
- Lesson 5: Implementing Power Management for a Device Driver
- Power Manager Device Drivers Interface
- JDBC Туре 4 DRIVER
- Test Driver Code
- Creating and Deleting Device Objects
- Installing Proprietary Video Drivers
- Introduction to Serial Devices
- Devices
- The Disk as a Storage Device
- Chapter 8. Device Driver Basics