The Little Device Driver Writer

Ruediger R. Asche
Microsoft Development Network Technology Group

Created: February 25, 1994

Abstract

This article provides an introduction to writing hardware device drivers for the Microsoft® Windows®, the next version of Windows (called Windows 95), the MS-DOS®, and the Windows NT™ operating systems. It covers the input/output (I/O) models of each particular operating system, as well as driver design and debugging strategies.

Introduction

Let us assume that you have recently been hired by a company that manufactures hardware devices. Your assignment is to write device drivers for the NPZ-3140 series of your company's top-selling adapter cards. Your marketing group says that they want to cover as many operating systems on the market as possible; thus, your drivers should ideally work for MS-DOS®, Microsoft® Windows®, UNIX®, Windows NT™, and OS/2®. The manager who has hired you has full confidence in your abilities and does not know diddlysquat about computers, so you are left on your own, with not the slightest idea of how to tackle this assignment.

If you fit this description, this article is for you. I try to provide you with all the information you need to know in order to start developing your drivers for MS-DOS, Windows, Windows NT, and even the next version of Windows, called Windows 95. (No, I will not elaborate on either UNIX or OS/2 here—sorry.)

The first section will cover the basics of device driver writing, whereas the second section describes the system architectures of the Windows NT and Windows 95 operating systems and how and where device drivers fit in. The final section outlines the pragmatics of device driver writing, that is, the tools you need to develop, debug, and test your drivers, and how to utilize them.

In this article, I will deal with two families of PC-based operating systems: the MS-DOS/Windows family and the Windows NT family. You will realize that the major difference between these families is that Windows NT is a designed system, whereas the MS-DOS/Windows stream has evolved over time and, therefore, carries a number of inconsistencies on its back. While the input/output (I/O) system of Windows NT has been designed to incorporate a multitude of I/O devices and technologies from the very beginning, MS-DOS was originally meant to run on fairly restricted hardware. As the IBM-compatible PC became more popular, however, support for new and more powerful hardware and technology had to be added, but since the systems had not originally been designed for flexibility and extensibility, the new support more often than not introduced a new application programming interface (API) set and an I/O model that would not fit in very well with the existing ones.

Device Drivers: The Basics

Device drivers serve several different purposes. In their purest form, they are the link between software and hardware. Depending on your point of view, you can see them either as part of the software (because they are generally implemented in software) or as hardware (because they are closely coupled with the hardware device they support, and the rest of the software cannot easily figure out which task is done by the driver and which by the hardware device).

Of course, this is a simplified definition. Many drivers do not, in fact, provide support for real hardware devices (such as mice, network cards, or disk controllers), but instead provide support for some logical concept, such as a file system, a network protocol, a logical random-access memory (RAM) drive, or a SCSI device class. For the general discussion, however, it is safe to state that a device driver is in some form responsible for providing a link between software and a physical I/O device.

The term software is deliberately generic—while a device driver normally communicates mainly with an operating system, it used to be fairly common (at least in the PC world) that applications were shipped with (sometimes significant) sets of device drivers that were specifically tailored to interface between the application in question and the hardware devices. I will not deal with application-specific device drivers here unless specifically mentioned because those kinds of drivers are seen less and less.

Normally, a device driver is part of an abstraction mechanism, that is, a software architecture that provides applications with hardware-independent, high-level access to I/O functionality while the lower, hardware-dependent levels are encapsulated in the device driver. For example, applications generally do not care where on a storage device a file resides; instead, they submit generic calls to the operating system to open, access, and close a file, and it is the task of the operating system and the device drivers to cooperatively locate the file on the storage device and read from or write to the correct physical locations of the device. Figure 1 depicts the control flow of a user request to the hardware device graphically. The advantage of this architecture is that no change needs to be made to the application if the hardware is changed.

Figure 1. Pure I/O flow model

The user interacts with an application. When he wants to perform some kind of I/O, say, save a file to disk, he asks the application to do the operation. The application will, in turn, submit the request to the operating system through the use of an application programming interface (or API, for short).

The API, as mentioned before, will deal with the requests in a high-level, device-independent way (such as through a call to SaveFile, a function that might be provided by the operating system). The operating system and the drivers will translate the call into something the hardware can understand. In order to do that, the operating system calls into a driver through a different interface, the SPI (or systems programming interface). Note that the term systems programming interface is not common terminology; generally all functional interfaces are called APIs. I use this new term to point out that there are essentially two different functional interfaces here.

The SPI reflects the I/O model, that is, the implied flow of control that the operating system defines for I/O calls on their way from the application to the hardware. The more general the I/O model, the more abstract the SPI tends to be; on the other hand, a more general I/O model will be able to incorporate more conceptually different hardware device types. For example, as we will see later on, the Windows graphical operating system defines different I/O models for keyboard and mouse input, and an even different model for output; consequently, drivers for those types of devices bear little resemblance to one another.

Figure 2 illustrates the same scenario for application-specific device drivers.

Figure 2. Application providing its own driver

The case illustrated in Figure 2 rarely occurs in modern operating systems. However, it happens frequently that an application does communicate with the driver directly through a well-defined "back door" interface often called IOCTL (or I/O control):

Figure 3. Application communicating with drivers via I/O control calls

This IOCTL architecture is provided so that the I/O model can be general enough to incorporate different device types, yet still have applications control the different device types in distinct, well-defined ways. For example, both a teletype and a serial communications port can be viewed as character devices that allow for bidirectional I/O, so it makes sense to apply the same I/O model to them; however, an application must be able to control a serial port differently from a teletype. For example, applications need to set the data transmission rate and query the communications status on a serial port, whereas they might want to query the teletype keyboard for specific character strokes (such as a control key). That kind of device-type-specific control is provided through the IOCTL interface.

Device Drivers for Microsoft Operating Systems

Microsoft provides two major operating-system families: Windows 95 (and its ancestors, MS-DOS and 16-bit Windows) and Windows NT. This does not imply that your device-driver development is limited to two types of drivers; on the contrary, you will be amazed to hear that there are as many as eight types of drivers that you need to worry about, let alone a few classes of "twilight drivers" that are not formally labeled as such but in reality serve the function of device drivers. Let us look at the system architectures of Windows NT and Windows 95 to figure out where your hardware comes into play.

Windows 95 and the Evolution of an Operating System

In the beginning there was MS-DOS. MS-DOS was an operating system designed for a fairly fixed hardware setup; when MS-DOS was designed, there was little need for a flexible interface that would have to be adapted to a multitude of different devices.

I will elaborate on the MS-DOS system architecture quite a bit because, for compatibility reasons, the MS-DOS API will persist, even though the MS-DOS–based architecture, as we will see, is being modified piece by piece. Thus, even for Windows 95 and Windows NT, there is still some degree of support for the MS-DOS model.

The MS-DOS I/O Model

MS-DOS, as most other operating systems, divides all I/O devices into two categories: character devices and block devices, the main distinction being that character devices read or write individual characters one at a time (such as keyboards, character-mode displays, or serial communications ports), whereas block devices generally access several bytes at a time (such as hard disk drives).

MS-DOS has a very small layered device architecture. The application submits calls on the operating system level; that is, the application calls generic routines through an API implemented by a set of software interrupts, most notably the infamous software interrupt 21h. Although there are other functionalities covered by INT 21h, let us focus on what happens when an application accesses a file via INT 21h. Based on the file allocation table (FAT) file system, MS-DOS translates those calls into device driver calls that are implemented through the software interrupt INT 13h. When you look at the specifications of those interfaces, you will see that INT 13h (which we could call the SPI, according to our terminology) works on the physical device level; that is, the interrupt allows you to address physical sectors on the disk drive, whereas INT 21h (the API) lets the application address files only on a logical level.

By default, INT 13h is hooked by the ROM BIOS, which normally provides a built-in driver for Winchester-compatible hard disk controllers. If you plan to write a driver for a different storage medium, you must redirect INT 13h to a resident MS-DOS driver. If you need to implement support for file systems other than FAT—Watch out! MS-DOS cannot boot from any drive that does not have the FAT file system—for a network redirector or any device that can be accessed through file calls, you need to hook into INT 21h.

Just as the BIOS provides a standard driver for hard disk controllers via INT 13h, it also provides drivers for the keyboard controller 8048 (INT 16h), the serial communications port 8250 (INT 14h), the parallel printer ports 8255 (INT 17h), and a few display cards (INT 10h). Unfortunately, those interfaces have become an integral part of PC programmers' folklore, and you will see a lot of MS-DOS–based applications out there in the market that call into those software interrupts directly; that is, they do not go through MS-DOS but access the device driver directly. We will see later why this is a problem.

The video interrupt 10h has sort of an interesting history. MS-DOS does provide a generic, hardware-independent interface for character-based input and output to and from a console (which is mostly implemented by a keyboard and a video display in character mode) through a subset of INT 21h functions, but there is no such interface for graphics output. If you look for a function that, say, allows you to output a rectangle on the screen, you are out of luck. Part of the reason is that a number of different competing standards for a graphics API have been suggested, such as Graphical Kernel System (GKS), but no single interface has been widely accepted as a standard yet. However, INT 10h provides a fairly low-level interface to address a number of common display-device types, and that is the closest to MS-DOS support for graphics output to the screen that an application can expect to get. Some C run-time systems provide a graphics function set, but none of those function sets are general and hardware-independent enough to be useful for a wide range of hardware devices.

For this reason, most applications do not use INT 10h, and a number of applications ship with custom device drivers for the most popular graphics adapters on the market. Those applications frequently define their own APIs to directly access the driver—in other words, both MS-DOS and the standard device-driver interfaces are bypassed. Since INT 10h is not very widely used and its specification lacks a number of advanced functions, many display drivers support only a subset of the INT 10h interface, or a self-defined superset that applications tailored specifically for that device may rely on.

To complete this discussion, it is worthwhile mentioning that the set of software interrupts used to access hardware has been modified several times. Originally, INT 15h used to be the device-driver entry point for the physical tape drive. When tape drives went out of fashion, the interrupt was redefined to serve a multitude of interfaces, in particular interfaces to access memory above 1 MB from real mode.

When expanded memory was introduced to the PC architecture, INT 67h was defined to be the software interface interrupt to access expanded memory specification (EMS). To make matters worse, different vendors defined different sets of functions. MS-DOS itself does not use the extended software interrupt interface; consequently, applications would address the device drivers directly.

Interrupt 33h was introduced as the API entry point for the Microsoft mouse. Although the API is fairly generic, there is no SPI provided; thus, mouse manufacturers who wish to provide drivers for their mice should provide a device driver that intercepts INT 33h and communicates with the hardware directly.

MS-DOS device drivers can be written either as static drivers or as terminate-and-stay-resident programs (TSRs). In both cases, the driver files are memory images that must fit into one 64K segment and store all code and data in that one segment. Those files cannot contain relocatable information because the loader does not know how to resolve that information. The difference between TSRs and static device drivers is that the latter must be equipped with a predefined device header that MS-DOS needs to chain devices together; the headers in TSRs will be filled in by the loader.

Number

Driver Type

Description

1

MS-DOS device drivers

Can replace drivers provided by the PC BIOS (such as display and keyboard drivers) or provide functionality unknown to MS-DOS, such as networks, network redirectors, or CD-ROMs.

Windows

After MS-DOS came Windows. For storage-device types, Windows did not replace the functionality provided by MS-DOS; instead, it routed most system-level calls, such as access to the file system, straight into MS-DOS, even after Windows was enhanced to take advantage of protected mode. It is worth mentioning here that protected mode is not directly MS-DOS–compatible. What happened was that a new interface called MS-DOS protected-mode interface, or DPMI, was introduced. Windows would act as a DPMI client that asked the server to translate a call made in protected mode to a corresponding call in real (or V86) mode that MS-DOS would understand. Consequently, Windows did not care to introduce a new general I/O model, directing most I/O requests to MS-DOS, the resident MS-DOS device drivers, or TSRs.

However, being a graphical user interface, Windows had to provide support for a few device types that MS-DOS does not support a priori, namely, pointing devices and the graphics modes of video displays. Furthermore, in order to support a wide range of devices, Windows completely abandoned all MS-DOS support for those device types, introducing I/O models of its own. In order to provide better localization support and support for the Windows input model, the keyboard driver that MS-DOS provided was also completely replaced. Finally, the communications driver for serial and parallel output was completely rewritten and does not call into MS-DOS either; however, all file I/O is being passed on to MS-DOS.

The underlying architecture for this implementation contained new systems programmer interfaces and a set of replaceable Windows drivers for those modules. From the point of view of the system, those drivers are in no way different from system and application library modules (so-called DLLs, meaning dynamic-link libraries), except that they must have specific predefined names and must provide certain functions that Windows calls into.

One of the problems with Windows physical device drivers is that there is hardly any consistency in the I/O model for the devices they support. For example, writing a keyboard driver and writing a serial communications driver bear almost no resemblance to each other; the system programming interfaces for those two types of devices are completely disjoint, although both device types can be characterized as character-based I/O devices. By the same token, the interfaces that applications use to access those device types are completely different from each other.

As we will see later, Windows NT treats serial communications ports pretty much like file streams, and MS-DOS considers keyboard input and console output pretty much like a file. Windows does neither.

This setup leaves novice Windows device driver programmers in a somewhat confused state. If the device for which you need to write a driver is a graphics card, a mouse, a keyboard, or a communications device, you as the driver programmer must write a DLL that implements low-level functionality for Windows to call into when the application requests input or output. However, if your device is a file storage medium, such as a SCSI controller, a CD-ROM drive, or a network redirector, you will have to provide a MS-DOS driver or TSR that will eventually be called after the application's I/O request has been routed to MS-DOS.

If your device fits neither of those categories—let us say that it is a fax board or an analog/digital converter—then it is your choice to implement it either way. Neither Windows nor MS-DOS provides any standardized interface to those types of devices; it is generally up to the device manufacturer to define an interface that can be implemented either by a MS-DOS driver or a Windows driver. Many manufacturers would provide a TSR because that driver could be used by both MS-DOS–based and Windows-based applications; however, TSRs generally use up precious conventional memory space that is not available to Windows-based applications, so that at times, both a Windows and MS-DOS device driver were shipped. If you want your device to be accessed like a storage medium (for example, to be able to read or write the device like a file), you must write an MS-DOS block device driver.

To make things even worse, you might have to provide any combination of the above drivers; for example, if you want your device to work for Windows-based applications, as well as MS-DOS–based applications under Windows, you might have to provide both an MS-DOS driver and a Windows driver and possibly also something that is called a virtual device driver (which we will discuss later).

Number

Driver Type

Description

2

Windows physical device drivers

Drivers that implement the upper end of the systems programming interface for graphics output devices, mice, keyboards, and communications devices.

Windows Printer Drivers

Printers under Windows used to be treated more or less like display devices, a fact that reflects one of the architectural peculiarities of the Windows graphics device interface (GDI): To an application, output devices are represented by a device-specific data type called a device context (or DC). A Windows-based application that wants to perform graphics output will request a DC for either a printer or the display from Windows and then for the most part merely submit output calls without really bothering about what the output device is. Windows would in any case call back into the device driver; a set of IOCTL functions (to be accessed through the API function Escape) allows the application to specifically address a printer (for example, determine when the end of the current page to be printed has been reached).

Consequently, a printer driver used to look very much like a display driver. A printer driver had to implement roughly the same SPI as a display driver. The back end of a printer driver, however, does not address the hardware directly, but instead outputs the binary control sequences to the printer via an interface to the spooler (which in turn addresses the parallel communications port, which may be redirected by the network).

The downside of this setup was that a lot of valuable time and money was wasted on writing multiple drivers for printers that only differed very slightly in technology. For example, most raster printers are based on very similar technology, and therefore, one printer driver, slightly altered for each model, would be sufficient to drive many different printers.

Based on this idea, the universal printer driver was introduced. Most printers under Windows are now accessed by only one driver, UNIDRV.DLL. The printer-specific data is encapsulated in a data file from which UNIDRV.DLL reads the printer-specific data at run time. In order to adapt UNIDRV.DLL for your printer, you can employ UNITOOL.EXE, a Windows-based application that allows you to interactively design your unidriver-compatible printer driver (frequently also referred to as a miniport driver).

Printer drivers for non-unitool printers (such as Postscript printers) must still be implemented like display drivers, the major difference being that on the low end the driver will not communicate with the hardware but with the spooler. (The Windows kernel exports a spooler interface to be accessed by the driver; this interface is documented in the Microsoft Windows version 3.1 Device Driver Kit (DDK) Device Driver Adaptation Guide).

Number

Driver Type

Description

2.1

Windows unitool-based printer drivers

Basically data files that encapsulate the differences between printer technologies for raster printers.

Installable Device Drivers

I mentioned earlier that in terms of the system architecture, there is no way for Windows to distinguish between physical drivers and other DLLs. This implies that a physical driver other than a system-provided driver can only be loaded by an application and will be unloaded by Windows as soon as the last application that links to it is freed from memory. In addition, there is also no straightforward way for a driver to determine whether it is active (for example, if the user enters a full-screen MS-DOS box, all of the Windows graphical user interface becomes inactive). This setup posed some serious problems to some of the advanced enhancements to the Windows architecture; in particular, it was difficult for the multimedia and pen components of Windows to work well with that small degree of control. Can you imagine your computer running a sound card for which a driver can only be loaded with an application?

For this reason, the concept of installable device drivers has been introduced. In order to write an installable device driver, you write a DLL (just like before), but that DLL must export a function called DriverProc that processes system notifications. Through the help of a new Windows interface, your application (or the system) can now install, uninstall, load, unload, enable, and disable drivers independent of an associated application. This feature, although designed for multimedia and pen drivers, as mentioned before, can also be exploited by your custom driver.

Number

Driver Type

Description

2.5

Installable Windows drivers

DLLs that receive system notifications and can be dynamically installed and uninstalled. Installable drivers must be written for multimedia devices (wave input and output devices and MIDI input and output devices); all other drivers, except for display drivers, can optionally be implemented as installable device drivers.

The Virtual Machine Manager (VMM)

Strictly speaking, Windows is not an operating system, but rather a family of operating systems. When your computer is running enhanced mode Windows version 3.1, in reality it runs two operating systems at the same time, namely, Windows itself and a low-level operating system called the virtual machine manager (VMM). The main purpose of the VMM is to provide an environment in which several MS-DOS–based applications can execute at the same time with each of them believing that they run on a dedicated machine. This works only because Intel® has provided its processors of the series 386 and above to execute in a so-called "virtual 86 mode," which to MS-DOS looks almost as if it would run in MS-DOS's native mode (real mode) but can be totally controlled by a low-level operating system such as the VMM. The processes in this multiprocess architecture are known as virtual machines (or VMs). In Windows enhanced mode, the Windows graphical environment itself runs in the so-called system virtual machine, which enjoys a few privileges but is otherwise treated just like any other virtual machine.

The VMM is an extensible operating system whose core and standard components are provided by Microsoft. By writing additional modules called VxDs (virtual device drivers), software and hardware vendors can complement the VMM, mostly in order to aid virtualization, but recently a lot of VxDs have been written to provide Windows-based applications with services that can only be realized with the help of VxDs, such as providing communications mechanisms among MS-DOS–based applications.

It is kind of hard to explain how VxDs fit into the picture for you as a device driver writer. To illustrate this, let us assume that your hardware device interrupts the CPU whenever something significant happens, and the application, in turn, submits I/O calls to a specified port to request information. Consequently, your device driver will establish an interrupt hook to process information from the device and submit the I/O calls on behalf of the application.

This holds true regardless of whether the hardware device is virtualized or not. It is the main purpose of the VxD to look at all hardware interrupts before your physical device driver processes them and, in turn, to look at all I/O calls before the hardware device receives them. The driver does not care whether it sees real physical interrupts or regurgitated ones, as long as they look real.

By the same token, the driver does not care whether the I/O calls it submits really do go to the hardware or will be first seen by a VxD that will then decide what to do with them, as long as it looks to the driver as if the communication with the device is the way it is supposed to look.

Think of it as Santa Claus. Kids do not really care whether their Christmas wish lists go directly to Santa or Dad, and the toy retailers do not care whether they sell to Santa or Dad. If you (Dad, or the VxD, for that matter) do a good job in virtualizing Santa, your kids (collectively modeled by a virtual machine as opposed to the neighbor's kids who are a virtual machine of their own) will firmly believe that they interact with the one and only Santa, while in reality they deal with a virtual Santa. Virtualization on the computer level works in a similar way.

The catch is that virtual machines can be very demanding kids. You might have several MS-DOS–based applications with their own drivers running, each of which addresses the hardware directly (or at least believes it does so), including interrupt handlers and I/O port accesses of their own. To correctly virtualize the hardware, a VxD may have to perform fairly elaborate tasks.

Note that writing virtual device drivers is something totally different from writing Windows device drivers. As the Windows 3.0 DDK Virtual Device Adaptation Guide states, "You do not need to know anything about Windows to write a virtual device driver." (17-1) This strict separation between applications and device drivers applies less to Windows 95, where a new architecture called "appy time events" has been introduced that allows VxDs to interact closely with Windows DLLs. (Please refer to my article "What's New in Windows 95 for VxD Writers?" for details.)

It may be the case that if your task is to write a device driver for Windows, you will need to provide both a physical device driver and a virtual device driver. This holds true for all display devices, most network devices, possibly some standard devices, and probably custom devices you need to support.

It should be noted that VxDs have much more power than merely that of providing hardware virtualization. Frequently, VxDs are written to replace or complement former MS-DOS device drivers or TSRs. The merit of doing so is that VxDs do not generally take up space in global conventional memory, which is still a bottleneck in Windows 3.1, and applications that rely on communication with a TSR will not have to be modified. (A VxD can be made to behave almost exactly like a TSR as far as V86-mode applications are concerned.) Also, since VxDs run in protected mode, expensive mode switches are avoided when you go for a VxD solution. In general, VxD-based drivers will be faster than MS-DOS drivers.

PC programmer's folklore has recently made VxDs an extremely hip concept, and you see a number of VxDs on the market that provide numerous nonvirtualization tasks, such as providing communications channels among MS-DOS virtual machines, interfacing between MS-DOS–based applications and Windows DLLs, or providing 32-bit Windows memory to MS-DOS–based applications.

For 95 percent of all applications written for the Windows graphical environment, there should not be a need to interface with a VxD at all. Note that many of the aforementioned VxD utilizations will not apply to Windows NT or Windows 95, so any reliance on VxDs will nail your applications to a particular platform.

Number

Driver Type

Description

3

Virtual device drivers

Drivers that help the VMM in virtualizing hardware to Windows-based and MS-DOS–based virtual machines.

Display Device Drivers and Grabbers

If the hardware device for which you need to write a Windows device driver is a display card, you will need to write both a physical device driver and a VxD. Well, two out of three ain't bad, as they say; in this context, that means that there is a third kind of driver that you will also need to write in order to make your graphics card work in the Windows environment, a so-called grabber.

The main responsibility of a grabber is to provide an interface between a character-mode MS-DOS screen and a Windows window. When a MS-DOS–based application running in windowed mode makes character output to its virtual screen, the grabber, with the help of fonts provided by Windows, will convert those characters to a bitmap that can be displayed in an application's window. (To be more precise, the application in whose window the output will occur is an instance of WINOLDAP.EXE, a Windows-based application that interfaces between MS-DOS–based applications and the Windows system.) Conversely, the grabber can reconvert parts of the window of a windowed MS-DOS–based application (which, as far as Windows is concerned, is only a bitmap) to the character sequence that it represents, so that a user can select text from a windowed application to copy it to the Windows Clipboard.

Technically speaking, a grabber is a Windows DLL, just like a physical device driver; the main difference as far as writing a grabber is concerned is that a grabber interacts closely with the corresponding display VxD, as well as the physical display device driver, the WINOLDAP application, and the Windows GDI.

Grabbers and their respective interfaces are documented in the Windows 3.1 DDK Virtual Device Adaptation Guide.

Windows 95 Layered I/O Drivers

As I mentioned before, Windows implementations up to and including Windows version 3.0 did not process file system calls themselves but rather called into MS-DOS to access file storage media. One of the main problems with this approach was that the processor had to switch back and forth between different execution modes several times in order to satisfy one request, and the resulting overhead slowed down the execution of the system quite a bit.

To relieve this problem, Windows 3.1 introduced a new architecture that employed several VxDs to process calls to access the disk controllers in protected mode instead of V86 mode, thereby cutting down on the number of mode switches. Another benefit of this new architecture was that it allowed INT 13h to be called nested so that MS-DOS–based applications could now be paged, which was not possible before because of reentrancy problems.

Windows 95 goes even further in putting MS-DOS out of work. Beginning with Windows 95, all MS-DOS file system calls are processed in protected mode in a VxD. Somehow related to the layered device driver architecture that we will discuss later on for Windows NT (the difference being that Windows NT's architecture is much more general), a complete new I/O system architecture has been designed. This architecture addresses storage devices and allows for a much greater control and flexibility over the control flow. The heart of the architecture consists of two Windows 95 novelties: the dynamic VxD loader (VXDLDR.386) and the layered I/O system provider VxD (IOS.386). It is the main responsibility of the IOS VxD to catch I/O calls that user-mode applications perform to file storage devices and route them to a set of layered VxDs that will cooperatively process the calls. The nice thing is that the lower end of the I/O model (that is, the drivers that communicate directly with the hardware) are in part compatible with the Windows NT model, which we will discuss in the next chapter (in part because this applies only to SCSI miniport drivers).

Beginning with Windows 95, there should no longer be any reason to write an MS-DOS device driver or TSR. As I pointed out before, VxDs can achieve exactly the same functionality; they leave a smaller footprint in conventional memory and generally execute code faster. Under Windows 95, system calls are not normally relayed to global V86 code; however, if there is an existing MS-DOS driver or TSR that must be called into for compatibility reasons (for example, an existing encryption/decryption driver), you can tell Windows 95 to still call down to V86 mode. This works through yet another Windows 95 novelty, the so-called safe driver list, to which are added all MS-DOS drivers whose functionality Windows 95 suspects is being taken over by a VxD. If there is an MS-DOS driver installed that is not on the safe driver list, Windows 95 will determine what software interrupts are hooked by that driver and chain the calls down to that unsafe driver.

Finally, it should be noted that TSRs must undergo major difficulties to work correctly in multithreaded or multitasking environments. Under Windows 3.1, it is possible to make TSRs multi-VM-compliant by instancing TSR data and employing the INT 2fh interface to control VM switching, but under new multithreaded architecture of Windows 95, it may very well be even more difficult to get the TSRs to work correctly.

Furthermore, all direct interaction between applications and device drivers, aside from the DeviceIOControl API (which has been defined by Windows NT and adapted by Windows 95), is discouraged and would require a certain amount of work because 32-bit applications cannot easily communicate with the hardware due to the architecture of Windows 95. For details, please refer to my article "What's New in Windows 95 for VxD Writers?"

The other Windows 95 system VxD I mentioned before, VXDLDR.386, has been designed to be able to dynamically load and unload device drivers. Except for Windows installable device drivers, no type of driver mentioned so far can ever be unloaded once it is are loaded (save after a system shutdown), and many drivers (for example, MS-DOS device drivers) can only be loaded at system boot time.

The dynamically loadable VxD interface allows Windows 95 to support more sophisticated hardware, such as dockable workstations, and provides a mechanism to reduce the footprint of VxDs that do not need to be loaded all the time.

Number

Driver Type

Description

4

Windows 95 Plug-and-Play drivers

Dynamically loadable VxDs.

 

4a

Windows 95 file system drivers

Plug-and-Play drivers that are embedded in the Windows 95 layered I/O system structure. On top of employing the VMM's system services, they use the system services provided by the I/O system VxD IOS.386.

 

Network Drivers

If you think that the device driver world for MS-DOS, Windows, and Windows 95 is convoluted, I hate to tell you that you ain't seen nothing yet. Network drivers are probably worth an article by themselves. Whereas under Windows NT network support is built into the operating system, MS-DOS architecture has absolutely no idea of a network. Thus, under MS-DOS, networking is generally implemented as extensions to the operating system. There are several such extension packets on the market, most notably Novell®'s Netware®, Banyan® Vines®, and Microsoft's LAN Manager, to mention only a few. Those packets implement several elements of a network operating system, among others a network redirector (that is, a module that allows remote drives to be mapped to logical disk drives), a command interpreter that allows the user to interact with the network software (for example, to connect to or disconnect from remote logical drives), possibly new network-aware file systems, security features, and services (such as pop-up message providers).

All of those systems define their own APIs for applications to access network functionalities and their own SPIs for drivers to attach to. To make matters even worse, drivers in the network world can mean a number of different things. There are network card drivers, which address physical network cards, as well as protocol drivers, which are in charge of decoding and encoding the incoming and outgoing data packets according to a specific network protocol. Needless to say, drivers that are written for the network software of one vendor frequently do not have any meaning whatsoever for network software provided by another vendor.

Windows for Workgroups implements some of the network functionalities as part of the operating system in the form of virtual device drivers.

In the MS-DOS/Windows/Windows 95 world, you frequently find a liberal mix of MS-DOS TSRs, Windows drivers, and VxDs to implement network support; you will also typically find that all kinds of interactions exist between those driver types and at times even between applications and drivers.

Good sources of information for network-related device and protocol driver work are the Windows NT Resource Kit, the Windows 3.1 Device Driver Kit, and Windows NT Device Driver Kit. (The documentation for all these kits is in the Development Library.)

Windows NT

If you have begun to be really discouraged because the MS-DOS/Windows/Windows 95 family of operating systems is extremely fragmented and has you put a lot of work and design into a seemingly simple device driver, you should look into the Windows NT system architecture. Windows NT has been designed with very ambitious goals in mind, one of them being to provide a very generic I/O model that incorporates as many different I/O devices as possible. Another Windows NT design goal was to make the operating system general enough to run on different hardware platforms, implying that the parts of Windows NT that interact with device drivers must be totally hardware-independent.

The Windows NT I/O Model

When looking at the Win32® API, you will notice that there are very few functions to access specific device types. For example, there is no OpenComm function anymore as in the 16-bit Windows API, so how does one access a communications port under Windows NT? Simple, through a call to CreateFile. Once a communications port has been successfully opened, calls to ReadFile and WriteFile will communicate through the communications port, just as they would be used to access a file.

As far as an application is concerned, Windows NT treats all input/output streams absolutely alike. When an application tries to open an I/O stream (regardless of whether the I/O stream corresponds to a file on a hard disk, a communications port, a file on a remote logical network drive, a universal naming convention (UNC) file, or whatever), the Windows NT kernel analyzes the name of the stream and, employing a mechanism known as "symbolic links," decides what driver to pass the request to. Please refer to the discussion in Helen Custer's Inside Windows NT (1993, pp 53-68) for more information on how this works. Once an appropriate driver has been found, Windows NT returns to the user-mode application a handle by which the I/O stream can be accessed. Whenever an I/O request is submitted, Windows NT will dispatch the request to the appropriate driver as identified by the handle. This way, one API set is sufficient to address almost all hardware devices, and your driver will need to implement a fairly generic SPI function set.

All drivers that you can write for Windows NT will fall into one of three categories: user-mode drivers, kernel-mode drivers, and virtual device drivers (VDDs), which should not be confused with Windows 95 VxDs. One more group of programs fall roughly into the category of drivers that are services. In a nutshell, a service is a background user-mode process that may act as a server process for all applications. For example, access to dynamic data exchange (DDE) or remote procedure calls (RPC) is provided by services. From the point of view of a service developer, a service looks a little bit like an installable device driver under Windows 95 in that it will receive system notifications and can be installed and loaded by the system, but a service is a process and not a DLL (as an installable driver is); plus, installable Windows 95 drivers are 16-bit drivers (which, of course, do not exist under Windows NT).

One of the really cool features of Windows NT is that it provides only one interface through which both services and kernel-mode drivers can be installed and loaded, the so-called service control manager. The service control manager can access both the local machine and a remote machine. Services and kernel-mode device drivers are referenced in the same format and section in Windows NT's system registry. Both services and kernel-mode device drivers can be started and (possibly) stopped either statically or dynamically, or through different techniques—either through software API calls or manually by the user using control panel applets or command-line commands such as "net start."

Note, however, that this architecture does not necessarily make the process of setting up and configuring a driver easier. To the user and system administrator, there are a number of different means to install and configure the driver, almost all of which can be found in Windows NT's control panel, such as the network, drivers, devices, or system control panel applets. However, eventually all of the changes that are being made through any of those techniques will get reflected in the same registry subtree.

Win32 User-Mode Drivers

Win32 user-mode drivers pretty much correspond to what I described earlier as physical drivers for 16-bit Windows; they are support modules that are used by the Windows NT graphical environment to implement the systems programming interface (SPI) from Windows to the driver.

Those drivers are labeled user-mode drivers because Windows NT distinguishes between two processor modes: user mode and kernel mode (sometimes referred to as executive mode). User mode is the less privileged mode; the architecture of Windows NT executes both user-mode applications and protected subsystems (such as the Windows NT graphical environment) in user mode, whereas all I/O device drivers execute in kernel mode.

The good news is that the number of drivers you need to write on this level is much smaller than under 16-bit Windows. Following the above discussion about the generic I/O model under Windows NT, there is no user-mode mouse, keyboard, or communications driver anymore; neither should there be any need to implement multimedia-type drivers as user-mode drivers (unless performance issues would force components to be written in user mode). The Windows kernel, which under 16-bit Windows defined its own disjoint SPIs for all of the above types of drivers, in the protected Win32 subsystem under Windows NT only needs to call CreateFile for the appropriate device type, which implies that the drivers for those devices run in kernel mode.

The only devices for which the kernel mode I/O model is not applicable are display output devices. While kernel-mode drivers are provided for displays, for performance reasons the Win32 subsystem does the actual work of translating an output call made in the GDI model to one that a hardware device can process. (The kernel-mode device driver exposes an interface to the user-mode driver to manipulate video memory directly.)

Note that, analogous to 16-bit Windows, there is a universal printer driver architecture that allows you to write drivers for certain printers interactively using UNITOOL.EXE. For non-unitool printer drivers, the discussion is analogous to 16-bit Windows.

Number

Driver Type

Description

5

Windows NT user-mode drivers

Display drivers that map the Windows graphics model onto output device hardware and non-unitool printer drivers.

Windows NT Kernel-Mode Drivers

Under Windows NT, the only way for any software to communicate directly with the hardware is by means of an kernel-mode driver. As mentioned earlier, kernel mode is the privileged execution mode of Windows NT. Privileged means that there is no way for the operating system to monitor this code or to protect memory from being accessed, as is the case with user-mode code. Thus, kernel-mode drivers had better know what they are doing—they are trusted by the operating system.

Theoretically, kernel-mode drivers can do whatever they want, including monitoring access to Windows NT's system resources. For this reason, kernel-mode drivers can only be installed by a user who is granted permission to do so; all of this is built into Windows NT's security mechanism.

Kernel-mode drivers can be layered, that is, Windows NT will pack up a user's I/O request into a data structure specific to the I/O model (an I/O request packet) and send the packet off to the driver that corresponds to the device. The driver can either process the request or do something with it and pass the modified request on to another driver. This is extremely useful, for example, for mass storage devices. The request will initially go to a file system driver, that is, a driver that understands file systems such as FAT (file allocation table) or NTFS (New Technology file system). The request can then be passed on to an intermediate driver (for example, a driver that encrypts and decrypts the files on the fly), and can eventually be routed to a driver that accesses the physical device, possibly after having traversed even more intermediate drivers, such as mirror drivers.

This way the file system is logically separated from data manipulation and the physical storage medium. Aside from this separation, layered device driver architectures have the advantage that third-party data manipulation techniques such as data encryption can easily be incorporated without modifications of any existing drivers. Unlike MS-DOS, where, due to the complicated interrupt control flow in a mixed protected/real mode environment, it is very hard to locate any particular entry point through which all data accesses must go, Windows NT's architecture ensures that any access to a particular storage device will necessarily be routed through the driver chain.

Layered device structures can be applied to any I/O device type under Windows NT. Another concept similar to layered device drivers is a variation of the familiar unidriver architecture. In many cases, classes of a particular device can be implemented in a so-called class driver with the particularities encapsulated in a miniport driver. Under Windows NT, this architecture is not only implemented for printer drivers, but also for SCSI controllers and certain kinds of video displays. Unlike unitool printer drivers, however, SCSI miniport and video miniport drivers are active, that is, they contain executable code, whereas unitool-based drivers are merely collections of data.

Number

Driver Type

Description

6

Windows NT kernel-mode drivers

Drivers that are responsible for implementing logical and physical I/O devices.

Windows NT Virtual Device Drivers (VDDs)

Virtual device drivers serve a similar purpose to the one Windows 95 VxDs originally served—that is, they provide a means to virtualize hardware to MS-DOS–based applications. Just like Windows 95, Windows NT allows several MS-DOS–based applications to execute concurrently, with each application being tricked into believing that it runs on a dedicated 8086 or 80286 computer running MS-DOS. (In the next version of Windows NT, 80386-specific instructions will also be supported.) Any application that was written for MS-DOS and assumes that it has exclusive access to a specific piece of custom hardware must be complemented with a means to interact between the application and an kernel-mode driver for that hardware; this is what VDDs are for. Unlike Windows 95 VxDs, however, VDDs are implemented as user-mode DLLs, which do not have full control over the machine.

You will need to write a VDD if you have an MS-DOS–based application or TSR that communicates directly with your custom hardware and relies on functionalities that are not directly available to applications written for Windows NT, such as establishing interrupt hooks.

Here is a word of warning, though: VDDs are not meant to be integral components of Windows NT installations. As 16-bit MS-DOS–based applications are supposed to go away over time, so should VDDs. If you really must write a VDD, keep in mind that (a) you must also write a kernel-mode driver for the same piece of hardware, and (b) over time, the culprit MS-DOS–based application will be rewritten to be a native Win32 application that does the same thing, only better and faster with less code.

Number

Driver Type

Description

7

Windows NT virtual device drivers (VDDs)

User-mode DLLs that aid in virtualizing custom hardware to MS-DOS–based applications.

Getting to Work: How to Write Your Driver

After all the theory, we are now ready to go to work. Let us look at the most common problems that your driver needs to attack. We will then see how those steps apply to the different types of drivers as explained before. Eventually, we will put together a list of all the driver types and their peculiarities.

This is what you need to do in your driver:

1. Process input from the hardware as well as output to the hardware.

Apparently, this step only applies to physical-device drivers, not intermediate ones. Generally, communication between hardware and software is realized through one of the following three techniques (or any combination of them, depending on how the hardware is implemented).

Hardware interrupts generated by the device

If your device supports hardware-interrupt generation, you will need to provide a hardware-interrupt handler. Every operating system provides a mechanism to do that; under MS-DOS, the process is very closely related to programming the programmable interrupt controller chip 8259 (that is, the relationship between a hardware interrupt number and its corresponding software interrupt is generally hardcoded, as well as the I/O sequence to finish processing and masking/unmasking an interrupt), whereas under Windows NT and the VMM, very generic calls are provided to intercept an interrupt vector and process an interrupt.

Note that the hardware-interrupt reflection mechanism in the MS-DOS/Windows family of operating systems is fairly convoluted and intricate. A hardware interrupt can be hooked by an arbitrary number of V86 drivers, an arbitrary number of protected user-mode DLLs, any number of VxDs, or any combination thereof. It is even possible for a user-mode application to take ownership of hardware interrupts on the VxD level; the corresponding interface (the bimodal interrupt handler interface) has been provided mainly to relieve performance problems. Check the article "Bimodal Interrupt Handlers" (MSDN Library Archive, Technical Articles) for details.

Hardware-interrupt processing is a fairly tricky task in all operating systems because you need to be aware of whether hardware-interrupt handlers can be reentered (which will impose certain requirements on your code) as well as ensure that generally all code and data that is touched by a hardware-interrupt handler is always resident in memory. Furthermore, it is normally the case that many hardware interrupts will not be processed while another hardware-interrupt handler is executing; thus, you will generally fine-tune your interrupt handlers so that they use up as few CPU cycles as possible. Also, due to the asynchronous nature of hardware interrupts, it is very possible that writing the interrupt handler may deadlock the system or corrupt data in the usual pathologic, rarely reproducible manner that is typical for asynchronous programming. (You might want to refer to the series of articles on multithreading in the Development Library, for example, "Synchronization on the Fly" or "Detecting Deadlocks in Multithreaded Win32 Applications.")

I/O calls submitted to the device

The keys to this communication strategy are two seemingly innocent machine language calls called in and out. (The terminology is taken from the x86 architecture; on MIPS®-based machines, the calls are named differently, but serve the same purpose.) Each hardware device that provides for I/O-port–based communication can be attached to one or more unique ports that are identified to the main processor by a number. By writing a specified bit pattern to a port or reading a bit pattern from the port, the hardware communicates with the CPU. Your device driver will need to submit those calls. Note that under Windows NT, port access is available through platform-independent input/output SPIs provided by the operating system.

Many virtual device drivers that provide hardware virtualization will intercept I/O calls in order to sort out concurrent access to a hardware device from multiple MS-DOS–based applications.

Direct memory access (DMA)

This is a fairly common technique for devices that make large data transfers. The hardware adapter and the processor decide to share a specified range of memory that both sides can access asynchronously. Under Windows NT, DMA is provided as part of the hardware abstraction layer module (HAL) and can be accessed from the lowest-level kernel-mode device drivers.

DMA is a common cause for incompatibilities among MS-DOS drivers when executing in enhanced-mode Windows. The reason for this is that memory is virtualized under the VMM such that memory that looks physically contiguous to an MS-DOS driver is, in fact, remapped somewhere else. MS-DOS or Windows drivers that need to be able to communicate with hardware devices on a DMA channel must either be complemented with a VxD or request DMA through the VDS (Virtual DMA Specification) interface that the virtual direct access memory driver (VDMAD) implements.

2. Serialize the hardware with the I/O stream.

One of the toughest problems to attack in device driver writing is that the hardware normally does not execute synchronously with the software, that is, a hardware signal can come in any time, regardless of the state the software is in. Since the software may not be ready to process the hardware input when it comes in, some means must be provided to queue the hardware input until it can be processed, or conversely, queue output to the device until the device is ready to process it. Some operating systems (such as the VMM and Windows NT) provide a mechanism to do so; other devices may have to provide the data structures and control mechanisms themselves.

"Scheduling events" under the VMM and "queuing deferred procedure calls (DPCs)" under Windows NT are similar concepts that are provided for serialization purposes. Aside from that, both Windows NT and Windows 95 provide a set of synchronization primitives (mutexes, semaphores, and events) that can be employed to synchronize access to shared data.

3. Process requests from the operating system.

This step does not apply to all device drivers; for example, VxDs that virtualize hardware are generally not called from the operating system for each I/O request, but instead sort out the hardware input and output from and to the virtual machines. Those drivers that do process I/O requests will need to provide functions that the operating system will call whenever an application program submits an I/O request.

The function set to be provided by your driver is determined by the I/O model. For example, an MS-DOS block device driver will need to hook itself into the INT 13h chain, wait until an INT 13h is encountered, look up the desired function code in the AX machine register, and dispatch to the appropriate function. For a Windows physical mouse driver, the driver must call the mouse_event function as provided by Windows' USER module, whereas a Windows display driver would need to provide entry points for fairly specific output requests, such as Output, ExtTextOut, or StretchDIBits. It may even be the case that there is no I/O model provided (as is true for Windows physical drivers for nonsupported devices such as analog/digital converters, in which case you will need to define an I/O model yourself).

On the other side of the spectrum, a Windows NT device driver must register a set of callback functions in a data structure that is provided by the operating system. Once one of the callback functions has been invoked by Windows NT, a so-called I/O request packet (IRP) must be decoded and interpreted.

Almost any driver for a physical device will also need to initialize the device at some point. Depending on the I/O model, this will take place at a predefined point in time (such as in the implementation of the Enable function for Windows physical drivers or the Device_Init notification for virtual drivers) or on demand.

Note that in some cases, it might make sense to have the driver automatically test the hardware upon initialization; for example, many communications drivers probe the I/O ports they expect the communications ports to be attached to in order to gain some kind of heuristic information as to whether the port is physically installed.

5. Trap error conditions and provide recovery strategies.

Many possible error conditions may arise during an I/O operation, beginning with hardware failures, data losses occurring when internal buffers overflow, or power failures to such harmless things as a storage device being out of space to store yet another file. Depending on the I/O model provided by the operating system, such conditions can either be propagated back to the system or be processed by the driver. The Windows NT I/O system requires that most functions that can process any request called by the system must return a status value of type NTSTATUS. This and the requirement that all Windows NT I/O status values are global throughout the system ensures that error conditions are never left up to the driver to process but instead always get propagated back to Windows NT, which can then decide whether to pass the error value on to the requesting application or to invoke an error handler of its own.

Windows NT also provides a systemwide event logging mechanism that can be used, among other things, by device drivers to write failure messages into the event log. This feature is a great way to inform both users and system administrators of causes for failures in device drivers.

On the other hand, Windows physical device drivers may shut down the system altogether, and virtual device drivers may decide to terminate individual processes or shut down the system as well.

6. Provide a means to install and configure the driver.

The process of installing a device driver and passing it configuration-specific parameters, such as the interrupt request (IRQ) to which a specific device is set and the I/O ports through which the CPU communicates with the device, differs very widely from operating system to operating system. In most cases, the location of the driver's executable image needs to be added to a system configuration file (such as CONFIG.SYS, WIN.INI or SYSTEM.INI, or Windows NT's registry) either by hand or programmatically, whereas the strategy of passing configuration information to the driver takes different forms, depending on the operating system. For MS-DOS drivers, command parameters passed to the entry that corresponds to the driver in CONFIG.SYS can be read by the driver at startup time; Windows NT provides references to a registry section that stores driver-specific parameters. Windows 95, Windows, and the VMM allow for custom entries in system initialization files.

The hardware abstraction layer (HAL) of Windows NT also provides a mapping mechanism that allows multiple I/O buses to be attached to one machine; if that is the case, device drivers use system-assigned virtual interrupt request vectors instead of physical ones.

Beginning with Windows 95, a novel architecture called Plug-and-Play is introduced to aid, among other things, self-initialization of hardware devices. A device that is compliant with this specification would provide a means to inform the device driver of its settings (for example, what IRQ it uses or what I/O ports it employs) so that the user would truly need only to plug in the device and not have to worry about any manual configuration anymore.

Symmetrical Multiprocessing

Windows NT was designed to run on a multitude of hardware platforms, including multiprocessor architectures. This implies that your kernel-mode driver for Windows NT needs to be made aware of the possibility of running on those kinds of machines. Be prepared that this poses quite a challenge to device driver writing—not only may several threads try to access the same data physically at the same time, but it may also happen that an interrupt service routine interrupts another processor than the one that is busy processing a deferred procedure call from the previous interrupt generated by the same device. There are other ways in which multiprocessor architectures differ from single processor systems; if possible, try to test your drivers on as many architectures as possible to ensure that they are stable under all the environments that Windows NT supports!

Tools for Driver Development

By now you should have at least a fuzzy idea about where your driver development is going and how you would attack it—if you had the tools to do so. In this section I list all the driver types I've discussed along with the tools you will need to develop them.

Compilers and Assemblers

Almost all kinds of executable drivers can be written in C, although some types require at least a stub written in assembly language. All compilers and assemblers provided by Microsoft and third parties can be employed, as long as they generate object modules in the Common Object File Format (COFF) for all Windows NT drivers or the Microsoft object format for all MS-DOS/Windows/Windows 95 drivers. An additional requirement for drivers for Windows NT is that structured exception handling must be supported by the compiler; also, in the future there will be some additional compiler requirements that may only be satisfied by Microsoft compilers.

Also note that all driver types for MS-DOS, Windows, and Windows 95, except VxDs, imply the segmented 16-bit execution model available on the Intel x86 family of microprocessors, whereas VxDs and all types of Windows NT drivers imply the flat 32-bit execution model. (Also refer to the next section for more information.)

Executable File Types and Linkers

Almost all driver types discussed are shipped as executable files, that is, files that contain executable code (the only exception is printer minidrivers). Unfortunately, different driver types are expected to take on a variety of file formats.

  1. The native MS-DOS executable format present in 16-bit old executable files. This format is recognized by the built-in MS-DOS loader. There are two variations: Nonrelocatable ("tiny model") memory images and relocatable segmented files. Typical extensions of files of this format are .SYS (for MS-DOS drivers), .COM (for nonrelocatable executables), and .EXE for relocatable executables.
  2. Executables for these formats can be generated using either the linker that is shipped with a 16-bit compiler or LINK.EXE as shipped with MS-DOS.

  3. The native 16-bit Windows executable format present in 16-bit new executable (NE) files. This format is roughly an extension of the old executable file. Any executable file that has this format is basically an old executable file with another whole portion tucked at its end. This information is built after the Windows system architecture and describes segment properties as well as resources. Typical extensions of files of this format are .EXE (for Windows executables), .DLL (for generic Windows DLLs), and .DRV (for Windows drivers).
  4. Executables for these formats can be generated using either linker that is shipped with a 16-bit compiler, such as MS Visual C++.

  5. An extension of the new executable file format that provides for 32-bit support present in 32-bit enhanced new executable files. This file format is understood by the VMM's VxD loader.
  6. Executables for these formats must be built using LINK386.EXE as shipped with the Windows or Windows 95 DDKs. An additional utility, ADDHDR.EXE, appends the VxD header to the executable.

  7. The format designed for use by the 32-bit operating systems that Microsoft builds, as in 32-bit portable executable files. All 32-bit applications (.EXE files) follow this format (even if they run under Win32s on top of Windows 3.1); also, 32-bit DLLs (.DLL), Windows NT services, and Windows NT drivers (.SYS) are built as portable executable files.

Every linker provided to generate portable executable files can be used to build your drivers, for example, the linker provided with Visual C++ (32-bit). Future versions of Microsoft's 32-bit linkers will be able to generate VxD executables as well as 32-bit NE executables.

Libraries and Header Files

Almost all types of drivers require some kind of support by the operating system. Depending on the type of driver, this support may be very rudimentary (as is the case for most MS-DOS drivers, where all back calls into the operating system can only be accomplished, if at all, by submitting software interrupts), header-file–based only (which is the case for VxDs written in assembly language, in which all calls to the VMM are syntactic extensions that resolve, again, to a software interrupt, namely INT 20h), or fairly elaborate (such as in Windows NT, where device drivers dynamically link into one or more system DLLs that provide a number of services, ranging from memory manipulation helper functions to services that access the system registry or dispatch I/O packets to other drivers).

Each device driver type requires its own header files and (possibly) libraries. With the exception of MS-DOS drivers and Windows printer miniport drivers, the header and library files for each driver type can be found in the corresponding DDKs that are included on the Microsoft Development Platform.

Debuggers

Ideally, you would like to be able to debug fully at the source-code level all applications and drivers for all platforms with the same cool graphical user interface (GUI) debugger, right? Unfortunately, there is no such thing, but whereas in the past you had to know a dozen different debuggers, now a few can take you a long way.

MS-DOS/Windows/Windows 95

Currently, no GUI debugger is available to debug drivers for the Windows 95 family of operating systems. The two choices I offer are Microsoft's WDEB386.EXE and Nu-Mega's SoftIce for Windows. Note that there are other debuggers on the market as well; I do not imply anything about the quality of any product here. All I mean by listing those two debuggers here is that they are the only two I have used.

Both debuggers have a character-oriented interface. WDEB386.EXE requires a serial terminal, whereas Nu-Mega's SoftIce for Windows can be executed either on a serial terminal or the debuggee machine itself. Also, there is symbolic but no source-level debugging available for WDEB386, but both source-level and symbolic debugging are available under SoftIce.

Windows NT

Windows NT requires that you execute your debugger on a second machine running the same version of Windows NT. A character-mode kernel debugger is provided with the Windows NT DDK (I386KD.EXE or MIPSKD.EXE, depending on the processor type in the debuggee machine). Although the user interface of the kernel debugger (KD) is fairly cryptic, there is support for debugging at the source-code level.

Beginning with Windows NT 3.5, it will be possible to debug kernel- and user-mode device drivers, as well as 16-bit and 32-bit applications and services with the same cool GUI source-code level debugger, that is, WINDBG.EXE.

Aids for Testing Your Drivers

The device driver kits for both Windows 3.1 and Windows NT are shipped with device driver test suites for certain kinds of physical drivers. Those test suites are specifically designed to test your drivers for the correct functionality. Those tests do not measure performance; for some driver types (such as physical display drivers) you might want to run benchmarking tests (of which there are several provided by third parties) to ensure that the drivers along with the hardware achieve satisfactory performance.

Microsoft also provides help for driver vendors in the form of the Microsoft Compatibility Labs, a group that is dedicated to testing your driver for you. In Appendix A, I have included a flier that helps you decide whether contacting the Microsoft Compatibility Labs group to have your driver tested is a good option for you.

Summary

In this section I list all the types of drivers we have discussed and summarize the preceding discussion for your reference.

Type 1: MS-DOS Device Drivers

Description

Can replace drivers provided by the PC BIOS (such as display and keyboard drivers) or provide functionality unknown to MS-DOS, such as networks, network redirectors, or CD-ROMs.

Functionality

Block and character mode I/O drivers for MS-DOS.

Examples

MSCDEX CD-ROM driver; most MS-DOS network drivers.

Format

16-bit non-relocatable memory image file <64K; either stripped (.SYS) or executable (.COM).

IOCTL support

System defined (INT 21h subfunction set).

Build tools necessary

16-bit assembler/compiler and linker; EXE2BIN.EXE.

Debug tools and setup

Nu-Mega SoftIce for MS-DOS; Microsoft WDEB386.EXE; MS-DOS DEBUG utility (only limited use). Serial terminal desirable (mandatory for WDEB386.EXE).

Installation of driver

Manual or programmatic edit of CONFIG.SYS file.

Sample driver (including source)

Robert Lai, Writing MS-DOS Device Drivers, 1987.

Further reading

Robert Lai, Writing MS-DOS Device Drivers, 1987.

Type 2: Windows Physical Device Drivers

Description

Implement the upper end of the systems programming interface for graphics output devices, mice, keyboards, and communications devices.

Functionality

I/O drivers for mice, keyboards, displays, printers not covered by UNIDRV.DLL, networks, serial and parallel communications ports.

Format

Non-privileged 16-bit Windows DLL (.DRV).

IOCTL support

Depends on driver type. Escape function for output drivers; other drivers do not have a consistent I/O model.

Build tools necessary

16-bit C compiler; dynamic linker; resource compiler (Visual C++); device driver header files and libraries (Windows 3.1 DDK).

Debug tools and setup

Nu-Mega SoftIce for Windows; Microsoft CodeView®; Microsoft Visual C++™ debugger; Microsoft WDEB386.EXE.

Installation of driver

Manual or programmatic edit of SYSTEM.INI file (for example, Control Panel applets).

Sample driver (including source)

See Microsoft Windows 3.1 DDK.

Further reading

Daniel Norton. Writing Windows Device Drivers, 1992.

Type 2.1: Windows Printer Minidriver

Description

Basically data files that encapsulate the differences between printer technologies for raster printers.

Functionality

Printer-specific support modules for the universal printer driver UNIDRV.DLL.

Format

Data tables.

IOCTL support

N/A

Build tools necessary

UNITOOL.EXE (Windows 3.1 DDK).

Debug tools and setup

Embedded in UNITOOL.EXE.

Installation of driver

Implicit edit of WIN.INI through Control Panel.

Sample driver (including source)

See Microsoft Windows 3.1 DDK.

Type 2.5: Installable Windows Drivers

Description

DLLs that receive system notifications and can be dynamically installed and uninstalled. Installable drivers must be written for multimedia devices (wave input and output devices and MIDI input and output devices); all other drivers, except for display drivers, can optionally be implemented as installable device drivers.

Functionality

Drivers for the Windows multimedia architecture, pens, and custom devices.

Examples

Soundcard drivers, MIDI drivers, joystick drivers.

Format

Nonprivileged 16-bit Windows DLL (.DRV).

IOCTL support

Driver-definable.

Build tools necessary

16-bit C compiler; dynamic linker; resource compiler (Visual C++); device driver header files and libraries (Windows 3.1 DDK).

Debug tools and setup

Nu-Mega SoftIce for Windows; Microsoft CodeView; Microsoft Visual C++ debugger; Microsoft WDEB386.EXE.

Installation of driver

Manual or programmatic edit of SYSTEM.INI file (for example, via the Control Panel applet, Drivers).

Sample driver (including source)

See Microsoft Windows 3.1 DDK.

Further reading

Long, David. "Writing an Installable Driver." (MSDN Library Archive, Technical Articles)

Type 3: Virtual Device Drivers (VxDs)

Description

Help the VMM in virtualizing hardware to Windows and MS-DOS virtual machines.

Functionality

Windows enhanced-mode, kernel-mode support modules for virtualization of hardware devices or low-level functionality.

Format

Privileged enhanced 32-bit new executable files (.386).

IOCTL support

Through API entry point or software interrupt.

Build tools necessary

32-bit assembler and (optionally) compiler; NE linker; ADDHDR.EXE; VMM header and preprocessor files and library (Windows 3.1 DDK).

Debug tools and setup

Nu-Mega SoftIce for Windows; Microsoft WDEB386.EXE.

Installation of driver

Manual or programmatic edit of SYSTEM.INI.

Sample driver (including source)

See Microsoft Windows 3.1 DDK.

Further reading

Microsoft Windows 3.1 DDK Virtual Device Adaptation Guide, 1987–1992.
D. Thielen and Bryan Woodruff. Writing Windows Virtual Device Drivers, 1994.

Type 4: Windows 95 Layered Device Drivers (Plug-and-Play)
Type 4a: Windows 95 File System Drivers

Description

Plug-and-Play drivers are dynamically loadable VxDs. Windows 95 file system drivers are Plug-and-Play drivers that are embedded in the Windows 95 layered I/O system structure. On top of employing the VMM's system services, they use the system services provided by the I/O system VxD IOS.386.

Functionality

Windows 95 kernel-mode drivers for layered I/O devices (storage devices).

Format

Privileged enhanced 32-bit new executable files (.386).

IOCTL support

Through predefined, device-type–specific I/O control calls and DeviceIOControl.

Build tools necessary

See Windows Virtual Device Drivers.

Debug tools and setup

Nu-Mega SoftIce for Windows; Microsoft WDEB386.EXE.

Installation of driver

Manual or programmatic edit of SYSTEM.INI.

Sample driver (including source)

Ruediger Asche, "What's New in Windows 95 for VxD Writers?," April 1994.
Windows 95 DDK (in beta).

Further reading

Ruediger Asche, "What's New in Windows 95 for VxD Writers?," April 1994.

Type 5: Windows NT User-Mode Drivers

Description

Display drivers that map the Windows graphic model onto output device hardware; printer drivers.

Functionality

Analogous to Windows physical device drivers.

Format

Nonprivileged 32-bit portable executable (PE) format DLL.

IOCTL support

Through the Escape function.

Build tools necessary

32-bit compiler (optionally assembler) and linker; header files and libraries as provided in the Windows NT 3.1 DDK.

Debug tools and setup

Windows NT debugger WINDBG.EXE (remote via serial terminal or networked computer) or Windows NT kernel-mode debuggers (requires secondary Windows NT machine).

Installation of driver

Manual or programmatic edit of Windows NT registry.

Sample driver (including source)

Windows NT 3.1 DDK.

Further reading

Windows NT Device Driver Kit Win32 Subsystem Driver Design Guide and Win32 Subsystem Driver Reference.

Type 6: Windows NT Kernel-Mode Drivers

Description

Drivers that are responsible for implementing logical and physical I/O devices.

Functionality

All Windows NT I/O device drivers and intermediate drivers.

Format

Privileged 32-bit PE DLL (.SYS).

IOCTL support

Through predefined, device-type–specific I/O control calls and DeviceIOControl.

Build tools necessary

32-bit compiler and linker; possibly assembler.

Debug tools and setup

Windows kernel-mode debugger (requires secondary Windows NT machine).

Installation of driver

Manual or programmatic edit of Windows NT registry (for example, via the Control Panel applets Drivers, Devices, or Services).

Sample driver (including source)

See Windows NT 3.1 DDK.

Further reading

Windows NT 3.1 DDK Kernel-mode Driver Design Guide and Kernel-mode Driver Reference.

Type 7: Windows NT Virtual Device Drivers (VDDs)

Description

User-mode DLLs that aid in virtualizing custom hardware to MS-DOS–based applications.

Functionality

To aid virtualization of MS-DOS–based applications under Windows NT.

Format

Nonprivileged 32-bit Windows PE DLL.

IOCTL support

N/A

Build tools necessary

See Windows drivers.

Debug tools and setup

Windows NT debugger WINDBG.EXE (remote via serial terminal or networked computer) or Windows NT kernel-mode debuggers (requires secondary Windows NT machine).

Installation of driver

Manual or programmatic edit of Windows NT registry (for example, via the Control Panel applets Drivers, Devices, or Services).

Sample driver (including source)

See Microsoft Windows NT 3.1 DDK.

Bibliography

Asche, Ruediger, and David Long. "Bimodal Interrupt Handlers." October 1993. (MSDN Library Archive, Technical Articles)

Asche, Ruediger. "Detecting Deadlocks in Multithreaded Win32 Applications." January 1994.

Asche, Ruediger. "Synchronization on the Fly." September 1993.

Asche, Ruediger. "What's New in Windows 95 for VxD Writers?." April 1994.

Custer, Helen. Inside Windows NT. Redmond, WA: Microsoft Press, 1993.

Egan, Janet, and Thomas Teixeira. Writing a UNIX Device Driver. New York, NY: John Wiley, 1988.

Lai, Robert. Writing MS-DOS Device Drivers. 2d ed. Reading, MA: Addison-Wesley, 1987.

Long, David. "Writing an Installable Driver." May 1993. (MSDN Library Archive, Technical Articles)

Microsoft Windows NT version 3.1 DDK Kernel-mode Driver Design Guide. 1993.

Microsoft Windows NT version 3.1 DDK Kernel-mode Driver Reference. 1993.

Microsoft Windows version 3.0 DDK Virtual Device Adaptation Guide. 1990.

Microsoft Windows version 3.1 DDK Device Driver Adaptation Guide. 1987-1992.

Microsoft Windows version 3.1 DDK Virtual Device Adaptation Guide. 1987-1992.

Norton, Daniel A. Writing Windows Device Drivers. Reading, MA: Addison-Wesley, 1992.

Thielen, D., and Bryan Woodruff. Writing Windows Virtual Device Drivers. Reading, MA: Addison-Wesley, 1994.

Appendix A: Hardware Compatibility Test Information

Customers Shop with Confidence for Compatibility-Tested Products

The Microsoft Compatibility Labs (MCL) are your gateway to the exclusive world of products certified compatible with the Microsoft® Windows™ operating system. Only products that pass our rigorous tests are entitled to display the Windows Compatibility Testing Program logo. That logo is your customers' assurance that your product will work with the world's most popular operating system.

The phenomenal success of the Windows Operating System, coupled with the explosive growth of the PC market, has encouraged hundreds of independent hardware vendors and original-equipment manufacturers (OEMs) to enter the market.

That's good for consumers; it gives them more choices at better prices. But it can also cause confusion: How are buyers to know if the product they're considering will work with their Windows-based software?

Their best assurance is the Windows compatibility logo. When customers see the logo on a product—your product—they know it will fit right in with their system with no problems.

The logo isn't the only thing you get from the program. You also get a detailed test report, free distribution of your driver, and inclusion on the Windows Hardware Compatibility List.

It's Easy to Do

We've made it as easy as we can for you to take part in the Microsoft Compatibility Testing Program, and we've set up the program so you don't pay anything or incur any obligation until we actually start testing. The best way to start is to give us a call at (206) 635-4949. We'll answer all your questions about the program and send you everything you need to get started.

The details vary depending on the kind of device to be tested, but in general, the program works like this:

  1. First comes the paperwork, but it's not bad. Just fill out our brief agreement form and send it to us. We'll send back detailed instructions for proceeding with the program.
  2. Then you do some testing, following the instructions we provide. We call this "core testing," and it checks the basic compatibility of your device and driver. Because you're doing the testing at your location, you can conduct the tests and fix problems on your schedule.
  3. When you complete the core testing satisfactorily, send us your hardware device and driver and some detailed information about your product. We tell you exactly what you need to send. At this time, you also send payment for the test program.
  4. We perform more extensive tests on your device and driver. If we find any problems, we let you know right away, and we'll do what we can to help you correct them.
  5. When the device and driver pass the second level of tests, we'll send you a detailed report of the results.

Here's What We Do

The first thing we'll do, when we receive your agreement, is assign a test engineer to your product and driver. The test engineer will be your contact throughout the testing program and will personally conduct the tests in our lab. If any problems show up, the test engineer will work with you to help you diagnose and solve them.

The exact tests we conduct depend on the kind of device and the operating systems it's intended to work with. We test all features of your device with all features of the appropriate operating systems, including:

For more information about the tests for each type of device, see the chart on the back page. If your product doesn't seem to fit one of the categories, give us a call. We'll give you a quote for custom testing.

Here's What You Get

If your product doesn't pass the tests, you'll get a written report that describes the problems, and you'll have the opportunity to talk with the test engineer if you have questions. We want your device and driver to pass the tests ultimately, so we'll do everything we reasonably can to help you correct the problems.

When the product passes the tests, you'll get a detailed written report that describes the scope of testing performed on your product and the exact hardware and software configurations used. You'll find the depth of information in our test reports useful for both support and marketing activities.

In addition, we will:

Here's What It Costs

Fees depend on the type and number of devices to be tested. The following chart shows the standard fees. Fees are subject to change. You should check with the Microsoft Compatibility Labs to confirm exactly what the fees will be for your test program.

Microsoft Compatibility Labs Testing Programs

Storage device

$1,000-$2,000 per device

MCL tests storage devices (CD ROM drives, tape drives, hard disk drives, etc.) with Windows NT only. Core testing is based on test tools contained in the Hardware Compatibility Test (HCT). In addition, devices and adapters are tested in a diverse hardware environment with a standard set of host adapters, tape drives, CD ROM drives, and hard-disk drives.

Video device

$1,000-$1,500 per device

Video devices are tested with Microsoft Windows 3.x and Microsoft Windows NT. Core testing is based on tests contained in the DDK. In addition, devices are tested with a suite of 32 Microsoft and third party applications on a broad range of systems. Note: We have significantly changed the fee structure for this program. Fees are no longer based on the number of resolutions supported by your adapter. All color depth and pixel resolutions supported by an adapter will be tested for a flat fee based on adapter RAM.

Network adapter

$2,500-$5,500 per device

Network adapters and drivers are tested with Microsoft LAN Manager, Microsoft Windows for Workgroups, and Microsoft Windows NT. Testing is offered for both NDIS 2.01 and NDIS 3.0 drivers. Core testing is based on tests contained in the DDK. In addition, devices are tested with all Microsoft protocols on a wide range of system platforms.

Audio device

$2,000-$3,000 per device

MCL tests audio devices with Microsoft Windows 3.x and Microsoft Windows NT. We also perform testing for OEM adaptations of the Microsoft Windows™ Sound System. Core testing is based on tests contained in the DDK. In addition, devices are tested with a suite of Microsoft and third party applications on a wide range of PC systems.

Modem

$500-$1,000 per device

MCL tests modems with the Microsoft Windows 3.x and Microsoft Windows NT Operating Systems. Fax modems are tested for compatibility with the Microsoft At Work™ architecture. Testing is performed with the test tools contained in the modem HCT. The modem HCT is available from MS developer services; call (800) 227-4679x11771.

Printer

$3,500-$5,000 per device

MCL tests printers with the Microsoft Windows 3.x and Microsoft Windows NT Operating Systems. Core testing is performed with the test tools contained in the Device Driver Kit (DDK); in addition, compatibility testing is performed with 32 applications and an average of 10 test files per application.

System

$500-$7,000 per system

We test systems for compatibility with the MS-DOS Operating System, Windows 3.x, Microsoft OS/2® 1.3, Microsoft LAN Manager, and Windows NT Advanced Server. Core testing is based on the HCTs. After core compatibility is established, we will test your system for 2 weeks with a standard set of network adapters in a high-stress LAN environment.

Note Pricing will vary depending on features supported by your device and driver and the number of Microsoft operating systems used for certification.

Request for MCL Information Packet

Company

_______________________________________________________

 

 

Product Name

_______________________________________________________

 

 

Device Type

_______________________________________________________

 

 

Microsoft Operating Systems supported

MS-DOS
Windows 3.1
Windows for Workgroups 3.11
MS LAN Manager 2.x
MS Windows NT 3.1

 

 

Contact

_______________________________________________________

 

 

Phone

_________________________

 

 

Fax

_________________________

 

 

Internet /CIS ID

_________________________

 

 

Address

________________________________________________________

 

 

City State Zip

________________________________________________________

Mail or fax the information above to:

Microsoft Compatibility Labs
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052-6399

Phone: (206) 635-4949

Fax: (206) 936-7329

Internet: MCLinfo@Microsoft.com

CompuServe: 72350,2636