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
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.
Win32 Program Interface
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.