52964.fb2
In this book, I will tell you how to write some types of device driver for Windows. I will primarily describe the Windows Driver Model for Windows 98 and Windows 2000. Additionally, I will cover device drivers that also run in Windows NT 3.51 and NT 4, which I call "NT style" drivers.
A device driver provides a software interface to hardware connected to a computer. It is a trusted part of the operating system. User application programs can access hardware in a well-defined manner, without having to worry about how the hardware must be controlled. For example, a disk driver might hide the fact that data must be written in 512-byte chunks. A driver is a piece of software that becomes part of the operating system kernel when it is loaded. A driver makes one or more devices available to the user mode programmer, each representing a physical or logical piece of hardware. For example, one physical hard disk may be viewed as two logical disks called C: and D:.
In Windows, a driver always makes a device look like a file. A handle to the device can be opened. An application program can then issue read and write requests to the driver, before the device handle is finally closed.
Clearly, there are many pieces of hardware that are essentially alike, because they share a bus or do similar tasks. Microsoft provides several generic drivers that perform these common tasks. Device drivers can use the facilities of these standard drivers. This approach makes it easier to share a common bus, and makes it simpler to write new drivers.
The task of writing a new driver, therefore, often starts by identifying which generic drivers can be used. A stack of drivers, layered one on top of each other, processes user requests in stages. A low-level bus driver might be used to handle all the basic communication with hardware. An intermediate class driver might provide the facilities that are common to a whole category of devices.
In Windows 98 and Windows 2000, device drivers must be designed according to the Windows Driver Model (WDM), which I describe in the following section. WDM is based on the device driver model used in Windows NT 4 and NT 3.51.
The Windows Driver Model has two separate but equally important aspects. First, the core model describes the standard structure for device drivers. Second, Microsoft provides a series of bus and class drivers for common types of devices.
The core WDM model describes how device drivers are installed and started, and how they should service user requests and interact with hardware. A WDM device driver must fit into the Plug and Play (PnP) system that lets users plug in devices that can be configured in software.
Microsoft provides a series of system drivers that have all the basic functionality needed to service many standard types of device. The first type of system driver supports different types of bus, such as the Universal Serial Bus (USB), IEEE 1394 (FireWire) and Audio port devices. Other class drivers implement standard Windows facilities such as Human Input Devices (HID) and kernel streaming. Finally, the Still Image Architecture (STI) provides a framework for handling still images, scanners, etc.
These system class drivers can make it significantly easier to write some types of device driver. For example, the USB system drivers handle all the low-level communications across this bus. A well defined interface is made available to other drivers. This makes it fairly straightforward to issue requests to the USB bus.
Originally Microsoft stated that WDM drivers would be binary compatible between Windows 98 and Windows 2000 x86, and source code compatible to Windows 2000 Alpha platforms. However, it now seems as though binary compatibility is not assured, even though the DDKs are unclear on the subject.
I have erred on the safe side, only installing drivers that have been built for the right operating system. That is, the Windows 98 Driver Development Kit (DDK) is used when building drivers for Windows 98, and the W2000 DDK for W2000.
If you use some WDM facilities that only appear in Windows 2000, then you may not achieve source code compatibility. For example, the W2000 USB system drivers support some features that are not available to W98 drivers.
I will look first at the core WDM functionality as a simple device driver is developed. Next, I will deal with drivers that have to use hardware resources such as accessing memory and handling interrupts. Finally, I will cover the USB and HID system drivers. Use the kernel routine IoIsWdmVersionAvailable to determine whether the required version WDM version is available. The DDK header files define two constants, WDM_MAJORVERSION and WDM_MINORVERSION. For Windows 98, these constants are 1 and 0. For Windows 2000, these are 1 and 0x10.
Figure 1.1 gives a rough indication of the differences between WDM and NT style drivers.
The rest of this book explains all the features mentioned in the figure.
The overlap between the two types of driver is considerable. Indeed, writing WDM and NT style drivers is essentially the same job. The main difference in the driver code is how devices are created.
In a WDM driver, the Plug and Play Manager tells you when a device is added or removed from the system. The PnP Manager uses installation INF files to find the correct driver for a new device. In contrast, an NT style driver has to find its own devices, usually in its initialisation routine. NT style drivers are usually installed using a special installation program.
The new bus and class drivers are only available to WDM device drivers. New WDM and NT style drivers should support the Power Management and the Windows Management Instrumentation features.
Figure 1.1 WDM and NT style device drivers
If you start writing a driver from scratch, it seems as though most of your code has nothing to do with talking to your device. There is much "infrastructure" to set up before you can perform some real Input and Output (I/O). This book should help you get started as I have tried to use some real, useful drivers as examples. Some drivers can be used directly, while others can form the basis of your own drivers.
A virtual device driver is used to explain the core WDM functionality. A virtual device does not use any real hardware. Three drivers, called Wdm1, Wdm2, and Wdm3, gradually implement more features. From the start, they provide a shared memory buffer so they could form the basis for other useful drivers. Indeed, the Wdm2 driver was used as the base for several other drivers in the book, including drivers that use the system drivers.
The DebugPrint software is used throughout the book to provide trace debugging output from drivers. The DebugPrint driver is examined in full in Chapter 14. You can use DebugPrint in your own device drivers.
WdmIo and PHDIo are general purpose drivers that can be used straightaway to provide access to simple hardware devices. A controlling Win32 program can use a simple but powerful command set to talk to the hardware. These drivers support interrupt-driven I/O. The example applications show how these generic drivers are used to talk to the parallel port.
The UsbKbd and HidKbd drivers both talk to a keyboard attached to the USB bus. The drivers illustrate the techniques required to use the USB and HID class drivers. Finally, the Win32 application HidKbdUser shows how a user mode application can find and talk to a HID device.
The accompanying CD contains all the drivers mentioned previously. The full source of the drivers is included, along with the compiled executables. Each driver has at least one test Win32 program that puts it through its paces. In addition, the book has some other useful Win32 utility programs that aid device driver development. Chapter 4 has full instructions for installing the book software. Most of the test Win32 programs are console applications to make them easy to write and understand; there is no reason why the code should not be in fully fledged windowing or MFC applications.
To obtain the full benefit of the book, you should install all the example drivers and run the Win32 test program. All the drivers include trace debugging statements. If the DebugPrint software is installed, you can see the trace output in the DebugPrint Monitor application.
A full inspection of the source code of each example will bear much fruit. In practice, one learns only by writing real code. Use these example drivers as a starting point, and add features to develop your understanding.
There are two types of software available to help you with your device driver requirement as shown in Table 1.1. Driver classes are source code C++ class wrappers that provide much of the default functionality needed by drivers. Generic drivers can be tailored from user mode to talk to many straightforward types of device. OSR have a debugging aid called OSRDDK. Chapter 4 discusses other tools that will be useful to you while developing device drivers.
Table 1.1 Device driver software tools
Company | Web site | Driver classes | Generic driver |
---|---|---|---|
Blue Water Systems | www.bluewatersystems.com | WinDK | WinRT |
KRF Tech | www.krftech.com | WinDriver | |
Compuware Numega | www.vireo.com | DriverWorks | DriverAgent |
Open Systems Research | www.osr.com |
In some cases, a new device driver may not be needed for new hardware. There are off-the-shelf generic products that handle simple I/O, be it memory mapped or in I/O space. Indeed, these may be a better option as NT and Windows 95 may be supported using the same interface, making your code portable. Even a simple NT device driver or Windows 95 VxD is no easy task. These generic drivers may be script-driven. Interrupt latency may be a problem if scripts have to be run when a user mode thread is scheduled. The WdmIo and PHDIo examples in the book are simple generic device drivers.
You may think that you need to write a new device driver to handle a special device attached to a parallel port. Check that you cannot achieve the desired effect using the full Win32 API interface. Perhaps issue asynchronous read or write calls and check for time-outs.
This book does not cover writing VxDs for Windows 95 or Windows 98. None of the drivers in this book will run in Windows 95. Windows CE software is not described. This book does not cover printer drivers or virtual DOS drivers.
The device driver model described in this book is the basis of many types of drivers. The details of the following driver types are not covered: file system drivers, video drivers, network drivers, and SCSI drivers.
As mentioned previously, only the USB and HID system drivers are described in detail.
The drivers in this book should run in non-x86 systems. However, they have not been tested there.
If you have "only" written Win32 application programs, you will find that writing device drivers requires a new frame of mind. There are no windows and messages to manipulate. You do not have the protective arms of Windows to stop you from trampling over other processes and the operating system. Source level debugging is more difficult to set up. Most support libraries are not available — not even the C standard library and the C++ new operator. You even have to use makefiles to build drivers, though it is easy to control the build process from a development tool such as Visual Studio.
As a device driver is a trusted part of the operating system, you can crash the system easily. (I can assure you that you will crash the system during your driver development.) Therefore, it is your responsibility to write safe and dependable code. Comment and test your code well. Check for error return values from every kernel call you make.
Device driver problems are a constant source of difficulty for users and support staff. Please insist that your driver is fully tested before release on an unsuspecting world. Test the driver on a variety of machines.
A device driver works in a demanding environment. More than one user application may be bombarding a driver with requests. A user program with open I/O requests may terminate suddenly. A driver may be running in a multiprocessor PC, with different pieces of the driver running on different processors. In fact, two read requests can be processed simultaneously by the same piece of driver code on two separate processors.
Low-level device drivers have to cope with hardware interrupts that may arrive at any moment. Only one part of a driver should access hardware electronics at a time. You may have to use Direct Memory Access (DMA) to transfer data from your device into memory, or vice versa.
Supporting configurable and hot-pluggable buses also adds to the burden of device driver writers. For example, a Plug and Play device might be removed by the user at any time. Also, the kernel can decide at any time that it needs to stop your device so that it can reassign all the hardware resources.
However, as mentioned previously, using the standard WDM bus and class drivers helps to reduce the amount of effort required to write drivers for certain categories of device.
Device driver writers come face to face with a huge range of terminology. Good specification documents will help hardware and software engineers work together to achieve a common goal.
You will need to understand how your device works. While you may not need to understand the details of its electronic implementation, it certainly helps if you have a working knowledge of its technology. For example, the Universal Serial Bus places certain restrictions on maximum packet sizes. You may need to split up data transfers to meet these requirements.
The Windows Driver Model itself uses many technical terms to describe its operation. I will gradually introduce you to all the structures and concepts needed for writing device drivers.
Each technology has its own specialized terminology. For example, the USB bus refers to interrupt transfers. These bear no relation to hardware interrupts. However, as this is how the USB specification describes its operation, I will stick to using the correct terms. Whenever a new and important piece of terminology is introduced, it is highlighted in italic.
A possible source of confusion is the word "class". Windows class drivers are standard drivers that you can use to access particular types of devices, such USB, HID, or IEEE 1394. In contrast, USB devices are categorized into various device classes. There is one USB device class for printers, another for audio appliances, etc. (Thank goodness I do not use C++ classes in the source code.)
To get you started in the device driver world, Chapter 24 lists many information resources, books, newsgroups, and mailing lists that may be of help. Chapter 24 gives a summary of the PC 99 specification — the hardware and software that should be provided on new Windows computers. Finally, the Glossary explains some of the many acronyms you come across when writing drivers.
Before I go any further, it is worth looking at how Win32 programs call a device driver. The Win32 specification has various implications for driver writers.
To Win32 programmers, a device is accessed as if it were a file, using the functions listed in Table 1.2. As well as open, read, write, and close routines, DeviceIoControl provides a driver with the option of providing any special functionality. Consult your Win32 documentation for full details of these functions.
Table 1.2 Win32 Program Interface
Win32 function | Action |
---|---|
CreateFile | Open device file |
CloseHandle | Close device file |
ReadFile ReadFileEx ReadFileScatter ReadFileVlm | Read |
WriteFile WriteFileEx WriteFileGather WriteFileVlm | Write |
DeviceIoControl | IOCTL |
CancelIo FlushFileBuffers | Cancel any overlapped operations |
A device driver provides one or more named devices to the Win32 programmer. If writing a device driver for a dongle that can be attached to any parallel port, the devices might be named "DongLpt1", "DongLpt2", etc. To the Win32 programmer these appear as \\.\DongLpt1, etc. (i.e., with \\.\ at the beginning). Note that when written as a C string this last device name appears as "\\\\.\\DongLpt1".
The CreateFile Win32 function is used to open or create a connection to a device. After the device filename, the next two parameters specify the read and write access mode and whether the file can be shared. CloseHandle is used to close the file handle when you have finished using the device file.
The ReadFile and WriteFile series of functions are used to issue read or write requests to the device file.
DeviceIoControl is used to issue special requests that can send data to a driver and read data back, all in one call. There are many predefined IOCTL codes that can be sent to standard drivers, but you can also make up your own for your driver.
When a process finishes, Win32 ensures that all handles are closed and it cancels any outstanding I/O requests.
One large set of functions deals with file systems. Similarly, serial port communication has its own set of specialised functions. Other operations, such as unloading the device driver or shutting down the system, can also result in your device driver being called.
Win32 supports asynchronous overlapped I/O calls, in which a program issues a read or write request and then gets on with another task. This feature has no impact on a device driver as any user request may be processed asynchronously from the Win32 process. In Windows 98, overlapped I/O requests cannot be issued to disk file systems, but can be issued to ordinary device drivers. Chapter 14 shows how to issue Win32 overlapped I/O requests.
Any number of Win32 threads could access your device at the same time, so your driver should expect this and cope correctly, even if the action is just to allow exclusive access by one thread. The kernel I/O Manager helps considerably by providing a mechanism for processing your read and write requests one at a time.
Your driver should be prepared to run on a multiprocessor system. Many of your driver routines need to be reentrant to cope with this situation. You have to ensure that your driver can cope with being run on two different processors at the same time, usually in different parts of the driver and possibly at different interrupt levels. Techniques for achieving these goals are described in this book.
Ideally, you should provide a version of your driver for each available CPU platform. This means compiling a DEC Alpha version as well as 80x86.
The end user may not be using English. For most I/O, this is not a problem for a driver.
However, if you log messages to the event log, it is nice to provide messages in a language that matches the administrator's locale. It should be easy to localize any support utilities that you provide.
Finally, your driver can determine whether Windows 2000 is running as a server or as a personal workstation. Server systems might have more memory and do more I/O.
In particular cases in which you are writing both the device driver and the user mode code, you may find it useful to put more restrictions on the type of access with which your driver can cope.
For instance, if implementing a particular protocol, you might dictate that a command has to be written first using WriteFile and the results read back using ReadFile. Alternatively, you could ignore WriteFile and ReadFile completely and just use DeviceIoControl with your own IOCTL codes.
Whatever approach you use, make sure that you follow a specification. If you are interfacing your hardware to a system class driver, you will have to work to a specification laid down in the Microsoft Driver Development Kit (DDK). Otherwise, you will have to create a specification for your API that your Win32 colleagues will have to follow.
The file metaphor is used for most device driver interactions in Win32 programs. However, Windows calls drivers in other situations. For example, keyboard keypresses arrive in programs as Windows messages. These keypresses come from a device driver. Internally, Windows calls the system keyboard driver using the file metaphor.
Many other specialised aspects of Win32 also use device drivers, DirectInput and DirectDraw to mention two. As another example, Human Input Device (HID) user mode clients use various routines, such as HidD_SetFeature, that end up as a driver call.
All these different ways of accessing drivers from Win32 end up as calls using the file metaphor. Therefore, all WDM and NT style device drivers have the same basic structure as they process the same sorts of calls.
In this book, I will explain the Windows Driver Model, including how to write device drivers that work in Windows 98 and Windows 2000. I will also cover NT style drivers that work in Windows NT 3.51 and NT 4.
A driver writer has a job completely different from a standard Windows programmer. There is much terminology to learn and each type of device has its own detailed hardware and software specifications. However, in the end, a device must be made available to Win32 programs and users.
Before writing a device driver in earnest, the next couple of chapters look at the big picture: device driver design and the crucial concepts and structures needed in the driver model.
If you are itching for something to do, order your MSDN Professional subscription. Install the MSDN library, the DDKs, and the Platform SDK. If you are writing a driver for both Windows 98 and Windows 2000, either set up a dual boot machine or get another test computer to hand.