Every operating system places requirements on device drivers. Here's a quick tour of the Windows CE rulebook.
Microsoft Windows CE is a modular and scalable operating system that supports a variety of devices. This article presents Windows CE's device driver model, which is unique throughout the Windows family.
Before exploring the details of the device drivers supported by Windows CE, let's review the two types of devices that are used on Windows CE platforms: built-in devices and installable devices.
Built-in describes those devices that are integrated into the platform. Devices that fall in this category include the display, touch panel, audio, serial port, printer, keyboard, LED, battery, and PC card socket. The development of the related drivers takes place during the development of the platform itself (by the OEM), and the drivers are integrated into the final CE image, which is stored in ROM or flash.
Installable devices are third-party peripherals that can be connected and detached from the platform at any time by the end user. For instance, a bar code reader can be connected to a Windows CE device by means of a cable that connects to the built-in serial port. Third-party vendors (who manufacture the installable devices) provide the device drivers. Those drivers can be installed at any time (in the object store, which is non-volatile RAM, for instance).
Windows CE supports four driver types: native, stream interface, USB, and NDIS. Each type is described in the following paragraphs. It is important to mention that this driver model is unique to CE; Windows 9x/NT/2000 drivers are not supported. Furthermore, CE drivers are implemented as either static libraries (.lib files) or dynamic-link libraries (.dll files).
Windows CE has been designed to directly use built-in devices. These devices are controlled by native drivers, which are intimately linked to Windows CE's core components. For example, the graphics, windowing, and events subsystem (GWES) module calls specific functions in the display device driver to render images at runtime. As one might expect, these functions are different from those required in the battery driver. Hence, each native driver must conform to a specific, well-defined interface called the device driver interface (DDI). These interfaces are explained in more detail later. Suffice it to say at this point that none of these interfaces can be changed by OEMs and that native drivers must fully support them.
Native drivers are built as dynamic-link libraries, with two exceptions: the battery and the LED drivers are built as static libraries (linked with the GWES module when a CE image is built) because of their small size. Native drivers are always loaded at boot time.
Stream interface drivers
Stream interface drivers all share a common interface. They are mostly used for controlling installable devices (a scanner, for example), but a few are used with built-in devices, such as the serial port device driver, because the stream interface is better suited for those devices.
Stream interface drivers that control installable devices are typically accessed by applications. For instance, upon connecting a GPS device to the platform, the user will start a GPS-enabled application that will access and use the device.
Microsoft chose to re-use an existing API (specifically, the file API) to enable the applications to access those drivers, instead of creating a new one. Consequently, stream interface drivers have been designed to expose a device's capabilities to the applications by presenting the device as a special file, which can be opened, read from, written, closed, and so on.
To be easily identified, stream interface drivers follow a unique file naming convention, which is composed of a three-letter prefix (such as CAM for camera or BCR for bar code reader), a digit that identifies a specific device when multiple instances are available, and a colon. Valid names are COM1:, COM2:, BCR1:, and so on. The three-letter prefix can be any combination of uppercase letters, but it must be unique on a given platform.When an application opens a file that follows this convention, the file system module recognizes that a driver is being accessed and re-routes subsequent file system calls to the specific driver.
In contrast to the native drivers, the stream interface drivers all share a common interface composed of 10 functions, or entry points, within each driver. These functions, described in Table 1, closely match those found in the file API that are used by the applications. For a given driver, each function must start with the driver's prefix, which is shown as “xxx” in Table 1.
|Table 1 Stream interface|
|xxx_Init||The driver is loaded|
|xxx_DeInit||The driver is unloaded|
|xxx_PowerOn||Power is turned on (restored)|
|xxx_PowerOff||Power is turned off (suspend mode)|
|xxx_Open||An application opens the driver|
|xxx_Close||An application closes the driver|
|xxx_Read||An application reads device-dependent data|
|xxx_Write||An application writes device-dependent data|
|xxx_Seek||An application positions the file marker|
|xxx_IOControl||An application issues a driver-dependent command|
In Listing 1, an application accesses the serial communication driver (COM1:) by opening it via CreateFile() , resulting in a call to COM_Open() in the serial driver. (CreateFile() combines the ability to create and open files. There is no OpenFile() call.) The driver returns a handle, which is then used with regular Win32 file functions, such as WriteFile() and ReadFile() , respectively processed by the driver's COM_Write() and COM_Read() functions. CloseHandle() is then called to close the handle. The driver's COM_Close() is then invoked to release internal resources.
An independent hardware vendor has the freedom to adapt these functions to the device's capabilities. For instance, in a camera driver, the xxx_Read() function may return the data of a complete picture, whereas the xxx_Seek() function may position the file marker at the beginning of a specific picture. The xxx_IOControl() function is commonly used to implement operations that do not fit in the other functions. The driver documentation becomes crucial for enabling developers to understand how to use the driver when writing applications that access the device.
Stream drivers can be loaded at various points in time. For built-in devices, such as an audio card, stream drivers are loaded at boot time via entries in the Registry. For detectable devices (a serial port implemented on a PC card for instance), the related driver is loaded at detection time, again using entries in the registry. Finally, applications can specifically request a driver to be loaded at run-time, by calling LoadDriver() , for instance. Stream drivers are loaded, managed, and unloaded by the device manager module.
Stream interface drivers typically rely on native drivers to perform their duties. For instance, a bar code reader will rely on the serial port driver to physically access the device. In this instance, the bar code reader is called a client driver, and would use the Win32 file API to access the serial driver, the same way an application would. Client drivers are useful because they encapsulate implementation details, such as processing data into a bitmap image, that applications do not have to be concerned about.
Universal Serial Bus (USB) is a communication protocol that describes fast serial data transfer between a single USB host and numerous USB peripherals, such as keyboards and mice.
Windows CE only supports the host side of the USB specification. This means that any USB-capable device can be connected to a CE platform, but a CE device cannot act as a USB device to another computer. The full USB host-side specification is supported on CE, including the various data transfer methods-control, isochronous, interrupt-driven, and bulk.
USB drivers can be implemented using three approaches. First, a USB driver can be implemented as a standard stream-interface driver, allowing applications to use the file API to access the device. Second, a USB driver may interact with an existing Windows API, if such an API already exists for the type of the device. Microsoft's USB mouse sample driver uses this approach-the USB driver directly interacts with the mouse API. Third, a USB driver may provide its own API, which may best fit the device's capabilities.
The Network Driver Interface Specification (NDIS) connects network drivers to:
- Protocol stacks (TCP/IP and IrDA)
- Network adapters (an Ethernet adapter, for example)
Only a subset of the original specification, NDIS 4.0 for NT, is supported on CE. As a result, miniport drivers are supported, but monolithic and full drivers are not. Fortunately, miniport drivers are largely source-compatible with those on NT.
Microsoft provides various NDIS driver samples, and recommends porting an existing NT driver, if one is available, rather than creating one from scratch. It is best to consult the documentation found in the Windows NT Device Driver Kit (DDK) to develop NDIS drivers, as the DDK documentation for CE does not discuss the process of writing NDIS miniport drivers in detail.
Device driver implementation
Adapting Windows CE to a specific platform is mostly a matter of developing the drivers for the particular devices the board features. (The other aspects are to configure, build, and test the Windows CE image itself.) Driver development, as I will explain, consists of implementing interrupt handlers (driectly in the kernel) and the code that access the devices (the drivers themselves).
Interrupts are processed in unique fashion in Windows CE, as shown in Figure 1. The kernel's event handler is the first to catch a raised interrupt. The interrupt, as well as lower priority interrupts, are disabled, but all other interrupts of higher priority remain enabled (provided the underlying architecture supports nesting interrupts based on their priorities). The related interrupt service routine (ISR) is then called.
The ISR resides in the OEM adaptation layer (OAL), a series of low-level functions written by OEMs that sit between the kernel and the hardware (not in any driver). The sole purpose of the ISR is to return a logical identifier to the interrupt service handler (ISH) in the kernel, but it can implement more functionality if performance is an issue. Since Windows CE 3.0 supports nested interrupts, ISRs can be interrupted and must be coded accordingly. For instance, ISRs that implement critical code that can't be preempted must temporarily disable all interrupts. However, this should be done only to execute a few instructions, since keeping higher-priority interrupts disabled may interfere with the real-time nature of the system (more about this later).
The kernel uses the returned logical identifier to unblock the related interrupt service thread (IST). The IST resides in the driver and implements the core driver's functionality (interrupt processing) by accessing the hardware as required. Once the interrupt is processed, the IST signals the end of interrupt and the kernel re-enables the interrupt.
Interrupt service routines (ISR) are installed in OEMInit() . Developers can assign one handler per interrupt or one handler for multiple interrupts. Listing 2 shows parts of the serial communication interrupt handling on an x86 platform. In this example, interrupt request lines (IRQ) are mapped to logical identifiers via a table called MapIntr2Sysintr . When the ISR is invoked, it obtains the IRQ number and returns the associated logical identifier. The code is adapted from Platform Builder 3.0's CEPC OAL samples.Listing 3 shows parts of the IST of a serial communication driver for an x86 platform. The code is adapted from Platform Builder 3.0 CEPC samples. At initialization (COM_Init()) , an event object (pSerialHead->hSerialEvent) is associated with a logical identifier (the same used by the ISR in Listing 2) and the IST is started (IST()). This thread waits for the event object to be set by the kernel (when the ISR returns the related interrupt identifier), then handles the interrupt. Once the interrupt is processed, the thread calls Interrupt-Done() to signal the interrupt processing completion to the kernel.
As mentioned above, built-in devices can be controlled by native drivers, which are linked with the GWES, and stream interface drivers, which areloaded by the device manager. Drivers linked with the GWES must adhere to a predefined interface called the device driver interface (DDI), whereas stream interface drivers are required to implement the standard stream functions. Drivers for built-in devices can have a layered or monolithic architecture, as illustrated in Figure 2.
A layered driver relies on a piece of code that can be reused across platforms to simplify and shorten the development time. This code, provided by Microsoft, is called the module device driver (MDD) and implements the core functionality of the driver. It doesn't access the hardware directly, but relies on another piece of code, which is hardware-dependent, called the platform-dependent driver (PDD). When porting a layered driver from one platform to another, only the PDD has to be rewritten, not the entire driver. An interface exists between the MDD and PDD (depending on the driver). It's called the device driver service provider interface (DDSPI). This interface defines the functions in the PDD that are called by the MDD at runtime.
If performance is an issue, one might prefer to develop a given driver as a monolithic piece of code, as opposed to a layered one. The development time is increased, and the code cannot easily be ported to another platform, but a tighter integration-which is what a monolithic driver provides-might be the only approach to meet certain performance requirements.
Whichever architecture is retained, the driver must always conform to the DDI for the device it controls. The DDI cannot be changed (that would require changes in Windows CE itself) and must be fully supported.
Most drivers for built-in devices follow a layered architecture because Microsoft provides layered samples for them. Nonetheless, an OEM can re-implement those drivers to better fit its devices. For instance, an OEM may alter the MDD, as well as the DDSPI, if it makes sense to do so. The only drawbacks are that the modified MDD might not be as portable as the original (of course, this might not be an issue), and changes might be required in the PDD.
Microsoft provides a driver development tool called Platform Builder. Actually, Platform Builder allows building, downloading, and debugging complete Windows CE images (including device drivers) for various processors. It integrates an editor, cross-compilers for all supported processors, a linker, an integrated debugger, an image loader, and a flock of additional tools.
New to Platform Builder v. 3.0 is the addition of hardware debugging capabilities, which allow bypassing bootloaders during development (permitting the device driver development phase to take place early) and debugging system initialization code and ISRs in the OAL.
Platform Builder provides numerous driver samples (some for each of the four driver types) targeted to both PC and generic platforms, respectively called CEPC and ODO. It also includes the device driver test kit (DDTK) to test the native drivers extensively.
Windows CE v. 3.0 underwent significant modifications to become a real-time system. Device drivers are not immune to those changes and can, in fact, diminish real-time performance if not properly implemented.
As mentioned earlier, nested interrupts are supported based on their priorities. Note that some architectures do not support nesting interrupts on their priorities (x86 is one), so this concept does not always apply. If it does, ISRs must be written accordingly. For instance, critical sections might require turning off all interrupts. This is even more true if, for performance consideration, the interrupt is directly processed in the ISR instead of the IST. In such a case, turning off interrupts directly impacts the systems' interrupt latency, and may degrade the overall system performance if not carefully fine-tuned.Interrupt service threads are subject to premption by the scheduler based on their priority, a situation that may not be acceptable in some cases. Under 3.0, drivers have up to 256 priorities to choose from to ensure that they execute in a timely manner.
Real-time processing can be achieved by raising a given IST's priority, but again, the impact of doing so must be reviewed against the other ISTs in the system. It is a good practice to store a driver's priority in the registry where it can easily be modified, as opposed to hard-coding it in the driver. This naturally requires the driver to load the priority value from the registry and adjusts the IST's priority accordingly once created.
An IST can also set its quantum (the time it executes before the scheduler kicks in) to a value other than the default (which is set in the OAL). In particular, an IST can set its quantum to 0, which means run to completion. However, whatever the quantum value is, interrupts are still processed. Should a higher priority thread become ready to run as the result of processing an interrupt, this thread will preempt the current thread. Therefore, run-to-completion is useful if the driver doesn't want to be interrupted by another thread of the same priority (in a multi-threaded driver for instance). Running to completion has little impact on the overall system performance, since higher-priority threads can still preempt as needed.
There is no doubt that developing drivers for Windows CE 3.0 is much easier than doing so for previous versions, thanks to the much refined development tools and the numerous driver implementation samples now available. And an increasing number OEMs provide Windows CE 3.0 drivers for their devices.
However, it is important to note that custom driver development requires substantial work, ranging from a few weeks to a few months. Engineers new to CE typically require three to six months to go through the steep learning curve, although training class will probably reduce the period of time. No matter what approach you take, it is important to allocate ample resources and time for Windows CE device driver development.
Jean Louis Gareau (BSCS, MSEE) is director of engineering at Annasoft Systems. Since 1997, he's been involved in the development of Windows CE devices, from platform adaptation to application development. He is also the author of Windows CE from the Ground Up (Annabooks, 1999). Contact him at .
- Listing 1: Driver access by applications
- Listing 2: Interrupt service routine
- Listing 3: Interrupt service thread