Writing device drivers for Windows CE is very similar to creating them on other operating systems. The operating system provides a handful of driver models that define the functions your driver must expose and their operation. What sets Windows CE apart is the rich set of driver frameworks that it provides. These allow driver writers to concentrate on the hardware specific bits while the framework handles the common tasks like queue management and interrupt routing.
Model Behavior
A device driver abstracts the functionality of a physical or virtual
device and manages the operation of these devices. Examples of physical
devices are network adapters, timers, and universal asynchronous
receiver-transmitters (UARTs). An example of a virtual device is a file
system. Implementing a device driver allows the functionality of your
device to be exposed to applications and other parts of the operating
system. Windows CE device drivers are trusted modules but they do not
have to run in kernel mode.
There are three main processes in Windows CE that load device drivers. Each process expects the driver to conform to a particular driver mode. The first process loaded by the kernel is filesys.exe, the file system process. This process loads file system drivers which must conform to the file system driver model.
After the file system and registry are loaded the device manager process, device.exe, is loaded. This process loads the majority of the device drivers in the system and they all expose the stream driver interface. Lastly, the graphics, windowing, and events subsystem (gwes.exe) runs and loads specialized drivers like display and keyboard drivers. These drivers conform to specialized driver models for their particular function. The following Table 1 below summarizes the three system processes that load device drivers.
Table 1 |
Windows CE supports the concept of installable file systems. When a device is attached such as a CompactFlash or SD/MMC drive the operating system will parse the partitions for the file system type and load the appropriate driver.
A file system driver (FSD) is a dynamic-link library (DLL) that exports file system entry points that map to standard Windows CE file system functions, such as CreateFile and CreateDirectory. When an application calls a file system function, and the function references a file on a volume registered by the FSD, the FSD Manager maps the call to the FSD. The FSD Manager manages all system interactions with installable FSDs.
The operating system provides a template for functions that you need to develop for a file system. The FSD may export the full range of functions entry points or it can exclude particular file system functions if you do not want that function to be available.
For example, to prevent applications from creating or destroying directories inside volumes belonging to your FSD, do not include CreateDirectory and RemoveDirectory in your FSD. The FSD Manager automatically supplies stub functions for any functions that you choose not to supply for your file system. These stub functions return an error code. As a result, if an application tries to create or remove a directory on a volume that belongs to your FSD, the call fails.
In addition to exporting entry points for the file system functions, an FSD must export entry points for the FSD_MountDisk and FSD_UnmountDisk functions. The Device Manager calls FSD_MountDisk when a target device for which that FSD has been registered is inserted or removed. The FSD_MountDisk and FSD_UnmountDisk entry points are each passed a value that uniquely identifies the disk being mounted. This value is passed back to the FSD Manager services that query, read, and write to that disk.
Stream Interface Drivers
The most common driver model in Windows CE is the stream interface
driver. This driver model has roots in the earliest implementations of
Unix. A stream driver exports functions to open, close, read, write,
seek, or control the underlying hardware.
The stream interface is appropriate for any I/O device that can be thought of logically as a data source or a data sink. That is, any peripheral that produces or consumes streams of data as its primary function is a good candidate to expose the stream interface. A good example is a serial port device. An example of a device that does not produce or consume data in the traditional sense would be a display device, and indeed, the stream interface is not exposed for controlling display hardware.
A stream interface driver receives commands from the Device Manager and from applications by means of file system calls. The driver encapsulates all of the information that is necessary to translate those commands into appropriate actions on the devices that it controls.
All stream interface drivers, whether they manage built-in devices or installable devices, or whether they are loaded at boot time or loaded dynamically, have similar interactions with other system components.
The following illustrations show the interactions between system components for a generic stream interface driver that manages a built-in device, and for a stream interface driver for a PC Card Client device. Figure 1, below, shows the architecture for stream interface drivers for built-in devices that are loaded by the Device Manager at boot time.
Figure 1 |
Block Drivers
A block driver allows reads and writes of the underlying hardware
resource through fixed sized block of data. Block devices do not allow
you to read or write individual bytes of data. Common block sizes are
512 bytes, 1 kilobyte (KB), 2 KB, and 4 KB. Block devices are ideally
suited for mass storage and persistent storage applications, such as
disk drives or nonvolatile RAM disks.
Some common types of block devices are hard disks and Advanced Technology Attachment (ATA) flash memory disks in miniature card, PC Card, and Compact Flash card form factors.
Drivers for block devices generally expose the stream interface, and the block devices appear as ordinary disk drives. Applications access files on a block device through standard file APIs such as CreateFile and ReadFile. The application calls the ReadFile function using a handle to a file that is stored on the block device.
The FAT file system for example, translates the read request to logical blocks. The FAT file system searches the buffer cache for the requested blocks. If these are not present, it issues an IOControl request to the corresponding block device driver to read bytes from the block device. Then, the block device driver receives the IOControl request, and then fulfills the request by accessing the block device through one of its low-level interfaces.
The FAT file system implementation supports block devices. The FAT file system does not read or write to block devices directly; it uses underlying block device drivers for all access to block device hardware. The block device driver must present the block device to the FAT file system as a block-oriented device. Block device drivers transparently manage or emulate ordinary disk drives, so applications do not need to behave differently when reading and writing files to the block device.
The FAT file system provides an abstraction between files in the application name space, such as \Storage Card\Excel Docs\Expense report.pxl, and devices in the device name space, such as DSK1:. Block device drivers must respond to the I/O control codes shown in Table 2 below to interface properly with the FAT file system.
Table 2 |
Bus Drivers
A bus driver is any software that loads other drivers. In this sense
device.exe is considered the "root" bus driver. A bus driver can be
thought of as having a hierarchical structure starting with the root
bus driver. Bus drivers have one or more of these responsibilities:
1. Managing physical busses,
such as PC Card, USB, or PCI.
2. Loading drivers on a
physical bus that the bus driver does not directly manage.
An example is the Bus Enumerator (regenum.dll),
which is a bus driver
that loads built-in drivers and PCI bus drivers.
3. Calling ActivateDeviceEx
directly to load a device driver.
4. The loaded device driver
might manage hardware indirectly through another
device driver.
To allow access to a device driver with CreateFile, bus drivers should provide the Device Manager with enough information to create bus-relative names and enable device handles. They should also provide enough information to identify themselves to drivers and applications for bus control (IOCTL) operations.
A bus driver can specify a busrelative name for the driver, but for drivers and applications to be able to access the bus driver for bus control operations, the bus driver must expose a stream interface. The $bus namespace provides a way to perform operations on a device that are different from typical device operations. By controlling access to the $bus namespace, you can also provide security for your system.
Bus Agnostic Drivers
A bus agnostic driver is written without knowledge where the underlying
hardware device that it manages is located. The device may reside on
the local microprocessor bus or it may be on a CompactFlash card
inserted into the system.
A bus agnostic driver does not call functions that are specific to a hardware platform. Each driver gets its resources from the registry, requests configuration information from its parent bus driver, sets power states through its parent bus driver, and translates bus addresses to system addresses through its parent bus driver. Typically, you can migrate bus agnostic drivers more easily between hardware platforms than other types of drivers.
A bus agnostic driver must adhere to the following tasks to determine the location of its hardware resources in the system:
1. Open the device key by
calling the OpenDeviceKey function.
2. Obtain ISR information by
calling the DDKReg_GetIsrInfo function.
3. Obtain hardware I/O or
memory window information by calling the DDKReg_GetWindowInfo function.
4. Obtain a hardware and by
calling the HalTranslateBusAddress function to translate the
bus-specific address to a system physical address.
5. Obtain a virtual address by
calling the MnMapIoSpace function to map the system physical address.
6. Reset your hardware and
disable the interrupt.
7. Load the installable ISR by
calling the LoadIntChainHandler function with information obtained from
DDKReg_GetIsrInfo. You should assume that your ISR will access the card
register to identify the interrupt. TransBusAddrToStatic is needed to
map the system physical address for hardware.
8. Begin the IST and enable the
interrupt.
Layered vs. Monolithic Drivers
Most device drivers consist of platform or hardware specific code and
model or device type specific code. Windows CE refers to these two
pieces as the platform dependent driver (PDD) and the model dependent
driver (MDD).
This is a slight misnomer because the two pieces are actually software modules or a way of organizing the source code. It is not until you link the two pieces together do you truly have a "driver". If a device driver is organized in this way it is referred to as a layered driver.
A monolithic driver is one that does not separate the source code in this fashion. Because the monolithic driver does not define an API isolating the MDD and PDD modules it contains less code and is generally faster (more efficient). However, this does make the driver more difficult to maintain and less portable to future versions of Windows CE.
The obvious advantage of the MDD/PDD model is that it encourages code reuse. The device type or device model code is theoretically the same regardless of the underlying hardware.
For example all serial drivers will need to process receive and transmit buffers and handle flow control logic. These operations are independent of the hardware and should be isolated into a MDD module for code reuse. Because the MDD module is at the topmost layer of the driver it exposes the stream interface to the Device Manager. Some examples of tasks performed by the MDD module are:
1. Contain code that is
common to all drivers of a given type.
2. Call PDD functions to access
the hardware.
3. Link to the PDD layer and
define the device driver service provider interface (DDSI) functions
that the MDD expects to call in that layer.
4. Expose device driver
interface (DDI) functions to the operating system (OS). Other parts of
the OS can call these functions. Related devices can share the same
DDI. Monolithic drivers also expose the DDI functions.
5. Handle interrupt processing.
6. Provide for reuse by
developers.
7. Can link to multiple PDDs.
8. Generally require no
changes. (If changed, you might have trouble migrating drivers to
future versions.)
9. Contain any interrupt
service threads (ISTs).
The PDD module has the following characteristics:
1. Consist of hardware
platform specific code.
2. Might require modification
for your hardware platform.
3. Are designed to work with
specific MDD implementations.
4. Expose the DDSI functions
that the MDD calls. (Monolithic drivers do not expose the DDSI
functions.)
Windows CE provides many MDD modules for a variety of device types.
Serial Drivers
The serial port driver handles any I/O devices that behave like serial
ports, including those based on 16450 and 16550 universal asynchronous
receiver-transmitter (UART) chips and those that use direct memory
access (DMA).
Many hardware platforms have devices of this type, including ordinary 9-pin serial ports, infrared I/O ports, and PC Card serial devices, such as modems. If multiple types of serial ports exist on a hardware platform, you can either create several different serial drivers, one for each serial port type, or create several different lower layers (PDD) and link them to a single upper layer (MDD). This creates one multipurpose serial driver. Creating separate drivers can simplify debugging and maintenance because each driver supports only one type of port. Creating a multipurpose driver, such as the sample serial port driver, is more complex but gives a small memory savings.
Because the functionality of serial ports maps to the functionality provided by standard stream interface functions, the serial port driver uses the stream interface as its device interface. The serial port driver is a classic example of the MDD/PDD model. The MDD module supplied with Windows CE strives to handle all of the device-independent serial port functions leaving the PDD module as simple as possible.
Serial port devices make extensive use of I/O control codes to set and query various attributes of a serial port. For example, there are I/O control codes for setting and clearing the Data Terminal Ready (DTR) line, for purging any undelivered data and for getting the status of a modem device. Therefore, you should support as many serial I/O control codes as possible in your implementation of COM_IOControl.
Audio Drivers
Window CE supports two driver models for audio devices: the MDD/PDD
model and the unified audio model (UAM). The MDD/PDD model is suitable
for only the simplest of audio devices.
Because the MDD is designed to support the most common subset of functions that audio devices require there is bound to be functionality on more complex audio devices that is not covered. Instead of forcing developers to modify the MDD in that situation the unified audio model was created. A driver that conforms to the UAM is a structured monolithic driver. The UAM gives the developer complete control over the audio device but in a structured manner. Figure 2a and 2b below illustrate the subtle differences in the two driver models.
Figure 2a |
Figure 2b |
Network Drivers
Window CE supports the same network driver interface specification
(NDIS) that the desktop version of Windows supports. This makes porting
network device drivers from the desktop to Windows CE very
straightforward. NDIS also provides a pair of abstraction layers that
connect network drivers to an overlying protocol stack, such as TCP/IP
or Infrared Data Association (IrDA) and an underlying network adapter.
NDIS performs a set of external functions for network adapter
drivers, such as registering and intercepting hardware interrupts and
communicating with underlying network adapters. As shown in Figure 3
below, NDIS provides driver interfaces (API) for accessing the
upper-layer network stack and for accessing hardware. This high level
of structure makes NDIS drivers very portable.
Figure 3 |
The only thing constant is change
Windows CE 6.0 introduces user-mode drivers. These drivers run in the
user space of memory and it is very difficult for these drivers to
crash the system. The traditional kernel-mode drivers share the same
memory space as the kernel and other drivers and could potentially
overwrite the memory of other processes and crash the system.
The following text is excerpted from the release notes included with the beta.
The new virtual memory architecture has significant implications for device drivers. Previous versions of the OS used special processes to add functionality to the base kernel. For example, one process managed loadable device drivers, another managed registry and file systems, another managed the windowing system, another managed system services, and so forth. In Windows CE 6 Beta, many system services execute in the context of the kernel. This improves the performance of intra-kernel calls that, in previous releases, required multiple traps into the kernel.
In Windows CE 6
Beta, device
drivers need to be aware of any process that passes them pointers to
memory buffers. In previous versions, each process had a unique virtual
address space, so this was not a significant consideration. In Windows
CE 6 Beta, a particular user-mode address might be valid in multiple
processes. Changes to drivers are documented in detail in the Help. In
many cases, drivers from previous releases are relatively easy to port
to Windows CE 6 Beta OS.
David Heil is chief engineer at CalAmp Solutions Corp.
This article is excerpted from a paper of the same name presented at the Embedded Systems Conference Boston 2006. Used with permission of the Embedded Systems Conference. For more information, please visit www.embedded.com/esc/boston/ |